mirror of
https://github.com/AleziaKurdis/overte.git
synced 2025-04-14 02:06:48 +02:00
Merge branch 'master' into scale
This commit is contained in:
commit
0751443a01
69 changed files with 2178 additions and 1398 deletions
|
@ -32,7 +32,7 @@ var BUTTON_SIZE = 32;
|
|||
var PADDING = 3;
|
||||
|
||||
var offButton = Overlays.addOverlay("image", {
|
||||
x: screenSize.x / 2 - BUTTON_SIZE,
|
||||
x: screenSize.x / 2 - BUTTON_SIZE * 2 + PADDING,
|
||||
y: screenSize.y- (BUTTON_SIZE + PADDING),
|
||||
width: BUTTON_SIZE,
|
||||
height: BUTTON_SIZE,
|
||||
|
@ -40,6 +40,17 @@ var offButton = Overlays.addOverlay("image", {
|
|||
color: { red: 255, green: 255, blue: 255},
|
||||
alpha: 1
|
||||
});
|
||||
|
||||
var deleteButton = Overlays.addOverlay("image", {
|
||||
x: screenSize.x / 2 - BUTTON_SIZE,
|
||||
y: screenSize.y- (BUTTON_SIZE + PADDING),
|
||||
width: BUTTON_SIZE,
|
||||
height: BUTTON_SIZE,
|
||||
imageURL: HIFI_PUBLIC_BUCKET + "images/delete.png",
|
||||
color: { red: 255, green: 255, blue: 255},
|
||||
alpha: 1
|
||||
});
|
||||
|
||||
var diceButton = Overlays.addOverlay("image", {
|
||||
x: screenSize.x / 2 + PADDING,
|
||||
y: screenSize.y - (BUTTON_SIZE + PADDING),
|
||||
|
@ -108,6 +119,8 @@ function mousePressEvent(event) {
|
|||
if (clickedOverlay == offButton) {
|
||||
deleteDice();
|
||||
Script.stop();
|
||||
} else if (clickedOverlay == deleteButton) {
|
||||
deleteDice();
|
||||
} else if (clickedOverlay == diceButton) {
|
||||
var HOW_HARD = 2.0;
|
||||
var position = Vec3.sum(Camera.getPosition(), Quat.getFront(Camera.getOrientation()));
|
||||
|
@ -120,6 +133,7 @@ function mousePressEvent(event) {
|
|||
function scriptEnding() {
|
||||
Overlays.deleteOverlay(offButton);
|
||||
Overlays.deleteOverlay(diceButton);
|
||||
Overlays.deleteOverlay(deleteButton);
|
||||
}
|
||||
|
||||
Entities.entityCollisionWithEntity.connect(entityCollisionWithEntity);
|
||||
|
|
|
@ -52,7 +52,7 @@ var button = Overlays.addOverlay('image', {
|
|||
width: BUTTON_DIMENSIONS.width,
|
||||
height: BUTTON_DIMENSIONS.height,
|
||||
imageURL: HIFI_PUBLIC_BUCKET + 'marketplace/hificontent/Games/blocks/planky_button.svg',
|
||||
alpha: 1
|
||||
alpha: 0.8
|
||||
});
|
||||
|
||||
|
||||
|
@ -93,7 +93,7 @@ function resetBlocks() {
|
|||
type: 'Model',
|
||||
modelURL: HIFI_PUBLIC_BUCKET + 'marketplace/hificontent/Games/blocks/block.fbx',
|
||||
shapeType: 'box',
|
||||
name: 'JengaBlock' + ((layerIndex * BLOCKS_PER_LAYER) + blockIndex),
|
||||
name: 'PlankyBlock' + ((layerIndex * BLOCKS_PER_LAYER) + blockIndex),
|
||||
dimensions: BLOCK_SIZE,
|
||||
position: {
|
||||
x: basePosition.x + localTransform.x,
|
||||
|
@ -133,7 +133,10 @@ function cleanup() {
|
|||
}
|
||||
|
||||
function onUpdate() {
|
||||
|
||||
if (windowWidth != Window.innerWidth) {
|
||||
windowWidth = Window.innerWidth;
|
||||
Overlays.editOverlay(button, {x: getButtonPosX()});
|
||||
}
|
||||
}
|
||||
|
||||
Script.update.connect(onUpdate)
|
||||
|
|
|
@ -179,7 +179,7 @@ function update(deltaTime) {
|
|||
if (distanceToTarget > CLOSE_ENOUGH) {
|
||||
// compute current velocity in the direction we want to move
|
||||
velocityTowardTarget = Vec3.dot(currentVelocity, Vec3.normalize(dPosition));
|
||||
velocityTowardTarget = Vec3.multiply(dPosition, velocityTowardTarget);
|
||||
velocityTowardTarget = Vec3.multiply(Vec3.normalize(dPosition), velocityTowardTarget);
|
||||
// compute the speed we would like to be going toward the target position
|
||||
|
||||
desiredVelocity = Vec3.multiply(dPosition, (1.0 / deltaTime) * SPRING_RATE);
|
||||
|
@ -193,12 +193,15 @@ function update(deltaTime) {
|
|||
// Add Damping
|
||||
newVelocity = Vec3.subtract(newVelocity, Vec3.multiply(newVelocity, DAMPING_RATE));
|
||||
// Update entity
|
||||
|
||||
//add damping to angular velocity:
|
||||
} else {
|
||||
newVelocity = entityProps.velocity;
|
||||
}
|
||||
if (shouldRotate) {
|
||||
angularVelocity = Vec3.subtract(angularVelocity, Vec3.multiply(angularVelocity, ANGULAR_DAMPING_RATE));
|
||||
} else {
|
||||
angularVelocity = entityProps.angularVelocity;
|
||||
}
|
||||
|
||||
Entities.editEntity(grabbedEntity, {
|
||||
velocity: newVelocity,
|
||||
angularVelocity: angularVelocity
|
||||
|
|
|
@ -16,8 +16,7 @@
|
|||
var ball, disc;
|
||||
var time = 0.0;
|
||||
var range = 1.0;
|
||||
var speed = 0.5;
|
||||
|
||||
var omega = 2.0 * Math.PI / 32.0;
|
||||
|
||||
var basePosition = Vec3.sum(Camera.getPosition(), Quat.getFront(Camera.getOrientation()));
|
||||
|
||||
|
@ -25,35 +24,44 @@ ball = Entities.addEntity(
|
|||
{ type: "Box",
|
||||
position: basePosition,
|
||||
dimensions: { x: 0.1, y: 0.1, z: 0.1 },
|
||||
color: { red: 255, green: 0, blue: 255 }
|
||||
color: { red: 255, green: 0, blue: 255 },
|
||||
collisionsWillMove: false,
|
||||
ignoreForCollisions: true
|
||||
});
|
||||
|
||||
disc = Entities.addEntity(
|
||||
{ type: "Sphere",
|
||||
position: basePosition,
|
||||
dimensions: { x: range, y: range / 20.0, z: range },
|
||||
dimensions: { x: range * 0.8, y: range / 20.0, z: range * 0.8},
|
||||
color: { red: 128, green: 128, blue: 128 }
|
||||
});
|
||||
|
||||
function randomColor() {
|
||||
return { red: Math.random() * 255, green: Math.random() * 255, blue: Math.random() * 255 };
|
||||
}
|
||||
|
||||
function update(deltaTime) {
|
||||
time += deltaTime * speed;
|
||||
time += deltaTime;
|
||||
if (!ball.isKnownID) {
|
||||
ball = Entities.identifyEntity(ball);
|
||||
}
|
||||
rotation = Quat.angleAxis(time/Math.PI * 180.0, { x: 0, y: 1, z: 0 });
|
||||
rotation = Quat.angleAxis(time * omega /Math.PI * 180.0, { x: 0, y: 1, z: 0 });
|
||||
Entities.editEntity(ball,
|
||||
{
|
||||
color: { red: 255 * (Math.sin(time)/2.0 + 0.5),
|
||||
green: 255 - 255 * (Math.sin(time)/2.0 + 0.5),
|
||||
color: { red: 255 * (Math.sin(time * omega)/2.0 + 0.5),
|
||||
green: 255 - 255 * (Math.sin(time * omega)/2.0 + 0.5),
|
||||
blue: 0 },
|
||||
position: { x: basePosition.x + Math.sin(time) / 2.0 * range,
|
||||
position: { x: basePosition.x + Math.sin(time * omega) / 2.0 * range,
|
||||
y: basePosition.y,
|
||||
z: basePosition.z + Math.cos(time) / 2.0 * range },
|
||||
velocity: { x: Math.cos(time)/2.0 * range,
|
||||
z: basePosition.z + Math.cos(time * omega) / 2.0 * range },
|
||||
velocity: { x: Math.cos(time * omega)/2.0 * range * omega,
|
||||
y: 0.0,
|
||||
z: -Math.sin(time)/2.0 * range },
|
||||
z: -Math.sin(time * omega)/2.0 * range * omega},
|
||||
rotation: rotation
|
||||
});
|
||||
if (Math.random() < 0.007) {
|
||||
Entities.editEntity(disc, { color: randomColor() });
|
||||
}
|
||||
}
|
||||
|
||||
function scriptEnding() {
|
||||
|
|
|
@ -33,6 +33,21 @@
|
|||
);
|
||||
};
|
||||
}
|
||||
function createEmitGroupNumberPropertyUpdateFunction(group, propertyName) {
|
||||
return function() {
|
||||
var properties = {};
|
||||
properties[group] = {};
|
||||
properties[group][propertyName] = this.value;
|
||||
EventBridge.emitWebEvent(
|
||||
JSON.stringify({
|
||||
type: "update",
|
||||
properties: properties,
|
||||
})
|
||||
);
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
function createEmitTextPropertyUpdateFunction(propertyName) {
|
||||
return function() {
|
||||
var properties = {};
|
||||
|
@ -46,6 +61,20 @@
|
|||
};
|
||||
}
|
||||
|
||||
function createEmitGroupTextPropertyUpdateFunction(group,propertyName) {
|
||||
return function() {
|
||||
var properties = {};
|
||||
properties[group] = {};
|
||||
properties[group][propertyName] = this.value;
|
||||
EventBridge.emitWebEvent(
|
||||
JSON.stringify({
|
||||
type: "update",
|
||||
properties: properties,
|
||||
})
|
||||
);
|
||||
};
|
||||
}
|
||||
|
||||
function createEmitVec3PropertyUpdateFunction(property, elX, elY, elZ) {
|
||||
return function() {
|
||||
var data = {
|
||||
|
@ -62,6 +91,23 @@
|
|||
}
|
||||
};
|
||||
|
||||
function createEmitGroupVec3PropertyUpdateFunction(group, property, elX, elY, elZ) {
|
||||
return function() {
|
||||
var data = {
|
||||
type: "update",
|
||||
properties: {
|
||||
}
|
||||
};
|
||||
data.properties[group] = { };
|
||||
data.properties[group][property] = {
|
||||
x: elX.value,
|
||||
y: elY.value,
|
||||
z: elZ.value,
|
||||
};
|
||||
EventBridge.emitWebEvent(JSON.stringify(data));
|
||||
}
|
||||
};
|
||||
|
||||
function createEmitVec3PropertyUpdateFunctionWithMultiplier(property, elX, elY, elZ, multiplier) {
|
||||
return function() {
|
||||
var data = {
|
||||
|
@ -94,6 +140,24 @@
|
|||
}
|
||||
};
|
||||
|
||||
function createEmitGroupColorPropertyUpdateFunction(group, property, elRed, elGreen, elBlue) {
|
||||
return function() {
|
||||
var data = {
|
||||
type: "update",
|
||||
properties: {
|
||||
}
|
||||
};
|
||||
data.properties[group] = { };
|
||||
|
||||
data.properties[group][property] = {
|
||||
red: elRed.value,
|
||||
green: elGreen.value,
|
||||
blue: elBlue.value,
|
||||
};
|
||||
EventBridge.emitWebEvent(JSON.stringify(data));
|
||||
}
|
||||
};
|
||||
|
||||
function loaded() {
|
||||
var allSections = [];
|
||||
var elID = document.getElementById("property-id");
|
||||
|
@ -205,6 +269,24 @@
|
|||
var elZoneStageDay = document.getElementById("property-zone-stage-day");
|
||||
var elZoneStageHour = document.getElementById("property-zone-stage-hour");
|
||||
|
||||
var elZoneBackgroundMode = document.getElementById("property-zone-background-mode");
|
||||
|
||||
var elZoneSkyboxColorRed = document.getElementById("property-zone-skybox-color-red");
|
||||
var elZoneSkyboxColorGreen = document.getElementById("property-zone-skybox-color-green");
|
||||
var elZoneSkyboxColorBlue = document.getElementById("property-zone-skybox-color-blue");
|
||||
var elZoneSkyboxURL = document.getElementById("property-zone-skybox-url");
|
||||
|
||||
var elZoneAtmosphereCenterX = document.getElementById("property-zone-atmosphere-center-x");
|
||||
var elZoneAtmosphereCenterY = document.getElementById("property-zone-atmosphere-center-y");
|
||||
var elZoneAtmosphereCenterZ = document.getElementById("property-zone-atmosphere-center-z");
|
||||
var elZoneAtmosphereInnerRadius = document.getElementById("property-zone-atmosphere-inner-radius");
|
||||
var elZoneAtmosphereOuterRadius = document.getElementById("property-zone-atmosphere-outer-radius");
|
||||
var elZoneAtmosphereMieScattering = document.getElementById("property-zone-atmosphere-mie-scattering");
|
||||
var elZoneAtmosphereRayleighScattering = document.getElementById("property-zone-atmosphere-rayleigh-scattering");
|
||||
var elZoneAtmosphereScatteringWavelengthsX = document.getElementById("property-zone-atmosphere-scattering-wavelengths-x");
|
||||
var elZoneAtmosphereScatteringWavelengthsY = document.getElementById("property-zone-atmosphere-scattering-wavelengths-y");
|
||||
var elZoneAtmosphereScatteringWavelengthsZ = document.getElementById("property-zone-atmosphere-scattering-wavelengths-z");
|
||||
|
||||
if (window.EventBridge !== undefined) {
|
||||
EventBridge.scriptEventReceived.connect(function(data) {
|
||||
data = JSON.parse(data);
|
||||
|
@ -386,6 +468,26 @@
|
|||
elZoneStageHour.value = properties.stageHour;
|
||||
elShapeType.value = properties.shapeType;
|
||||
elCompoundShapeURL.value = properties.compoundShapeURL;
|
||||
|
||||
elZoneBackgroundMode.value = properties.backgroundMode;
|
||||
|
||||
elZoneSkyboxColorRed.value = properties.skybox.color.red;
|
||||
elZoneSkyboxColorGreen.value = properties.skybox.color.green;
|
||||
elZoneSkyboxColorBlue.value = properties.skybox.color.blue;
|
||||
elZoneSkyboxURL.value = properties.skybox.url;
|
||||
|
||||
elZoneAtmosphereCenterX.value = properties.atmosphere.center.x;
|
||||
elZoneAtmosphereCenterY.value = properties.atmosphere.center.y;
|
||||
elZoneAtmosphereCenterZ.value = properties.atmosphere.center.z;
|
||||
elZoneAtmosphereInnerRadius.value = properties.atmosphere.innerRadius;
|
||||
elZoneAtmosphereOuterRadius.value = properties.atmosphere.outerRadius;
|
||||
elZoneAtmosphereMieScattering.value = properties.atmosphere.mieScattering;
|
||||
elZoneAtmosphereRayleighScattering.value = properties.atmosphere.rayleighScattering;
|
||||
elZoneAtmosphereScatteringWavelengthsX.value = properties.atmosphere.scatteringWavelengths.x;
|
||||
elZoneAtmosphereScatteringWavelengthsY.value = properties.atmosphere.scatteringWavelengths.y;
|
||||
elZoneAtmosphereScatteringWavelengthsZ.value = properties.atmosphere.scatteringWavelengths.z;
|
||||
|
||||
|
||||
}
|
||||
|
||||
if (selected) {
|
||||
|
@ -521,6 +623,34 @@
|
|||
elZoneStageDay.addEventListener('change', createEmitNumberPropertyUpdateFunction('stageDay'));
|
||||
elZoneStageHour.addEventListener('change', createEmitNumberPropertyUpdateFunction('stageHour'));
|
||||
|
||||
|
||||
elZoneBackgroundMode.addEventListener('change', createEmitTextPropertyUpdateFunction('backgroundMode'));
|
||||
var zoneSkyboxColorChangeFunction = createEmitGroupColorPropertyUpdateFunction('skybox','color',
|
||||
elZoneSkyboxColorRed, elZoneSkyboxColorGreen, elZoneSkyboxColorBlue);
|
||||
elZoneSkyboxColorRed.addEventListener('change', zoneSkyboxColorChangeFunction);
|
||||
elZoneSkyboxColorGreen.addEventListener('change', zoneSkyboxColorChangeFunction);
|
||||
elZoneSkyboxColorBlue.addEventListener('change', zoneSkyboxColorChangeFunction);
|
||||
|
||||
elZoneSkyboxURL.addEventListener('change', createEmitGroupTextPropertyUpdateFunction('skybox','url'));
|
||||
|
||||
var zoneAtmosphereCenterChangeFunction = createEmitGroupVec3PropertyUpdateFunction(
|
||||
'atmosphere','center', elZoneAtmosphereCenterX, elZoneAtmosphereCenterY, elZoneAtmosphereCenterZ);
|
||||
elZoneAtmosphereCenterX.addEventListener('change', zoneAtmosphereCenterChangeFunction);
|
||||
elZoneAtmosphereCenterY.addEventListener('change', zoneAtmosphereCenterChangeFunction);
|
||||
elZoneAtmosphereCenterZ.addEventListener('change', zoneAtmosphereCenterChangeFunction);
|
||||
|
||||
|
||||
elZoneAtmosphereInnerRadius.addEventListener('change', createEmitGroupNumberPropertyUpdateFunction('atmosphere','innerRadius'));
|
||||
elZoneAtmosphereOuterRadius.addEventListener('change', createEmitGroupNumberPropertyUpdateFunction('atmosphere','outerRadius'));
|
||||
elZoneAtmosphereMieScattering.addEventListener('change', createEmitGroupNumberPropertyUpdateFunction('atmosphere','mieScattering'));
|
||||
elZoneAtmosphereRayleighScattering.addEventListener('change', createEmitGroupNumberPropertyUpdateFunction('atmosphere','rayleighScattering'));
|
||||
var zoneAtmosphereScatterWavelengthsChangeFunction = createEmitGroupVec3PropertyUpdateFunction(
|
||||
'atmosphere','scatteringWavelengths', elZoneAtmosphereScatteringWavelengthsX,
|
||||
elZoneAtmosphereScatteringWavelengthsY, elZoneAtmosphereScatteringWavelengthsZ);
|
||||
elZoneAtmosphereScatteringWavelengthsX.addEventListener('change', zoneAtmosphereScatterWavelengthsChangeFunction);
|
||||
elZoneAtmosphereScatteringWavelengthsY.addEventListener('change', zoneAtmosphereScatterWavelengthsChangeFunction);
|
||||
elZoneAtmosphereScatteringWavelengthsZ.addEventListener('change', zoneAtmosphereScatterWavelengthsChangeFunction);
|
||||
|
||||
elMoveSelectionToGrid.addEventListener("click", function() {
|
||||
EventBridge.emitWebEvent(JSON.stringify({
|
||||
type: "action",
|
||||
|
@ -958,6 +1088,72 @@
|
|||
<input class="coord" type='number' id="property-zone-stage-hour" min="0" max="24" step="0.5"></input>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="zone-section property">
|
||||
<div class="label">Background Mode</div>
|
||||
<div class="value">
|
||||
<select name="SelectBackgroundMode" id="property-zone-background-mode" name="SelectBackgroundMode">
|
||||
<option value='inherit'>Nothing</option>
|
||||
<option value='skybox'>Skybox</option>
|
||||
<option value='atmosphere'>Atmosphere</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="zone-section property">
|
||||
<div class="label">Skybox Color</div>
|
||||
<div class="value">
|
||||
<div class="input-area">R <input class="coord" type='number' id="property-zone-skybox-color-red"></input></div>
|
||||
<div class="input-area">G <input class="coord" type='number' id="property-zone-skybox-color-green"></input></div>
|
||||
<div class="input-area">B <input class="coord" type='number' id="property-zone-skybox-color-blue"></input></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="zone-section property">
|
||||
<div class="label">Skybox URL</div>
|
||||
<div class="value">
|
||||
<input type="text" id="property-zone-skybox-url" class="url"></input>
|
||||
</div>
|
||||
</div>
|
||||
<div class="zone-section property">
|
||||
<div class="label">Atmosphere Center</div>
|
||||
<div class="value">
|
||||
<div class="input-area">X <br><input class="coord" type='number' id="property-zone-atmosphere-center-x"></input></div>
|
||||
<div class="input-area">Y <br><input class="coord" type='number' id="property-zone-atmosphere-center-y"></input></div>
|
||||
<div class="input-area">Z <br><input class="coord" type='number' id="property-zone-atmosphere-center-z"></input></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="zone-section property">
|
||||
<div class="label">Atmosphere Inner Radius</div>
|
||||
<div class="value">
|
||||
<input class="coord" type='number' id="property-zone-atmosphere-inner-radius" step="1"></input>
|
||||
</div>
|
||||
</div>
|
||||
<div class="zone-section property">
|
||||
<div class="label">Atmosphere Outer Radius</div>
|
||||
<div class="value">
|
||||
<input class="coord" type='number' id="property-zone-atmosphere-outer-radius" step="1"></input>
|
||||
</div>
|
||||
</div>
|
||||
<div class="zone-section property">
|
||||
<div class="label">Atmosphere Mie Scattering</div>
|
||||
<div class="value">
|
||||
<input class="coord" type='number' id="property-zone-atmosphere-mie-scattering" min="0" max="0.5" step="0.001"></input>
|
||||
</div>
|
||||
</div>
|
||||
<div class="zone-section property">
|
||||
<div class="label">Atmosphere Rayleigh Scattering</div>
|
||||
<div class="value">
|
||||
<input class="coord" type='number' id="property-zone-atmosphere-rayleigh-scattering" min="0" max="0.5" step="0.001"></input>
|
||||
</div>
|
||||
</div>
|
||||
<div class="zone-section property">
|
||||
<div class="label">Atmosphere Scattering Wavelenghts</div>
|
||||
<div class="value">
|
||||
<div class="input-area">X <br><input class="coord" type='number' id="property-zone-atmosphere-scattering-wavelengths-x" min="0" max="1" step="0.001"></input></div>
|
||||
<div class="input-area">Y <br><input class="coord" type='number' id="property-zone-atmosphere-scattering-wavelengths-y" min="0" max="1" step="0.001"></input></div>
|
||||
<div class="input-area">Z <br><input class="coord" type='number' id="property-zone-atmosphere-scattering-wavelengths-z" min="0" max="1" step="0.001"></input></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
</body>
|
||||
|
|
|
@ -203,7 +203,7 @@ SelectionManager = (function() {
|
|||
try {
|
||||
listeners[i]();
|
||||
} catch (e) {
|
||||
print("got exception");
|
||||
print("EntitySelectionTool got exception: " = JSON.stringify(e));
|
||||
}
|
||||
}
|
||||
};
|
||||
|
|
|
@ -71,6 +71,7 @@
|
|||
#include <NetworkingConstants.h>
|
||||
#include <OctalCode.h>
|
||||
#include <OctreeSceneStats.h>
|
||||
#include <ObjectMotionState.h>
|
||||
#include <PacketHeaders.h>
|
||||
#include <PathUtils.h>
|
||||
#include <PerfStat.h>
|
||||
|
@ -432,6 +433,7 @@ Application::Application(int& argc, char** argv, QElapsedTimer &startup_time) :
|
|||
connect(nodeList.data(), &NodeList::nodeKilled, this, &Application::nodeKilled);
|
||||
connect(nodeList.data(), SIGNAL(nodeKilled(SharedNodePointer)), SLOT(nodeKilled(SharedNodePointer)));
|
||||
connect(nodeList.data(), &NodeList::uuidChanged, _myAvatar, &MyAvatar::setSessionUUID);
|
||||
connect(nodeList.data(), &NodeList::uuidChanged, this, &Application::setSessionUUID);
|
||||
connect(nodeList.data(), &NodeList::limitOfSilentDomainCheckInsReached, nodeList.data(), &NodeList::reset);
|
||||
connect(nodeList.data(), &NodeList::packetVersionMismatch, this, &Application::notifyPacketVersionMismatch);
|
||||
|
||||
|
@ -690,6 +692,9 @@ Application::~Application() {
|
|||
nodeThread->quit();
|
||||
nodeThread->wait();
|
||||
|
||||
Leapmotion::destroy();
|
||||
RealSense::destroy();
|
||||
|
||||
qInstallMessageHandler(NULL); // NOTE: Do this as late as possible so we continue to get our log messages
|
||||
}
|
||||
|
||||
|
@ -2131,19 +2136,20 @@ void Application::init() {
|
|||
_entities.init();
|
||||
_entities.setViewFrustum(getViewFrustum());
|
||||
|
||||
EntityTree* tree = _entities.getTree();
|
||||
_physicsEngine.setEntityTree(tree);
|
||||
tree->setSimulation(&_physicsEngine);
|
||||
_physicsEngine.init(&_entityEditSender);
|
||||
ObjectMotionState::setShapeManager(&_shapeManager);
|
||||
_physicsEngine.init();
|
||||
|
||||
EntityTree* tree = _entities.getTree();
|
||||
_entitySimulation.init(tree, &_physicsEngine, &_shapeManager, &_entityEditSender);
|
||||
tree->setSimulation(&_entitySimulation);
|
||||
|
||||
auto entityScriptingInterface = DependencyManager::get<EntityScriptingInterface>();
|
||||
|
||||
connect(&_physicsEngine, &EntitySimulation::entityCollisionWithEntity,
|
||||
connect(&_entitySimulation, &EntitySimulation::entityCollisionWithEntity,
|
||||
entityScriptingInterface.data(), &EntityScriptingInterface::entityCollisionWithEntity);
|
||||
|
||||
// connect the _entityCollisionSystem to our EntityTreeRenderer since that's what handles running entity scripts
|
||||
connect(&_physicsEngine, &EntitySimulation::entityCollisionWithEntity,
|
||||
connect(&_entitySimulation, &EntitySimulation::entityCollisionWithEntity,
|
||||
&_entities, &EntityTreeRenderer::entityCollisionWithEntity);
|
||||
|
||||
// connect the _entities (EntityTreeRenderer) to our script engine's EntityScriptingInterface for firing
|
||||
|
@ -2427,8 +2433,22 @@ void Application::update(float deltaTime) {
|
|||
{
|
||||
PerformanceTimer perfTimer("physics");
|
||||
_myAvatar->relayDriveKeysToCharacterController();
|
||||
|
||||
_entitySimulation.lock();
|
||||
_physicsEngine.deleteObjects(_entitySimulation.getObjectsToDelete());
|
||||
_physicsEngine.addObjects(_entitySimulation.getObjectsToAdd());
|
||||
_physicsEngine.changeObjects(_entitySimulation.getObjectsToChange());
|
||||
_entitySimulation.unlock();
|
||||
|
||||
_physicsEngine.stepSimulation();
|
||||
_physicsEngine.dumpStatsIfNecessary();
|
||||
|
||||
if (_physicsEngine.hasOutgoingChanges()) {
|
||||
_entitySimulation.lock();
|
||||
_entitySimulation.handleOutgoingChanges(_physicsEngine.getOutgoingChanges());
|
||||
_entitySimulation.handleCollisionEvents(_physicsEngine.getCollisionEvents());
|
||||
_entitySimulation.unlock();
|
||||
_physicsEngine.dumpStatsIfNecessary();
|
||||
}
|
||||
}
|
||||
|
||||
if (!_aboutToQuit) {
|
||||
|
@ -2639,7 +2659,10 @@ void Application::queryOctree(NodeType_t serverType, PacketType packetType, Node
|
|||
if (rootCode) {
|
||||
VoxelPositionSize rootDetails;
|
||||
voxelDetailsForCode(rootCode, rootDetails);
|
||||
AACube serverBounds(glm::vec3(rootDetails.x, rootDetails.y, rootDetails.z), rootDetails.s);
|
||||
AACube serverBounds(glm::vec3(rootDetails.x * TREE_SCALE,
|
||||
rootDetails.y * TREE_SCALE,
|
||||
rootDetails.z * TREE_SCALE),
|
||||
rootDetails.s * TREE_SCALE);
|
||||
|
||||
ViewFrustum::location serverFrustumLocation = _viewFrustum.cubeInFrustum(serverBounds);
|
||||
|
||||
|
@ -2680,7 +2703,6 @@ void Application::queryOctree(NodeType_t serverType, PacketType packetType, Node
|
|||
// only send to the NodeTypes that are serverType
|
||||
if (node->getActiveSocket() && node->getType() == serverType) {
|
||||
|
||||
|
||||
// get the server bounds for this server
|
||||
QUuid nodeUUID = node->getUUID();
|
||||
|
||||
|
@ -2702,7 +2724,12 @@ void Application::queryOctree(NodeType_t serverType, PacketType packetType, Node
|
|||
if (rootCode) {
|
||||
VoxelPositionSize rootDetails;
|
||||
voxelDetailsForCode(rootCode, rootDetails);
|
||||
AACube serverBounds(glm::vec3(rootDetails.x, rootDetails.y, rootDetails.z), rootDetails.s);
|
||||
AACube serverBounds(glm::vec3(rootDetails.x * TREE_SCALE,
|
||||
rootDetails.y * TREE_SCALE,
|
||||
rootDetails.z * TREE_SCALE),
|
||||
rootDetails.s * TREE_SCALE);
|
||||
|
||||
|
||||
|
||||
ViewFrustum::location serverFrustumLocation = _viewFrustum.cubeInFrustum(serverBounds);
|
||||
if (serverFrustumLocation != ViewFrustum::OUTSIDE) {
|
||||
|
@ -2748,7 +2775,7 @@ void Application::queryOctree(NodeType_t serverType, PacketType packetType, Node
|
|||
}
|
||||
// set up the packet for sending...
|
||||
unsigned char* endOfQueryPacket = queryPacket;
|
||||
|
||||
|
||||
// insert packet type/version and node UUID
|
||||
endOfQueryPacket += populatePacketHeader(reinterpret_cast<char*>(endOfQueryPacket), packetType);
|
||||
|
||||
|
@ -3960,6 +3987,9 @@ bool Application::acceptURL(const QString& urlString) {
|
|||
return false;
|
||||
}
|
||||
|
||||
void Application::setSessionUUID(const QUuid& sessionUUID) {
|
||||
_physicsEngine.setSessionUUID(sessionUUID);
|
||||
}
|
||||
|
||||
bool Application::askToSetAvatarUrl(const QString& url) {
|
||||
QUrl realUrl(url);
|
||||
|
|
|
@ -33,8 +33,10 @@
|
|||
#include <OctreeQuery.h>
|
||||
#include <OffscreenUi.h>
|
||||
#include <PacketHeaders.h>
|
||||
#include <PhysicalEntitySimulation.h>
|
||||
#include <PhysicsEngine.h>
|
||||
#include <ScriptEngine.h>
|
||||
#include <ShapeManager.h>
|
||||
#include <StDev.h>
|
||||
#include <TextureCache.h>
|
||||
#include <ViewFrustum.h>
|
||||
|
@ -370,6 +372,7 @@ signals:
|
|||
void beforeAboutToQuit();
|
||||
|
||||
public slots:
|
||||
void setSessionUUID(const QUuid& sessionUUID);
|
||||
void domainChanged(const QString& domainHostname);
|
||||
void updateWindowTitle();
|
||||
void nodeAdded(SharedNodePointer node);
|
||||
|
@ -524,6 +527,8 @@ private:
|
|||
bool _justStarted;
|
||||
Stars _stars;
|
||||
|
||||
ShapeManager _shapeManager;
|
||||
PhysicalEntitySimulation _entitySimulation;
|
||||
PhysicsEngine _physicsEngine;
|
||||
|
||||
EntityTreeRenderer _entities;
|
||||
|
|
|
@ -396,7 +396,7 @@ Menu::Menu() {
|
|||
#if defined(HAVE_FACESHIFT) || defined(HAVE_DDE)
|
||||
faceTrackingMenu->addSeparator();
|
||||
addCheckableActionToQMenuAndActionHash(faceTrackingMenu, MenuOption::MuteFaceTracking,
|
||||
0, false,
|
||||
Qt::CTRL | Qt::SHIFT | Qt::Key_F, false,
|
||||
qApp, SLOT(toggleFaceTrackerMute()));
|
||||
#endif
|
||||
|
||||
|
|
|
@ -66,6 +66,14 @@ DeviceTracker::ID DeviceTracker::registerDevice(const Name& name, DeviceTracker*
|
|||
return deviceID;
|
||||
}
|
||||
|
||||
void DeviceTracker::destroyDevice(const Name& name) {
|
||||
DeviceTracker::ID deviceID = getDeviceID(name);
|
||||
if (deviceID != INVALID_DEVICE) {
|
||||
delete Singleton::get()->_devicesVector[getDeviceID(name)];
|
||||
Singleton::get()->_devicesVector[getDeviceID(name)] = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
void DeviceTracker::updateAll() {
|
||||
//TODO C++11 for (auto deviceIt = Singleton::get()->_devicesVector.begin(); deviceIt != Singleton::get()->_devicesVector.end(); deviceIt++) {
|
||||
for (Vector::iterator deviceIt = Singleton::get()->_devicesVector.begin(); deviceIt != Singleton::get()->_devicesVector.end(); deviceIt++) {
|
||||
|
|
|
@ -79,6 +79,8 @@ public:
|
|||
/// INVALID_DEVICE_NAME if the name is already taken
|
||||
static ID registerDevice(const Name& name, DeviceTracker* tracker);
|
||||
|
||||
static void destroyDevice(const Name& name);
|
||||
|
||||
// DeviceTracker interface
|
||||
|
||||
virtual void update();
|
||||
|
|
|
@ -50,6 +50,11 @@ void Leapmotion::init() {
|
|||
}
|
||||
}
|
||||
|
||||
// static
|
||||
void Leapmotion::destroy() {
|
||||
DeviceTracker::destroyDevice(NAME);
|
||||
}
|
||||
|
||||
// static
|
||||
Leapmotion* Leapmotion::getInstance() {
|
||||
DeviceTracker* device = DeviceTracker::getDevice(NAME);
|
||||
|
|
|
@ -26,6 +26,7 @@ public:
|
|||
static const Name NAME;
|
||||
|
||||
static void init();
|
||||
static void destroy();
|
||||
|
||||
/// Leapmotion MotionTracker factory
|
||||
static Leapmotion* getInstance();
|
||||
|
|
|
@ -54,6 +54,11 @@ void RealSense::init() {
|
|||
}
|
||||
}
|
||||
|
||||
// static
|
||||
void RealSense::destroy() {
|
||||
DeviceTracker::destroyDevice(NAME);
|
||||
}
|
||||
|
||||
// static
|
||||
RealSense* RealSense::getInstance() {
|
||||
DeviceTracker* device = DeviceTracker::getDevice(NAME);
|
||||
|
|
|
@ -32,6 +32,7 @@ public:
|
|||
static const Name NAME;
|
||||
|
||||
static void init();
|
||||
static void destroy();
|
||||
|
||||
/// RealSense MotionTracker factory
|
||||
static RealSense* getInstance();
|
||||
|
|
|
@ -7,4 +7,9 @@ add_dependency_external_projects(glm)
|
|||
find_package(GLM REQUIRED)
|
||||
target_include_directories(${TARGET_NAME} PUBLIC ${GLM_INCLUDE_DIRS})
|
||||
|
||||
link_hifi_libraries(shared gpu script-engine render-utils)
|
||||
add_dependency_external_projects(bullet)
|
||||
find_package(Bullet REQUIRED)
|
||||
target_include_directories(${TARGET_NAME} SYSTEM PRIVATE ${BULLET_INCLUDE_DIRS})
|
||||
target_link_libraries(${TARGET_NAME} ${BULLET_LIBRARIES})
|
||||
|
||||
link_hifi_libraries(shared gpu script-engine render-utils)
|
||||
|
|
|
@ -25,6 +25,7 @@
|
|||
#include <PerfStat.h>
|
||||
#include <SceneScriptingInterface.h>
|
||||
#include <ScriptEngine.h>
|
||||
#include <TextureCache.h>
|
||||
|
||||
#include "EntityTreeRenderer.h"
|
||||
|
||||
|
@ -455,7 +456,9 @@ void EntityTreeRenderer::render(RenderArgs::RenderMode renderMode,
|
|||
if (_bestZone->getSkyboxProperties().getURL().isEmpty()) {
|
||||
stage->getSkybox()->clearCubemap();
|
||||
} else {
|
||||
stage->getSkybox()->clearCubemap(); // NOTE: this should be changed to do something to set the cubemap
|
||||
// Update the Texture of the Skybox with the one pointed by this zone
|
||||
auto cubeMap = DependencyManager::get<TextureCache>()->getTexture(_bestZone->getSkyboxProperties().getURL(), CUBE_TEXTURE);
|
||||
stage->getSkybox()->setCubemap(cubeMap->getGPUTexture());
|
||||
}
|
||||
stage->setBackgroundMode(model::SunSkyStage::SKY_BOX);
|
||||
}
|
||||
|
|
|
@ -14,16 +14,17 @@
|
|||
#include <glm/gtx/quaternion.hpp>
|
||||
#include <gpu/GPUConfig.h>
|
||||
#include <DeferredLightingEffect.h>
|
||||
#include <PhysicsEngine.h>
|
||||
|
||||
#include "RenderableDebugableEntityItem.h"
|
||||
|
||||
|
||||
void RenderableDebugableEntityItem::renderBoundingBox(EntityItem* entity, RenderArgs* args, bool puffedOut) {
|
||||
void RenderableDebugableEntityItem::renderBoundingBox(EntityItem* entity, RenderArgs* args,
|
||||
float puffedOut, glm::vec4& color) {
|
||||
glm::vec3 position = entity->getPosition();
|
||||
glm::vec3 center = entity->getCenter();
|
||||
glm::vec3 dimensions = entity->getDimensions();
|
||||
glm::quat rotation = entity->getRotation();
|
||||
glm::vec4 greenColor(0.0f, 1.0f, 0.0f, 1.0f);
|
||||
|
||||
glPushMatrix();
|
||||
glTranslatef(position.x, position.y, position.z);
|
||||
|
@ -33,15 +34,36 @@ void RenderableDebugableEntityItem::renderBoundingBox(EntityItem* entity, Render
|
|||
glm::vec3 positionToCenter = center - position;
|
||||
glTranslatef(positionToCenter.x, positionToCenter.y, positionToCenter.z);
|
||||
glScalef(dimensions.x, dimensions.y, dimensions.z);
|
||||
if (puffedOut) {
|
||||
DependencyManager::get<DeferredLightingEffect>()->renderWireCube(1.2f, greenColor);
|
||||
} else {
|
||||
DependencyManager::get<DeferredLightingEffect>()->renderWireCube(1.0f, greenColor);
|
||||
}
|
||||
DependencyManager::get<DeferredLightingEffect>()->renderWireCube(1.0f + puffedOut, color);
|
||||
glPopMatrix();
|
||||
glPopMatrix();
|
||||
}
|
||||
|
||||
void RenderableDebugableEntityItem::renderHoverDot(EntityItem* entity, RenderArgs* args) {
|
||||
glm::vec3 position = entity->getPosition();
|
||||
glm::vec3 center = entity->getCenter();
|
||||
glm::vec3 dimensions = entity->getDimensions();
|
||||
glm::quat rotation = entity->getRotation();
|
||||
glm::vec4 blueColor(0.0f, 0.0f, 1.0f, 1.0f);
|
||||
float radius = 0.05f;
|
||||
|
||||
glPushMatrix();
|
||||
glTranslatef(position.x, position.y + dimensions.y + radius, position.z);
|
||||
glm::vec3 axis = glm::axis(rotation);
|
||||
glRotatef(glm::degrees(glm::angle(rotation)), axis.x, axis.y, axis.z);
|
||||
|
||||
glPushMatrix();
|
||||
glm::vec3 positionToCenter = center - position;
|
||||
glTranslatef(positionToCenter.x, positionToCenter.y, positionToCenter.z);
|
||||
|
||||
glScalef(radius, radius, radius);
|
||||
|
||||
const int SLICES = 8;
|
||||
const int STACKS = 8;
|
||||
DependencyManager::get<DeferredLightingEffect>()->renderSolidSphere(0.5f, SLICES, STACKS, blueColor);
|
||||
glPopMatrix();
|
||||
glPopMatrix();
|
||||
}
|
||||
|
||||
void RenderableDebugableEntityItem::render(EntityItem* entity, RenderArgs* args) {
|
||||
bool debugSimulationOwnership = args->_debugFlags & RenderArgs::RENDER_DEBUG_SIMULATION_OWNERSHIP;
|
||||
|
@ -49,7 +71,17 @@ void RenderableDebugableEntityItem::render(EntityItem* entity, RenderArgs* args)
|
|||
if (debugSimulationOwnership) {
|
||||
quint64 now = usecTimestampNow();
|
||||
if (now - entity->getLastEditedFromRemote() < 0.1f * USECS_PER_SECOND) {
|
||||
renderBoundingBox(entity, args, true);
|
||||
glm::vec4 redColor(1.0f, 0.0f, 0.0f, 1.0f);
|
||||
renderBoundingBox(entity, args, 0.2f, redColor);
|
||||
}
|
||||
|
||||
if (now - entity->getLastBroadcast() < 0.2f * USECS_PER_SECOND) {
|
||||
glm::vec4 yellowColor(1.0f, 1.0f, 0.2f, 1.0f);
|
||||
renderBoundingBox(entity, args, 0.3f, yellowColor);
|
||||
}
|
||||
|
||||
if (PhysicsEngine::physicsInfoIsActive(entity->getPhysicsInfo())) {
|
||||
renderHoverDot(entity, args);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -16,7 +16,8 @@
|
|||
|
||||
class RenderableDebugableEntityItem {
|
||||
public:
|
||||
static void renderBoundingBox(EntityItem* entity, RenderArgs* args, bool puffedOut);
|
||||
static void renderBoundingBox(EntityItem* entity, RenderArgs* args, float puffedOut, glm::vec4& color);
|
||||
static void renderHoverDot(EntityItem* entity, RenderArgs* args);
|
||||
static void render(EntityItem* entity, RenderArgs* args);
|
||||
};
|
||||
|
||||
|
|
|
@ -192,7 +192,8 @@ void RenderableModelEntityItem::render(RenderArgs* args) {
|
|||
}
|
||||
|
||||
if (!didDraw) {
|
||||
RenderableDebugableEntityItem::renderBoundingBox(this, args, false);
|
||||
glm::vec4 greenColor(0.0f, 1.0f, 0.0f, 1.0f);
|
||||
RenderableDebugableEntityItem::renderBoundingBox(this, args, 0.0f, greenColor);
|
||||
}
|
||||
|
||||
RenderableDebugableEntityItem::render(this, args);
|
||||
|
@ -277,6 +278,11 @@ bool RenderableModelEntityItem::isReadyToComputeShape() {
|
|||
return false; // hmm...
|
||||
}
|
||||
|
||||
if (_needsInitialSimulation) {
|
||||
// the _model's offset will be wrong until _needsInitialSimulation is false
|
||||
return false;
|
||||
}
|
||||
|
||||
assert(!_model->getCollisionURL().isEmpty());
|
||||
|
||||
if (_model->getURL().isEmpty()) {
|
||||
|
|
|
@ -16,12 +16,19 @@
|
|||
#include "EntityItemPropertiesMacros.h"
|
||||
|
||||
AtmospherePropertyGroup::AtmospherePropertyGroup() {
|
||||
_center = glm::vec3(0.0f);
|
||||
_innerRadius = 0.0f;
|
||||
_outerRadius = 0.0f;
|
||||
_mieScattering = 0.0f;
|
||||
_rayleighScattering = 0.0f;
|
||||
_scatteringWavelengths = glm::vec3(0.0f);
|
||||
const glm::vec3 DEFAULT_CENTER = glm::vec3(0.0f, -1000.0f, 0.0f);
|
||||
const float DEFAULT_INNER_RADIUS = 1000.0f;
|
||||
const float DEFAULT_OUTER_RADIUS = 1025.0f;
|
||||
const float DEFAULT_RAYLEIGH_SCATTERING = 0.0025f;
|
||||
const float DEFAULT_MIE_SCATTERING = 0.0010f;
|
||||
const glm::vec3 DEFAULT_SCATTERING_WAVELENGTHS = glm::vec3(0.650f, 0.570f, 0.475f);
|
||||
|
||||
_center = DEFAULT_CENTER;
|
||||
_innerRadius = DEFAULT_INNER_RADIUS;
|
||||
_outerRadius = DEFAULT_OUTER_RADIUS;
|
||||
_mieScattering = DEFAULT_MIE_SCATTERING;
|
||||
_rayleighScattering = DEFAULT_RAYLEIGH_SCATTERING;
|
||||
_scatteringWavelengths = DEFAULT_SCATTERING_WAVELENGTHS;
|
||||
_hasStars = true;
|
||||
}
|
||||
|
||||
|
@ -89,6 +96,14 @@ bool AtmospherePropertyGroup::decodeFromEditPacket(EntityPropertyFlags& property
|
|||
READ_ENTITY_PROPERTY(PROP_ATMOSPHERE_RAYLEIGH_SCATTERING, float, _rayleighScattering);
|
||||
READ_ENTITY_PROPERTY(PROP_ATMOSPHERE_SCATTERING_WAVELENGTHS, glm::vec3, _scatteringWavelengths);
|
||||
READ_ENTITY_PROPERTY(PROP_ATMOSPHERE_HAS_STARS, bool, _hasStars);
|
||||
|
||||
DECODE_GROUP_PROPERTY_HAS_CHANGED(PROP_ATMOSPHERE_CENTER, Center);
|
||||
DECODE_GROUP_PROPERTY_HAS_CHANGED(PROP_ATMOSPHERE_INNER_RADIUS, InnerRadius);
|
||||
DECODE_GROUP_PROPERTY_HAS_CHANGED(PROP_ATMOSPHERE_OUTER_RADIUS, OuterRadius);
|
||||
DECODE_GROUP_PROPERTY_HAS_CHANGED(PROP_ATMOSPHERE_MIE_SCATTERING, MieScattering);
|
||||
DECODE_GROUP_PROPERTY_HAS_CHANGED(PROP_ATMOSPHERE_RAYLEIGH_SCATTERING, RayleighScattering);
|
||||
DECODE_GROUP_PROPERTY_HAS_CHANGED(PROP_ATMOSPHERE_SCATTERING_WAVELENGTHS, ScatteringWavelengths);
|
||||
DECODE_GROUP_PROPERTY_HAS_CHANGED(PROP_ATMOSPHERE_HAS_STARS, HasStars);
|
||||
|
||||
processedBytes += bytesRead;
|
||||
|
||||
|
|
|
@ -64,9 +64,10 @@ EntityItem::EntityItem(const EntityItemID& entityItemID) :
|
|||
_simulatorIDChangedTime(0),
|
||||
_marketplaceID(ENTITY_ITEM_DEFAULT_MARKETPLACE_ID),
|
||||
_name(ENTITY_ITEM_DEFAULT_NAME),
|
||||
_physicsInfo(NULL),
|
||||
_dirtyFlags(0),
|
||||
_element(NULL)
|
||||
_element(nullptr),
|
||||
_physicsInfo(nullptr),
|
||||
_simulated(false)
|
||||
{
|
||||
quint64 now = usecTimestampNow();
|
||||
_lastSimulated = now;
|
||||
|
@ -79,9 +80,11 @@ EntityItem::EntityItem(const EntityItemID& entityItemID, const EntityItemPropert
|
|||
}
|
||||
|
||||
EntityItem::~EntityItem() {
|
||||
// be sure to clean up _physicsInfo before calling this dtor
|
||||
assert(_physicsInfo == NULL);
|
||||
assert(_element == NULL);
|
||||
// these pointers MUST be NULL at delete, else we probably have a dangling backpointer
|
||||
// to this EntityItem in the corresponding data structure.
|
||||
assert(!_simulated);
|
||||
assert(!_element);
|
||||
assert(!_physicsInfo);
|
||||
}
|
||||
|
||||
EntityPropertyFlags EntityItem::getEntityProperties(EncodeBitstreamParams& params) const {
|
||||
|
@ -310,10 +313,10 @@ int EntityItem::readEntityDataFromBuffer(const unsigned char* data, int bytesLef
|
|||
// if this bitstream indicates that this node is the simulation owner, ignore any physics-related updates.
|
||||
glm::vec3 savePosition = _position;
|
||||
glm::quat saveRotation = _rotation;
|
||||
glm::vec3 saveVelocity = _velocity;
|
||||
glm::vec3 saveAngularVelocity = _angularVelocity;
|
||||
glm::vec3 saveGravity = _gravity;
|
||||
glm::vec3 saveAcceleration = _acceleration;
|
||||
// glm::vec3 saveVelocity = _velocity;
|
||||
// glm::vec3 saveAngularVelocity = _angularVelocity;
|
||||
// glm::vec3 saveGravity = _gravity;
|
||||
// glm::vec3 saveAcceleration = _acceleration;
|
||||
|
||||
|
||||
// Header bytes
|
||||
|
@ -398,7 +401,7 @@ int EntityItem::readEntityDataFromBuffer(const unsigned char* data, int bytesLef
|
|||
if (lastEditedFromBufferAdjusted > now) {
|
||||
lastEditedFromBufferAdjusted = now;
|
||||
}
|
||||
|
||||
|
||||
bool fromSameServerEdit = (lastEditedFromBuffer == _lastEditedFromRemoteInRemoteTime);
|
||||
|
||||
#ifdef WANT_DEBUG
|
||||
|
@ -542,7 +545,7 @@ int EntityItem::readEntityDataFromBuffer(const unsigned char* data, int bytesLef
|
|||
READ_ENTITY_PROPERTY_SETTER(PROP_GRAVITY, glm::vec3, updateGravityInDomainUnits);
|
||||
}
|
||||
if (args.bitstreamVersion >= VERSION_ENTITIES_HAVE_ACCELERATION) {
|
||||
READ_ENTITY_PROPERTY_SETTER(PROP_ACCELERATION, glm::vec3, updateAcceleration);
|
||||
READ_ENTITY_PROPERTY_SETTER(PROP_ACCELERATION, glm::vec3, setAcceleration);
|
||||
}
|
||||
|
||||
READ_ENTITY_PROPERTY(PROP_DAMPING, float, _damping);
|
||||
|
@ -584,7 +587,7 @@ int EntityItem::readEntityDataFromBuffer(const unsigned char* data, int bytesLef
|
|||
READ_ENTITY_PROPERTY_STRING(PROP_MARKETPLACE_ID, setMarketplaceID);
|
||||
}
|
||||
|
||||
if (overwriteLocalData && (getDirtyFlags() & (EntityItem::DIRTY_POSITION | EntityItem::DIRTY_VELOCITY))) {
|
||||
if (overwriteLocalData && (getDirtyFlags() & (EntityItem::DIRTY_TRANSFORM | EntityItem::DIRTY_VELOCITIES))) {
|
||||
// NOTE: This code is attempting to "repair" the old data we just got from the server to make it more
|
||||
// closely match where the entities should be if they'd stepped forward in time to "now". The server
|
||||
// is sending us data with a known "last simulated" time. That time is likely in the past, and therefore
|
||||
|
@ -615,10 +618,10 @@ int EntityItem::readEntityDataFromBuffer(const unsigned char* data, int bytesLef
|
|||
// this node, so our version has to be newer than what the packet contained.
|
||||
_position = savePosition;
|
||||
_rotation = saveRotation;
|
||||
_velocity = saveVelocity;
|
||||
_angularVelocity = saveAngularVelocity;
|
||||
_gravity = saveGravity;
|
||||
_acceleration = saveAcceleration;
|
||||
// _velocity = saveVelocity;
|
||||
// _angularVelocity = saveAngularVelocity;
|
||||
// _gravity = saveGravity;
|
||||
// _acceleration = saveAcceleration;
|
||||
}
|
||||
|
||||
return bytesRead;
|
||||
|
@ -659,12 +662,17 @@ void EntityItem::setDensity(float density) {
|
|||
_density = glm::max(glm::min(density, ENTITY_ITEM_MAX_DENSITY), ENTITY_ITEM_MIN_DENSITY);
|
||||
}
|
||||
|
||||
const float ACTIVATION_RELATIVE_DENSITY_DELTA = 0.01f; // 1 percent
|
||||
|
||||
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;
|
||||
float clampedDensity = glm::max(glm::min(density, ENTITY_ITEM_MAX_DENSITY), ENTITY_ITEM_MIN_DENSITY);
|
||||
if (_density != clampedDensity) {
|
||||
_density = clampedDensity;
|
||||
|
||||
if (fabsf(_density - clampedDensity) / _density > ACTIVATION_RELATIVE_DENSITY_DELTA) {
|
||||
// the density has changed enough that we should update the physics simulation
|
||||
_dirtyFlags |= EntityItem::DIRTY_MASS;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -918,7 +926,7 @@ bool EntityItem::setProperties(const EntityItemProperties& properties) {
|
|||
SET_ENTITY_PROPERTY_FROM_PROPERTIES(density, updateDensity);
|
||||
SET_ENTITY_PROPERTY_FROM_PROPERTIES(velocity, updateVelocity);
|
||||
SET_ENTITY_PROPERTY_FROM_PROPERTIES(gravity, updateGravity);
|
||||
SET_ENTITY_PROPERTY_FROM_PROPERTIES(acceleration, updateAcceleration);
|
||||
SET_ENTITY_PROPERTY_FROM_PROPERTIES(acceleration, setAcceleration);
|
||||
SET_ENTITY_PROPERTY_FROM_PROPERTIES(damping, updateDamping);
|
||||
SET_ENTITY_PROPERTY_FROM_PROPERTIES(lifetime, updateLifetime);
|
||||
SET_ENTITY_PROPERTY_FROM_PROPERTIES(script, setScript);
|
||||
|
@ -947,7 +955,7 @@ bool EntityItem::setProperties(const EntityItemProperties& properties) {
|
|||
if (_created != UNKNOWN_CREATED_TIME) {
|
||||
setLastEdited(now);
|
||||
}
|
||||
if (getDirtyFlags() & (EntityItem::DIRTY_POSITION | EntityItem::DIRTY_VELOCITY)) {
|
||||
if (getDirtyFlags() & (EntityItem::DIRTY_TRANSFORM | EntityItem::DIRTY_VELOCITIES)) {
|
||||
// TODO: Andrew & Brad to discuss. Is this correct? Maybe it is. Need to think through all cases.
|
||||
_lastSimulated = now;
|
||||
}
|
||||
|
@ -1097,25 +1105,19 @@ void EntityItem::computeShapeInfo(ShapeInfo& info) {
|
|||
info.setParams(getShapeType(), 0.5f * getDimensions());
|
||||
}
|
||||
|
||||
const float MIN_POSITION_DELTA = 0.0001f;
|
||||
const float MIN_DIMENSIONS_DELTA = 0.0005f;
|
||||
const float MIN_ALIGNMENT_DOT = 0.999999f;
|
||||
const float MIN_VELOCITY_DELTA = 0.01f;
|
||||
const float MIN_DAMPING_DELTA = 0.001f;
|
||||
const float MIN_GRAVITY_DELTA = 0.001f;
|
||||
const float MIN_ACCELERATION_DELTA = 0.001f;
|
||||
const float MIN_SPIN_DELTA = 0.0003f;
|
||||
|
||||
void EntityItem::updatePositionInDomainUnits(const glm::vec3& value) {
|
||||
glm::vec3 position = value * (float)TREE_SCALE;
|
||||
updatePosition(position);
|
||||
}
|
||||
|
||||
void EntityItem::updatePosition(const glm::vec3& value) {
|
||||
if (value != _position) {
|
||||
auto distance = glm::distance(_position, value);
|
||||
_dirtyFlags |= (distance > MIN_POSITION_DELTA) ? EntityItem::DIRTY_POSITION : EntityItem::DIRTY_PHYSICS_NO_WAKE;
|
||||
auto delta = glm::distance(_position, value);
|
||||
if (delta > IGNORE_POSITION_DELTA) {
|
||||
_dirtyFlags |= EntityItem::DIRTY_POSITION;
|
||||
_position = value;
|
||||
if (delta > ACTIVATION_POSITION_DELTA) {
|
||||
_dirtyFlags |= EntityItem::DIRTY_PHYSICS_ACTIVATION;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1125,17 +1127,27 @@ void EntityItem::updateDimensionsInDomainUnits(const glm::vec3& value) {
|
|||
}
|
||||
|
||||
void EntityItem::updateDimensions(const glm::vec3& value) {
|
||||
if (glm::distance(_dimensions, value) > MIN_DIMENSIONS_DELTA) {
|
||||
auto delta = glm::distance(_dimensions, value);
|
||||
if (delta > IGNORE_DIMENSIONS_DELTA) {
|
||||
_dimensions = value;
|
||||
_dirtyFlags |= (EntityItem::DIRTY_SHAPE | EntityItem::DIRTY_MASS);
|
||||
if (delta > ACTIVATION_DIMENSIONS_DELTA) {
|
||||
// rebuilding the shape will always activate
|
||||
_dirtyFlags |= (EntityItem::DIRTY_SHAPE | EntityItem::DIRTY_MASS);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void EntityItem::updateRotation(const glm::quat& rotation) {
|
||||
if (rotation != _rotation) {
|
||||
auto alignmentDot = glm::abs(glm::dot(_rotation, rotation));
|
||||
_dirtyFlags |= (alignmentDot < MIN_ALIGNMENT_DOT) ? EntityItem::DIRTY_POSITION : EntityItem::DIRTY_PHYSICS_NO_WAKE;
|
||||
if (_rotation != rotation) {
|
||||
_rotation = rotation;
|
||||
|
||||
auto alignmentDot = glm::abs(glm::dot(_rotation, rotation));
|
||||
if (alignmentDot < IGNORE_ALIGNMENT_DOT) {
|
||||
_dirtyFlags |= EntityItem::DIRTY_ROTATION;
|
||||
}
|
||||
if (alignmentDot < ACTIVATION_ALIGNMENT_DOT) {
|
||||
_dirtyFlags |= EntityItem::DIRTY_PHYSICS_ACTIVATION;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1156,9 +1168,11 @@ void EntityItem::updateMass(float mass) {
|
|||
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;
|
||||
float oldDensity = _density;
|
||||
_density = newDensity;
|
||||
|
||||
if (fabsf(_density - oldDensity) / _density > ACTIVATION_RELATIVE_DENSITY_DELTA) {
|
||||
// the density has changed enough that we should update the physics simulation
|
||||
_dirtyFlags |= EntityItem::DIRTY_MASS;
|
||||
}
|
||||
}
|
||||
|
@ -1169,19 +1183,26 @@ void EntityItem::updateVelocityInDomainUnits(const glm::vec3& value) {
|
|||
}
|
||||
|
||||
void EntityItem::updateVelocity(const glm::vec3& value) {
|
||||
if (glm::distance(_velocity, value) > MIN_VELOCITY_DELTA) {
|
||||
if (glm::length(value) < MIN_VELOCITY_DELTA) {
|
||||
auto delta = glm::distance(_velocity, value);
|
||||
if (delta > IGNORE_LINEAR_VELOCITY_DELTA) {
|
||||
const float MIN_LINEAR_SPEED = 0.001f;
|
||||
if (glm::length(value) < MIN_LINEAR_SPEED) {
|
||||
_velocity = ENTITY_ITEM_ZERO_VEC3;
|
||||
} else {
|
||||
_velocity = value;
|
||||
}
|
||||
_dirtyFlags |= EntityItem::DIRTY_VELOCITY;
|
||||
_dirtyFlags |= EntityItem::DIRTY_LINEAR_VELOCITY;
|
||||
|
||||
if (delta > ACTIVATION_LINEAR_VELOCITY_DELTA) {
|
||||
_dirtyFlags |= EntityItem::DIRTY_PHYSICS_ACTIVATION;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void EntityItem::updateDamping(float value) {
|
||||
if (fabsf(_damping - value) > MIN_DAMPING_DELTA) {
|
||||
_damping = glm::clamp(value, 0.0f, 1.0f);
|
||||
auto clampedDamping = glm::clamp(value, 0.0f, 1.0f);
|
||||
if (fabsf(_damping - clampedDamping) > IGNORE_DAMPING_DELTA) {
|
||||
_damping = clampedDamping;
|
||||
_dirtyFlags |= EntityItem::DIRTY_MATERIAL;
|
||||
}
|
||||
}
|
||||
|
@ -1192,34 +1213,36 @@ void EntityItem::updateGravityInDomainUnits(const glm::vec3& value) {
|
|||
}
|
||||
|
||||
void EntityItem::updateGravity(const glm::vec3& value) {
|
||||
if (glm::distance(_gravity, value) > MIN_GRAVITY_DELTA) {
|
||||
auto delta = glm::distance(_gravity, value);
|
||||
if (delta > IGNORE_GRAVITY_DELTA) {
|
||||
_gravity = value;
|
||||
_dirtyFlags |= EntityItem::DIRTY_VELOCITY;
|
||||
}
|
||||
}
|
||||
|
||||
void EntityItem::updateAcceleration(const glm::vec3& value) {
|
||||
if (glm::distance(_acceleration, value) > MIN_ACCELERATION_DELTA) {
|
||||
_acceleration = value;
|
||||
_dirtyFlags |= EntityItem::DIRTY_VELOCITY;
|
||||
_dirtyFlags |= EntityItem::DIRTY_LINEAR_VELOCITY;
|
||||
if (delta > ACTIVATION_GRAVITY_DELTA) {
|
||||
_dirtyFlags |= EntityItem::DIRTY_PHYSICS_ACTIVATION;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void EntityItem::updateAngularVelocity(const glm::vec3& value) {
|
||||
auto distance = glm::distance(_angularVelocity, value);
|
||||
if (distance > MIN_SPIN_DELTA) {
|
||||
_dirtyFlags |= EntityItem::DIRTY_VELOCITY;
|
||||
if (glm::length(value) < MIN_SPIN_DELTA) {
|
||||
auto delta = glm::distance(_angularVelocity, value);
|
||||
if (delta > IGNORE_ANGULAR_VELOCITY_DELTA) {
|
||||
_dirtyFlags |= EntityItem::DIRTY_ANGULAR_VELOCITY;
|
||||
const float MIN_ANGULAR_SPEED = 0.0002f;
|
||||
if (glm::length(value) < MIN_ANGULAR_SPEED) {
|
||||
_angularVelocity = ENTITY_ITEM_ZERO_VEC3;
|
||||
} else {
|
||||
_angularVelocity = value;
|
||||
}
|
||||
if (delta > ACTIVATION_ANGULAR_VELOCITY_DELTA) {
|
||||
_dirtyFlags |= EntityItem::DIRTY_PHYSICS_ACTIVATION;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void EntityItem::updateAngularDamping(float value) {
|
||||
if (fabsf(_angularDamping - value) > MIN_DAMPING_DELTA) {
|
||||
_angularDamping = glm::clamp(value, 0.0f, 1.0f);
|
||||
auto clampedDamping = glm::clamp(value, 0.0f, 1.0f);
|
||||
if (fabsf(_angularDamping - clampedDamping) > IGNORE_DAMPING_DELTA) {
|
||||
_angularDamping = clampedDamping;
|
||||
_dirtyFlags |= EntityItem::DIRTY_MATERIAL;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -28,10 +28,28 @@
|
|||
#include "EntityItemPropertiesDefaults.h"
|
||||
#include "EntityTypes.h"
|
||||
|
||||
class EntityTree;
|
||||
class EntitySimulation;
|
||||
class EntityTreeElement;
|
||||
class EntityTreeElementExtraEncodeData;
|
||||
|
||||
// these thesholds determine what updates will be ignored (client and server)
|
||||
const float IGNORE_POSITION_DELTA = 0.0001f;
|
||||
const float IGNORE_DIMENSIONS_DELTA = 0.0005f;
|
||||
const float IGNORE_ALIGNMENT_DOT = 0.99997f;
|
||||
const float IGNORE_LINEAR_VELOCITY_DELTA = 0.001f;
|
||||
const float IGNORE_DAMPING_DELTA = 0.001f;
|
||||
const float IGNORE_GRAVITY_DELTA = 0.001f;
|
||||
const float IGNORE_ANGULAR_VELOCITY_DELTA = 0.0002f;
|
||||
|
||||
// these thresholds determine what updates will activate the physical object
|
||||
const float ACTIVATION_POSITION_DELTA = 0.005f;
|
||||
const float ACTIVATION_DIMENSIONS_DELTA = 0.005f;
|
||||
const float ACTIVATION_ALIGNMENT_DOT = 0.99990f;
|
||||
const float ACTIVATION_LINEAR_VELOCITY_DELTA = 0.01f;
|
||||
const float ACTIVATION_GRAVITY_DELTA = 0.1f;
|
||||
const float ACTIVATION_ANGULAR_VELOCITY_DELTA = 0.03f;
|
||||
|
||||
|
||||
#define DONT_ALLOW_INSTANTIATION virtual void pureVirtualFunctionPlaceHolder() = 0;
|
||||
#define ALLOW_INSTANTIATION virtual void pureVirtualFunctionPlaceHolder() { };
|
||||
|
||||
|
@ -44,19 +62,28 @@ class EntityTreeElementExtraEncodeData;
|
|||
/// to all other entity types. In particular: postion, size, rotation, age, lifetime, velocity, gravity. You can not instantiate
|
||||
/// one directly, instead you must only construct one of it's derived classes with additional features.
|
||||
class EntityItem {
|
||||
// These two classes manage lists of EntityItem pointers and must be able to cleanup pointers when an EntityItem is deleted.
|
||||
// To make the cleanup robust each EntityItem has backpointers to its manager classes (which are only ever set/cleared by
|
||||
// the managers themselves, hence they are fiends) whose NULL status can be used to determine which managers still need to
|
||||
// do cleanup.
|
||||
friend class EntityTreeElement;
|
||||
friend class EntitySimulation;
|
||||
public:
|
||||
enum EntityDirtyFlags {
|
||||
DIRTY_POSITION = 0x0001,
|
||||
DIRTY_VELOCITY = 0x0002,
|
||||
DIRTY_MASS = 0x0004,
|
||||
DIRTY_COLLISION_GROUP = 0x0008,
|
||||
DIRTY_MOTION_TYPE = 0x0010,
|
||||
DIRTY_SHAPE = 0x0020,
|
||||
DIRTY_LIFETIME = 0x0040,
|
||||
DIRTY_UPDATEABLE = 0x0080,
|
||||
DIRTY_MATERIAL = 0x00100,
|
||||
DIRTY_PHYSICS_NO_WAKE = 0x0200 // we want to update values in physics engine without "waking" the object up
|
||||
DIRTY_ROTATION = 0x0002,
|
||||
DIRTY_LINEAR_VELOCITY = 0x0004,
|
||||
DIRTY_ANGULAR_VELOCITY = 0x0008,
|
||||
DIRTY_MASS = 0x0010,
|
||||
DIRTY_COLLISION_GROUP = 0x0020,
|
||||
DIRTY_MOTION_TYPE = 0x0040,
|
||||
DIRTY_SHAPE = 0x0080,
|
||||
DIRTY_LIFETIME = 0x0100,
|
||||
DIRTY_UPDATEABLE = 0x0200,
|
||||
DIRTY_MATERIAL = 0x00400,
|
||||
DIRTY_PHYSICS_ACTIVATION = 0x0800, // we want to activate the object
|
||||
DIRTY_TRANSFORM = DIRTY_POSITION | DIRTY_ROTATION,
|
||||
DIRTY_VELOCITIES = DIRTY_LINEAR_VELOCITY | DIRTY_ANGULAR_VELOCITY
|
||||
};
|
||||
|
||||
DONT_ALLOW_INSTANTIATION // This class can not be instantiated directly
|
||||
|
@ -66,7 +93,7 @@ public:
|
|||
virtual ~EntityItem();
|
||||
|
||||
// ID and EntityItemID related methods
|
||||
QUuid getID() const { return _id; }
|
||||
const QUuid& getID() const { return _id; }
|
||||
void setID(const QUuid& id) { _id = id; }
|
||||
uint32_t getCreatorTokenID() const { return _creatorTokenID; }
|
||||
void setCreatorTokenID(uint32_t creatorTokenID) { _creatorTokenID = creatorTokenID; }
|
||||
|
@ -95,6 +122,10 @@ public:
|
|||
float getEditedAgo() const /// Elapsed seconds since this entity was last edited
|
||||
{ return (float)(usecTimestampNow() - getLastEdited()) / (float)USECS_PER_SECOND; }
|
||||
|
||||
/// Last time we sent out an edit packet for this entity
|
||||
quint64 getLastBroadcast() const { return _lastBroadcast; }
|
||||
void setLastBroadcast(quint64 lastBroadcast) { _lastBroadcast = lastBroadcast; }
|
||||
|
||||
void markAsChangedOnServer() { _changedOnServer = usecTimestampNow(); }
|
||||
quint64 getLastChangedOnServer() const { return _changedOnServer; }
|
||||
|
||||
|
@ -243,6 +274,8 @@ public:
|
|||
bool getCollisionsWillMove() const { return _collisionsWillMove; }
|
||||
void setCollisionsWillMove(bool value) { _collisionsWillMove = value; }
|
||||
|
||||
virtual bool shouldBePhysical() const { return !_ignoreForCollisions; }
|
||||
|
||||
bool getLocked() const { return _locked; }
|
||||
void setLocked(bool value) { _locked = value; }
|
||||
|
||||
|
@ -281,7 +314,6 @@ public:
|
|||
void updateDamping(float value);
|
||||
void updateGravityInDomainUnits(const glm::vec3& value);
|
||||
void updateGravity(const glm::vec3& value);
|
||||
void updateAcceleration(const glm::vec3& value);
|
||||
void updateAngularVelocity(const glm::vec3& value);
|
||||
void updateAngularVelocityInDegrees(const glm::vec3& value) { updateAngularVelocity(glm::radians(value)); }
|
||||
void updateAngularDamping(float value);
|
||||
|
@ -296,8 +328,8 @@ public:
|
|||
bool isMoving() const;
|
||||
|
||||
void* getPhysicsInfo() const { return _physicsInfo; }
|
||||
void setPhysicsInfo(void* data) { _physicsInfo = data; }
|
||||
|
||||
void setPhysicsInfo(void* data) { _physicsInfo = data; }
|
||||
EntityTreeElement* getElement() const { return _element; }
|
||||
|
||||
static void setSendPhysicsUpdates(bool value) { _sendPhysicsUpdates = value; }
|
||||
|
@ -320,6 +352,7 @@ protected:
|
|||
quint64 _lastSimulated; // last time this entity called simulate(), this includes velocity, angular velocity, and physics changes
|
||||
quint64 _lastUpdated; // last time this entity called update(), this includes animations and non-physics changes
|
||||
quint64 _lastEdited; // last official local or remote edit time
|
||||
quint64 _lastBroadcast; // the last time we sent an edit packet about this entity
|
||||
|
||||
quint64 _lastEditedFromRemote; // last time we received and edit from the server
|
||||
quint64 _lastEditedFromRemoteInRemoteTime; // last time we received an edit from the server (in server-time-frame)
|
||||
|
@ -371,16 +404,13 @@ protected:
|
|||
/// set radius in domain scale units (0.0 - 1.0) this will also reset dimensions to be equal for each axis
|
||||
void setRadius(float value);
|
||||
|
||||
// _physicsInfo is a hook reserved for use by the EntitySimulation, which is guaranteed to set _physicsInfo
|
||||
// to a non-NULL value when the EntityItem has a representation in the physics engine.
|
||||
void* _physicsInfo = NULL; // only set by EntitySimulation
|
||||
|
||||
// DirtyFlags are set whenever a property changes that the EntitySimulation needs to know about.
|
||||
uint32_t _dirtyFlags; // things that have changed from EXTERNAL changes (via script or packet) but NOT from simulation
|
||||
|
||||
EntityTreeElement* _element; // back pointer to containing Element
|
||||
// these backpointers are only ever set/cleared by friends:
|
||||
EntityTreeElement* _element = nullptr; // set by EntityTreeElement
|
||||
void* _physicsInfo = nullptr; // set by EntitySimulation
|
||||
bool _simulated; // set by EntitySimulation
|
||||
};
|
||||
|
||||
|
||||
|
||||
#endif // hifi_EntityItem_h
|
||||
|
|
|
@ -181,6 +181,10 @@ void EntityItemProperties::debugDump() const {
|
|||
qCDebug(entities) << " _dimensions=" << getDimensions();
|
||||
qCDebug(entities) << " _modelURL=" << _modelURL;
|
||||
qCDebug(entities) << " _compoundShapeURL=" << _compoundShapeURL;
|
||||
|
||||
getAtmosphere().debugDump();
|
||||
getSkybox().debugDump();
|
||||
|
||||
qCDebug(entities) << " changed properties...";
|
||||
EntityPropertyFlags props = getChangedProperties();
|
||||
props.debugDumpBits();
|
||||
|
|
|
@ -279,6 +279,9 @@ inline QDebug operator<<(QDebug debug, const EntityItemProperties& properties) {
|
|||
DEBUG_PROPERTY_IF_CHANGED(debug, properties, ParticleRadius, particleRadius, "");
|
||||
DEBUG_PROPERTY_IF_CHANGED(debug, properties, MarketplaceID, marketplaceID, "");
|
||||
DEBUG_PROPERTY_IF_CHANGED(debug, properties, BackgroundMode, backgroundMode, "");
|
||||
|
||||
properties.getAtmosphere().debugDump();
|
||||
properties.getSkybox().debugDump();
|
||||
|
||||
debug << " last edited:" << properties.getLastEdited() << "\n";
|
||||
debug << " edited ago:" << properties.getEditedAgo() << "\n";
|
||||
|
|
|
@ -107,6 +107,11 @@
|
|||
} \
|
||||
}
|
||||
|
||||
#define DECODE_GROUP_PROPERTY_HAS_CHANGED(P,N) \
|
||||
if (propertyFlags.getHasProperty(P)) { \
|
||||
set##N##Changed(true); \
|
||||
}
|
||||
|
||||
#define READ_ENTITY_PROPERTY_COLOR(P,M) \
|
||||
if (propertyFlags.getHasProperty(P)) { \
|
||||
if (overwriteLocalData) { \
|
||||
|
|
|
@ -86,6 +86,7 @@ EntityItemID EntityScriptingInterface::addEntity(const EntityItemProperties& pro
|
|||
if (_entityTree) {
|
||||
_entityTree->lockForWrite();
|
||||
EntityItem* entity = _entityTree->addEntity(id, propertiesWithSimID);
|
||||
entity->setLastBroadcast(usecTimestampNow());
|
||||
if (entity) {
|
||||
// This Node is creating a new object. If it's in motion, set this Node as the simulator.
|
||||
setSimId(propertiesWithSimID, entity);
|
||||
|
@ -178,6 +179,7 @@ EntityItemID EntityScriptingInterface::editEntity(EntityItemID entityID, const E
|
|||
if (propertiesWithSimID.getType() == EntityTypes::Unknown) {
|
||||
EntityItem* entity = _entityTree->findEntityByEntityItemID(entityID);
|
||||
if (entity) {
|
||||
entity->setLastBroadcast(usecTimestampNow());
|
||||
propertiesWithSimID.setType(entity->getType());
|
||||
setSimId(propertiesWithSimID, entity);
|
||||
}
|
||||
|
|
|
@ -19,41 +19,67 @@ void EntitySimulation::setEntityTree(EntityTree* tree) {
|
|||
if (_entityTree && _entityTree != tree) {
|
||||
_mortalEntities.clear();
|
||||
_nextExpiry = quint64(-1);
|
||||
_updateableEntities.clear();
|
||||
_entitiesToBeSorted.clear();
|
||||
_entitiesToUpdate.clear();
|
||||
_entitiesToSort.clear();
|
||||
_simpleKinematicEntities.clear();
|
||||
}
|
||||
_entityTree = tree;
|
||||
}
|
||||
|
||||
void EntitySimulation::updateEntities(QSet<EntityItem*>& entitiesToDelete) {
|
||||
void EntitySimulation::updateEntities() {
|
||||
quint64 now = usecTimestampNow();
|
||||
|
||||
// these methods may accumulate entries in _entitiesToBeDeleted
|
||||
expireMortalEntities(now);
|
||||
callUpdateOnEntitiesThatNeedIt(now);
|
||||
moveSimpleKinematics(now);
|
||||
updateEntitiesInternal(now);
|
||||
sortEntitiesThatMoved();
|
||||
}
|
||||
|
||||
// at this point we harvest _entitiesToBeDeleted
|
||||
entitiesToDelete.unite(_entitiesToDelete);
|
||||
void EntitySimulation::getEntitiesToDelete(VectorOfEntities& entitiesToDelete) {
|
||||
for (auto entityItr : _entitiesToDelete) {
|
||||
EntityItem* entity = &(*entityItr);
|
||||
// this entity is still in its tree, so we insert into the external list
|
||||
entitiesToDelete.push_back(entity);
|
||||
++entityItr;
|
||||
}
|
||||
_entitiesToDelete.clear();
|
||||
}
|
||||
|
||||
// private
|
||||
void EntitySimulation::addEntityInternal(EntityItem* entity) {
|
||||
if (entity->isMoving() && !entity->getPhysicsInfo()) {
|
||||
_simpleKinematicEntities.insert(entity);
|
||||
}
|
||||
}
|
||||
|
||||
void EntitySimulation::changeEntityInternal(EntityItem* entity) {
|
||||
if (entity->isMoving() && !entity->getPhysicsInfo()) {
|
||||
_simpleKinematicEntities.insert(entity);
|
||||
} else {
|
||||
_simpleKinematicEntities.remove(entity);
|
||||
}
|
||||
}
|
||||
|
||||
// protected
|
||||
void EntitySimulation::expireMortalEntities(const quint64& now) {
|
||||
if (now > _nextExpiry) {
|
||||
// only search for expired entities if we expect to find one
|
||||
_nextExpiry = quint64(-1);
|
||||
QSet<EntityItem*>::iterator itemItr = _mortalEntities.begin();
|
||||
SetOfEntities::iterator itemItr = _mortalEntities.begin();
|
||||
while (itemItr != _mortalEntities.end()) {
|
||||
EntityItem* entity = *itemItr;
|
||||
quint64 expiry = entity->getExpiry();
|
||||
if (expiry < now) {
|
||||
_entitiesToDelete.insert(entity);
|
||||
itemItr = _mortalEntities.erase(itemItr);
|
||||
_updateableEntities.remove(entity);
|
||||
_entitiesToBeSorted.remove(entity);
|
||||
_entitiesToUpdate.remove(entity);
|
||||
_entitiesToSort.remove(entity);
|
||||
_simpleKinematicEntities.remove(entity);
|
||||
removeEntityInternal(entity);
|
||||
|
||||
_allEntities.remove(entity);
|
||||
entity->_simulated = false;
|
||||
} else {
|
||||
if (expiry < _nextExpiry) {
|
||||
// remeber the smallest _nextExpiry so we know when to start the next search
|
||||
|
@ -65,16 +91,16 @@ void EntitySimulation::expireMortalEntities(const quint64& now) {
|
|||
}
|
||||
}
|
||||
|
||||
// private
|
||||
// protected
|
||||
void EntitySimulation::callUpdateOnEntitiesThatNeedIt(const quint64& now) {
|
||||
PerformanceTimer perfTimer("updatingEntities");
|
||||
QSet<EntityItem*>::iterator itemItr = _updateableEntities.begin();
|
||||
while (itemItr != _updateableEntities.end()) {
|
||||
SetOfEntities::iterator itemItr = _entitiesToUpdate.begin();
|
||||
while (itemItr != _entitiesToUpdate.end()) {
|
||||
EntityItem* entity = *itemItr;
|
||||
// TODO: catch transition from needing update to not as a "change"
|
||||
// so we don't have to scan for it here.
|
||||
if (!entity->needsToCallUpdate()) {
|
||||
itemItr = _updateableEntities.erase(itemItr);
|
||||
itemItr = _entitiesToUpdate.erase(itemItr);
|
||||
} else {
|
||||
entity->update(now);
|
||||
++itemItr;
|
||||
|
@ -82,15 +108,15 @@ void EntitySimulation::callUpdateOnEntitiesThatNeedIt(const quint64& now) {
|
|||
}
|
||||
}
|
||||
|
||||
// private
|
||||
// protected
|
||||
void EntitySimulation::sortEntitiesThatMoved() {
|
||||
// NOTE: this is only for entities that have been moved by THIS EntitySimulation.
|
||||
// External changes to entity position/shape are expected to be sorted outside of the EntitySimulation.
|
||||
PerformanceTimer perfTimer("sortingEntities");
|
||||
MovingEntitiesOperator moveOperator(_entityTree);
|
||||
AACube domainBounds(glm::vec3(0.0f,0.0f,0.0f), (float)TREE_SCALE);
|
||||
QSet<EntityItem*>::iterator itemItr = _entitiesToBeSorted.begin();
|
||||
while (itemItr != _entitiesToBeSorted.end()) {
|
||||
SetOfEntities::iterator itemItr = _entitiesToSort.begin();
|
||||
while (itemItr != _entitiesToSort.end()) {
|
||||
EntityItem* entity = *itemItr;
|
||||
// check to see if this movement has sent the entity outside of the domain.
|
||||
AACube newCube = entity->getMaximumAACube();
|
||||
|
@ -98,9 +124,14 @@ void EntitySimulation::sortEntitiesThatMoved() {
|
|||
qCDebug(entities) << "Entity " << entity->getEntityItemID() << " moved out of domain bounds.";
|
||||
_entitiesToDelete.insert(entity);
|
||||
_mortalEntities.remove(entity);
|
||||
_updateableEntities.remove(entity);
|
||||
_entitiesToUpdate.remove(entity);
|
||||
_simpleKinematicEntities.remove(entity);
|
||||
removeEntityInternal(entity);
|
||||
itemItr = _entitiesToBeSorted.erase(itemItr);
|
||||
|
||||
_allEntities.remove(entity);
|
||||
entity->_simulated = false;
|
||||
|
||||
itemItr = _entitiesToSort.erase(itemItr);
|
||||
} else {
|
||||
moveOperator.addEntityToMoveList(entity, newCube);
|
||||
++itemItr;
|
||||
|
@ -111,8 +142,7 @@ void EntitySimulation::sortEntitiesThatMoved() {
|
|||
_entityTree->recurseTreeWithOperator(&moveOperator);
|
||||
}
|
||||
|
||||
sortEntitiesThatMovedInternal();
|
||||
_entitiesToBeSorted.clear();
|
||||
_entitiesToSort.clear();
|
||||
}
|
||||
|
||||
void EntitySimulation::addEntity(EntityItem* entity) {
|
||||
|
@ -125,10 +155,13 @@ void EntitySimulation::addEntity(EntityItem* entity) {
|
|||
}
|
||||
}
|
||||
if (entity->needsToCallUpdate()) {
|
||||
_updateableEntities.insert(entity);
|
||||
_entitiesToUpdate.insert(entity);
|
||||
}
|
||||
addEntityInternal(entity);
|
||||
|
||||
_allEntities.insert(entity);
|
||||
entity->_simulated = true;
|
||||
|
||||
// DirtyFlags are used to signal changes to entities that have already been added,
|
||||
// so we can clear them for this entity which has just been added.
|
||||
entity->clearDirtyFlags();
|
||||
|
@ -136,15 +169,25 @@ void EntitySimulation::addEntity(EntityItem* entity) {
|
|||
|
||||
void EntitySimulation::removeEntity(EntityItem* entity) {
|
||||
assert(entity);
|
||||
_updateableEntities.remove(entity);
|
||||
_entitiesToUpdate.remove(entity);
|
||||
_mortalEntities.remove(entity);
|
||||
_entitiesToBeSorted.remove(entity);
|
||||
_entitiesToSort.remove(entity);
|
||||
_simpleKinematicEntities.remove(entity);
|
||||
_entitiesToDelete.remove(entity);
|
||||
removeEntityInternal(entity);
|
||||
|
||||
_allEntities.remove(entity);
|
||||
entity->_simulated = false;
|
||||
}
|
||||
|
||||
void EntitySimulation::entityChanged(EntityItem* entity) {
|
||||
void EntitySimulation::changeEntity(EntityItem* entity) {
|
||||
assert(entity);
|
||||
if (!entity->_simulated) {
|
||||
// This entity was either never added to the simulation or has been removed
|
||||
// (probably for pending delete), so we don't want to keep a pointer to it
|
||||
// on any internal lists.
|
||||
return;
|
||||
}
|
||||
|
||||
// Although it is not the responsibility of the EntitySimulation to sort the tree for EXTERNAL changes
|
||||
// it IS responsibile for triggering deletes for entities that leave the bounds of the domain, hence
|
||||
|
@ -158,8 +201,11 @@ void EntitySimulation::entityChanged(EntityItem* entity) {
|
|||
qCDebug(entities) << "Entity " << entity->getEntityItemID() << " moved out of domain bounds.";
|
||||
_entitiesToDelete.insert(entity);
|
||||
_mortalEntities.remove(entity);
|
||||
_updateableEntities.remove(entity);
|
||||
_entitiesToUpdate.remove(entity);
|
||||
_entitiesToSort.remove(entity);
|
||||
_simpleKinematicEntities.remove(entity);
|
||||
removeEntityInternal(entity);
|
||||
entity->_simulated = false;
|
||||
wasRemoved = true;
|
||||
}
|
||||
}
|
||||
|
@ -177,18 +223,41 @@ void EntitySimulation::entityChanged(EntityItem* entity) {
|
|||
entity->clearDirtyFlags(EntityItem::DIRTY_LIFETIME);
|
||||
}
|
||||
if (entity->needsToCallUpdate()) {
|
||||
_updateableEntities.insert(entity);
|
||||
_entitiesToUpdate.insert(entity);
|
||||
} else {
|
||||
_updateableEntities.remove(entity);
|
||||
_entitiesToUpdate.remove(entity);
|
||||
}
|
||||
entityChangedInternal(entity);
|
||||
changeEntityInternal(entity);
|
||||
}
|
||||
}
|
||||
|
||||
void EntitySimulation::clearEntities() {
|
||||
_mortalEntities.clear();
|
||||
_nextExpiry = quint64(-1);
|
||||
_updateableEntities.clear();
|
||||
_entitiesToBeSorted.clear();
|
||||
_entitiesToUpdate.clear();
|
||||
_entitiesToSort.clear();
|
||||
_simpleKinematicEntities.clear();
|
||||
_entitiesToDelete.clear();
|
||||
|
||||
clearEntitiesInternal();
|
||||
|
||||
for (auto entityItr : _allEntities) {
|
||||
entityItr->_simulated = false;
|
||||
}
|
||||
_allEntities.clear();
|
||||
}
|
||||
|
||||
void EntitySimulation::moveSimpleKinematics(const quint64& now) {
|
||||
SetOfEntities::iterator itemItr = _simpleKinematicEntities.begin();
|
||||
while (itemItr != _simpleKinematicEntities.end()) {
|
||||
EntityItem* entity = *itemItr;
|
||||
if (entity->isMoving() && !entity->getPhysicsInfo()) {
|
||||
entity->simulate(now);
|
||||
_entitiesToSort.insert(entity);
|
||||
++itemItr;
|
||||
} else {
|
||||
// the entity is no longer non-physical-kinematic
|
||||
itemItr = _simpleKinematicEntities.erase(itemItr);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -14,23 +14,30 @@
|
|||
|
||||
#include <QtCore/QObject>
|
||||
#include <QSet>
|
||||
#include <QVector>
|
||||
|
||||
#include <PerfStat.h>
|
||||
|
||||
#include "EntityItem.h"
|
||||
#include "EntityTree.h"
|
||||
|
||||
typedef QSet<EntityItem*> SetOfEntities;
|
||||
typedef QVector<EntityItem*> VectorOfEntities;
|
||||
|
||||
// the EntitySimulation needs to know when these things change on an entity,
|
||||
// so it can sort EntityItem or relay its state to the PhysicsEngine.
|
||||
const int DIRTY_SIMULATION_FLAGS =
|
||||
EntityItem::DIRTY_POSITION |
|
||||
EntityItem::DIRTY_VELOCITY |
|
||||
EntityItem::DIRTY_ROTATION |
|
||||
EntityItem::DIRTY_LINEAR_VELOCITY |
|
||||
EntityItem::DIRTY_ANGULAR_VELOCITY |
|
||||
EntityItem::DIRTY_MASS |
|
||||
EntityItem::DIRTY_COLLISION_GROUP |
|
||||
EntityItem::DIRTY_MOTION_TYPE |
|
||||
EntityItem::DIRTY_SHAPE |
|
||||
EntityItem::DIRTY_LIFETIME |
|
||||
EntityItem::DIRTY_UPDATEABLE;
|
||||
EntityItem::DIRTY_UPDATEABLE |
|
||||
EntityItem::DIRTY_MATERIAL;
|
||||
|
||||
class EntitySimulation : public QObject {
|
||||
Q_OBJECT
|
||||
|
@ -44,25 +51,34 @@ public:
|
|||
/// \param tree pointer to EntityTree which is stored internally
|
||||
void setEntityTree(EntityTree* tree);
|
||||
|
||||
/// \param[out] entitiesToDelete list of entities removed from simulation and should be deleted.
|
||||
void updateEntities(QSet<EntityItem*>& entitiesToDelete);
|
||||
void updateEntities();
|
||||
|
||||
/// \param entity pointer to EntityItem to add to the simulation
|
||||
/// \sideeffect the EntityItem::_simulationState member may be updated to indicate membership to internal list
|
||||
friend class EntityTree;
|
||||
|
||||
protected: // these only called by the EntityTree?
|
||||
/// \param entity pointer to EntityItem to be added
|
||||
/// \sideeffect sets relevant backpointers in entity, but maybe later when appropriate data structures are locked
|
||||
void addEntity(EntityItem* entity);
|
||||
|
||||
/// \param entity pointer to EntityItem to removed from the simulation
|
||||
/// \sideeffect the EntityItem::_simulationState member may be updated to indicate non-membership to internal list
|
||||
/// \param entity pointer to EntityItem to be removed
|
||||
/// \brief the actual removal may happen later when appropriate data structures are locked
|
||||
/// \sideeffect nulls relevant backpointers in entity
|
||||
void removeEntity(EntityItem* entity);
|
||||
|
||||
/// \param entity pointer to EntityItem to that may have changed in a way that would affect its simulation
|
||||
/// call this whenever an entity was changed from some EXTERNAL event (NOT by the EntitySimulation itself)
|
||||
void entityChanged(EntityItem* entity);
|
||||
void changeEntity(EntityItem* entity);
|
||||
|
||||
void clearEntities();
|
||||
|
||||
void moveSimpleKinematics(const quint64& now);
|
||||
|
||||
public:
|
||||
|
||||
EntityTree* getEntityTree() { return _entityTree; }
|
||||
|
||||
void getEntitiesToDelete(VectorOfEntities& entitiesToDelete);
|
||||
|
||||
signals:
|
||||
void entityCollisionWithEntity(const EntityItemID& idA, const EntityItemID& idB, const Collision& collision);
|
||||
|
||||
|
@ -70,18 +86,10 @@ protected:
|
|||
|
||||
// These pure virtual methods are protected because they are not to be called will-nilly. The base class
|
||||
// calls them in the right places.
|
||||
|
||||
// NOTE: updateEntitiesInternal() should clear all dirty flags on each changed entity as side effect
|
||||
virtual void updateEntitiesInternal(const quint64& now) = 0;
|
||||
|
||||
virtual void addEntityInternal(EntityItem* entity) = 0;
|
||||
|
||||
virtual void addEntityInternal(EntityItem* entity);
|
||||
virtual void removeEntityInternal(EntityItem* entity) = 0;
|
||||
|
||||
virtual void entityChangedInternal(EntityItem* entity) = 0;
|
||||
|
||||
virtual void sortEntitiesThatMovedInternal() {}
|
||||
|
||||
virtual void changeEntityInternal(EntityItem* entity);
|
||||
virtual void clearEntitiesInternal() = 0;
|
||||
|
||||
void expireMortalEntities(const quint64& now);
|
||||
|
@ -95,11 +103,16 @@ protected:
|
|||
|
||||
// We maintain multiple lists, each for its distinct purpose.
|
||||
// An entity may be in more than one list.
|
||||
QSet<EntityItem*> _mortalEntities; // entities that have an expiry
|
||||
SetOfEntities _allEntities; // tracks all entities added the simulation
|
||||
SetOfEntities _mortalEntities; // entities that have an expiry
|
||||
quint64 _nextExpiry;
|
||||
QSet<EntityItem*> _updateableEntities; // entities that need update() called
|
||||
QSet<EntityItem*> _entitiesToBeSorted; // entities that were moved by THIS simulation and might need to be resorted in the tree
|
||||
QSet<EntityItem*> _entitiesToDelete;
|
||||
SetOfEntities _entitiesToUpdate; // entities that need to call EntityItem::update()
|
||||
SetOfEntities _entitiesToSort; // entities moved by simulation (and might need resort in EntityTree)
|
||||
SetOfEntities _entitiesToDelete; // entities simulation decided needed to be deleted (EntityTree will actually delete)
|
||||
SetOfEntities _simpleKinematicEntities; // entities undergoing non-colliding kinematic motion
|
||||
|
||||
private:
|
||||
void moveSimpleKinematics();
|
||||
};
|
||||
|
||||
#endif // hifi_EntitySimulation_h
|
||||
|
|
|
@ -145,8 +145,7 @@ bool EntityTree::updateEntityWithElement(EntityItem* entity, const EntityItemPro
|
|||
// squash the physics-related changes.
|
||||
properties.setSimulatorIDChanged(false);
|
||||
properties.setPositionChanged(false);
|
||||
properties.setVelocityChanged(false);
|
||||
properties.setAccelerationChanged(false);
|
||||
properties.setRotationChanged(false);
|
||||
} else {
|
||||
qCDebug(entities) << "allowing simulatorID change";
|
||||
}
|
||||
|
@ -160,10 +159,10 @@ bool EntityTree::updateEntityWithElement(EntityItem* entity, const EntityItemPro
|
|||
|
||||
uint32_t newFlags = entity->getDirtyFlags() & ~preFlags;
|
||||
if (newFlags) {
|
||||
if (_simulation) {
|
||||
if (_simulation) {
|
||||
if (newFlags & DIRTY_SIMULATION_FLAGS) {
|
||||
_simulation->lock();
|
||||
_simulation->entityChanged(entity);
|
||||
_simulation->changeEntity(entity);
|
||||
_simulation->unlock();
|
||||
}
|
||||
} else {
|
||||
|
@ -351,8 +350,8 @@ void EntityTree::processRemovedEntities(const DeleteEntityOperator& theOperator)
|
|||
|
||||
if (_simulation) {
|
||||
_simulation->removeEntity(theEntity);
|
||||
}
|
||||
delete theEntity; // now actually delete the entity!
|
||||
}
|
||||
delete theEntity; // we can delete the entity immediately
|
||||
}
|
||||
if (_simulation) {
|
||||
_simulation->unlock();
|
||||
|
@ -742,7 +741,7 @@ void EntityTree::releaseSceneEncodeData(OctreeElementExtraEncodeData* extraEncod
|
|||
void EntityTree::entityChanged(EntityItem* entity) {
|
||||
if (_simulation) {
|
||||
_simulation->lock();
|
||||
_simulation->entityChanged(entity);
|
||||
_simulation->changeEntity(entity);
|
||||
_simulation->unlock();
|
||||
}
|
||||
}
|
||||
|
@ -750,16 +749,21 @@ void EntityTree::entityChanged(EntityItem* entity) {
|
|||
void EntityTree::update() {
|
||||
if (_simulation) {
|
||||
lockForWrite();
|
||||
QSet<EntityItem*> entitiesToDelete;
|
||||
_simulation->lock();
|
||||
_simulation->updateEntities(entitiesToDelete);
|
||||
_simulation->updateEntities();
|
||||
VectorOfEntities pendingDeletes;
|
||||
_simulation->getEntitiesToDelete(pendingDeletes);
|
||||
_simulation->unlock();
|
||||
if (entitiesToDelete.size() > 0) {
|
||||
|
||||
if (pendingDeletes.size() > 0) {
|
||||
// translate into list of ID's
|
||||
QSet<EntityItemID> idsToDelete;
|
||||
foreach (EntityItem* entity, entitiesToDelete) {
|
||||
for (auto entityItr : pendingDeletes) {
|
||||
EntityItem* entity = &(*entityItr);
|
||||
assert(!entity->getPhysicsInfo()); // TODO: Andrew to remove this after testing
|
||||
idsToDelete.insert(entity->getEntityItemID());
|
||||
}
|
||||
// delete these things the roundabout way
|
||||
deleteEntities(idsToDelete, true);
|
||||
}
|
||||
unlock();
|
||||
|
|
|
@ -13,12 +13,13 @@
|
|||
#define hifi_EntityTree_h
|
||||
|
||||
#include <QSet>
|
||||
#include <QVector>
|
||||
|
||||
#include <Octree.h>
|
||||
|
||||
#include "EntityTreeElement.h"
|
||||
#include "DeleteEntityOperator.h"
|
||||
|
||||
|
||||
class Model;
|
||||
class EntitySimulation;
|
||||
|
||||
|
|
|
@ -33,8 +33,8 @@ EntityItem* ModelEntityItem::factory(const EntityItemID& entityID, const EntityI
|
|||
}
|
||||
|
||||
ModelEntityItem::ModelEntityItem(const EntityItemID& entityItemID, const EntityItemProperties& properties) :
|
||||
EntityItem(entityItemID, properties)
|
||||
{
|
||||
EntityItem(entityItemID, properties)
|
||||
{
|
||||
_type = EntityTypes::Model;
|
||||
setProperties(properties);
|
||||
_lastAnimated = usecTimestampNow();
|
||||
|
@ -84,17 +84,17 @@ bool ModelEntityItem::setProperties(const EntityItemProperties& properties) {
|
|||
}
|
||||
setLastEdited(properties._lastEdited);
|
||||
}
|
||||
|
||||
|
||||
return somethingChanged;
|
||||
}
|
||||
|
||||
int ModelEntityItem::readEntitySubclassDataFromBuffer(const unsigned char* data, int bytesLeftToRead,
|
||||
int ModelEntityItem::readEntitySubclassDataFromBuffer(const unsigned char* data, int bytesLeftToRead,
|
||||
ReadBitstreamToTreeParams& args,
|
||||
EntityPropertyFlags& propertyFlags, bool overwriteLocalData) {
|
||||
|
||||
|
||||
int bytesRead = 0;
|
||||
const unsigned char* dataAt = data;
|
||||
|
||||
|
||||
READ_ENTITY_PROPERTY_COLOR(PROP_COLOR, _color);
|
||||
READ_ENTITY_PROPERTY_STRING(PROP_MODEL_URL, setModelURL);
|
||||
if (args.bitstreamVersion < VERSION_ENTITIES_HAS_COLLISION_MODEL) {
|
||||
|
@ -105,7 +105,7 @@ int ModelEntityItem::readEntitySubclassDataFromBuffer(const unsigned char* data,
|
|||
READ_ENTITY_PROPERTY_STRING(PROP_COMPOUND_SHAPE_URL, setCompoundShapeURL);
|
||||
}
|
||||
READ_ENTITY_PROPERTY_STRING(PROP_ANIMATION_URL, setAnimationURL);
|
||||
|
||||
|
||||
// Because we're using AnimationLoop which will reset the frame index if you change it's running state
|
||||
// we want to read these values in the order they appear in the buffer, but call our setters in an
|
||||
// order that allows AnimationLoop to preserve the correct frame rate.
|
||||
|
@ -115,7 +115,7 @@ int ModelEntityItem::readEntitySubclassDataFromBuffer(const unsigned char* data,
|
|||
READ_ENTITY_PROPERTY(PROP_ANIMATION_FPS, float, animationFPS);
|
||||
READ_ENTITY_PROPERTY(PROP_ANIMATION_FRAME_INDEX, float, animationFrameIndex);
|
||||
READ_ENTITY_PROPERTY(PROP_ANIMATION_PLAYING, bool, animationIsPlaying);
|
||||
|
||||
|
||||
if (propertyFlags.getHasProperty(PROP_ANIMATION_PLAYING)) {
|
||||
if (animationIsPlaying != getAnimationIsPlaying()) {
|
||||
setAnimationIsPlaying(animationIsPlaying);
|
||||
|
@ -148,12 +148,12 @@ EntityPropertyFlags ModelEntityItem::getEntityProperties(EncodeBitstreamParams&
|
|||
requestedProperties += PROP_ANIMATION_SETTINGS;
|
||||
requestedProperties += PROP_TEXTURES;
|
||||
requestedProperties += PROP_SHAPE_TYPE;
|
||||
|
||||
|
||||
return requestedProperties;
|
||||
}
|
||||
|
||||
|
||||
void ModelEntityItem::appendSubclassData(OctreePacketData* packetData, EncodeBitstreamParams& params,
|
||||
void ModelEntityItem::appendSubclassData(OctreePacketData* packetData, EncodeBitstreamParams& params,
|
||||
EntityTreeElementExtraEncodeData* entityTreeElementExtraEncodeData,
|
||||
EntityPropertyFlags& requestedProperties,
|
||||
EntityPropertyFlags& propertyFlags,
|
||||
|
@ -186,7 +186,7 @@ void ModelEntityItem::cleanupLoadedAnimations() {
|
|||
|
||||
Animation* ModelEntityItem::getAnimation(const QString& url) {
|
||||
AnimationPointer animation;
|
||||
|
||||
|
||||
// if we don't already have this model then create it and initialize it
|
||||
if (_loadedAnimations.find(url) == _loadedAnimations.end()) {
|
||||
animation = DependencyManager::get<AnimationCache>()->getAnimation(url);
|
||||
|
@ -204,7 +204,7 @@ void ModelEntityItem::mapJoints(const QStringList& modelJointNames) {
|
|||
}
|
||||
|
||||
Animation* myAnimation = getAnimation(_animationURL);
|
||||
|
||||
|
||||
if (!_jointMappingCompleted) {
|
||||
QStringList animationJointNames = myAnimation->getJointNames();
|
||||
|
||||
|
@ -229,7 +229,7 @@ QVector<glm::quat> ModelEntityItem::getAnimationFrame() {
|
|||
if (animationFrameIndex < 0 || animationFrameIndex > frameCount) {
|
||||
animationFrameIndex = 0;
|
||||
}
|
||||
|
||||
|
||||
QVector<glm::quat> rotations = frames[animationFrameIndex].rotations;
|
||||
|
||||
frameData.resize(_jointMapping.size());
|
||||
|
@ -244,8 +244,8 @@ QVector<glm::quat> ModelEntityItem::getAnimationFrame() {
|
|||
return frameData;
|
||||
}
|
||||
|
||||
bool ModelEntityItem::isAnimatingSomething() const {
|
||||
return getAnimationIsPlaying() &&
|
||||
bool ModelEntityItem::isAnimatingSomething() const {
|
||||
return getAnimationIsPlaying() &&
|
||||
getAnimationFPS() != 0.0f &&
|
||||
!getAnimationURL().isEmpty();
|
||||
}
|
||||
|
@ -278,7 +278,7 @@ void ModelEntityItem::debugDump() const {
|
|||
void ModelEntityItem::updateShapeType(ShapeType type) {
|
||||
// BEGIN_TEMPORARY_WORKAROUND
|
||||
// we have allowed inconsistent ShapeType's to be stored in SVO files in the past (this was a bug)
|
||||
// but we are now enforcing the entity properties to be consistent. To make the possible we're
|
||||
// but we are now enforcing the entity properties to be consistent. To make the possible we're
|
||||
// introducing a temporary workaround: we will ignore ShapeType updates that conflict with the
|
||||
// _compoundShapeURL.
|
||||
if (hasCompoundShapeURL()) {
|
||||
|
@ -292,7 +292,7 @@ void ModelEntityItem::updateShapeType(ShapeType type) {
|
|||
}
|
||||
}
|
||||
|
||||
// virtual
|
||||
// virtual
|
||||
ShapeType ModelEntityItem::getShapeType() const {
|
||||
if (_shapeType == SHAPE_TYPE_COMPOUND) {
|
||||
return hasCompoundShapeURL() ? SHAPE_TYPE_COMPOUND : SHAPE_TYPE_NONE;
|
||||
|
@ -309,9 +309,9 @@ void ModelEntityItem::setCompoundShapeURL(const QString& url) {
|
|||
}
|
||||
}
|
||||
|
||||
void ModelEntityItem::setAnimationURL(const QString& url) {
|
||||
void ModelEntityItem::setAnimationURL(const QString& url) {
|
||||
_dirtyFlags |= EntityItem::DIRTY_UPDATEABLE;
|
||||
_animationURL = url;
|
||||
_animationURL = url;
|
||||
}
|
||||
|
||||
void ModelEntityItem::setAnimationFrameIndex(float value) {
|
||||
|
@ -327,7 +327,7 @@ void ModelEntityItem::setAnimationFrameIndex(float value) {
|
|||
_animationLoop.setFrameIndex(value);
|
||||
}
|
||||
|
||||
void ModelEntityItem::setAnimationSettings(const QString& value) {
|
||||
void ModelEntityItem::setAnimationSettings(const QString& value) {
|
||||
// the animations setting is a JSON string that may contain various animation settings.
|
||||
// if it includes fps, frameIndex, or running, those values will be parsed out and
|
||||
// will over ride the regular animation settings
|
||||
|
@ -388,21 +388,21 @@ void ModelEntityItem::setAnimationSettings(const QString& value) {
|
|||
setAnimationStartAutomatically(startAutomatically);
|
||||
}
|
||||
|
||||
_animationSettings = value;
|
||||
_animationSettings = value;
|
||||
_dirtyFlags |= EntityItem::DIRTY_UPDATEABLE;
|
||||
}
|
||||
|
||||
void ModelEntityItem::setAnimationIsPlaying(bool value) {
|
||||
_dirtyFlags |= EntityItem::DIRTY_UPDATEABLE;
|
||||
_animationLoop.setRunning(value);
|
||||
_animationLoop.setRunning(value);
|
||||
}
|
||||
|
||||
void ModelEntityItem::setAnimationFPS(float value) {
|
||||
_dirtyFlags |= EntityItem::DIRTY_UPDATEABLE;
|
||||
_animationLoop.setFPS(value);
|
||||
_animationLoop.setFPS(value);
|
||||
}
|
||||
|
||||
QString ModelEntityItem::getAnimationSettings() const {
|
||||
QString ModelEntityItem::getAnimationSettings() const {
|
||||
// the animations setting is a JSON string that may contain various animation settings.
|
||||
// if it includes fps, frameIndex, or running, those values will be parsed out and
|
||||
// will over ride the regular animation settings
|
||||
|
@ -411,7 +411,7 @@ QString ModelEntityItem::getAnimationSettings() const {
|
|||
QJsonDocument settingsAsJson = QJsonDocument::fromJson(value.toUtf8());
|
||||
QJsonObject settingsAsJsonObject = settingsAsJson.object();
|
||||
QVariantMap settingsMap = settingsAsJsonObject.toVariantMap();
|
||||
|
||||
|
||||
QVariant fpsValue(getAnimationFPS());
|
||||
settingsMap["fps"] = fpsValue;
|
||||
|
||||
|
@ -435,10 +435,15 @@ QString ModelEntityItem::getAnimationSettings() const {
|
|||
|
||||
QVariant startAutomaticallyValue(getAnimationStartAutomatically());
|
||||
settingsMap["startAutomatically"] = startAutomaticallyValue;
|
||||
|
||||
|
||||
settingsAsJsonObject = QJsonObject::fromVariantMap(settingsMap);
|
||||
QJsonDocument newDocument(settingsAsJsonObject);
|
||||
QByteArray jsonByteArray = newDocument.toJson(QJsonDocument::Compact);
|
||||
QString jsonByteString(jsonByteArray);
|
||||
return jsonByteString;
|
||||
}
|
||||
|
||||
// virtual
|
||||
bool ModelEntityItem::shouldBePhysical() const {
|
||||
return EntityItem::shouldBePhysical() && getShapeType() != SHAPE_TYPE_NONE;
|
||||
}
|
||||
|
|
|
@ -117,6 +117,8 @@ public:
|
|||
static const QString DEFAULT_TEXTURES;
|
||||
const QString& getTextures() const { return _textures; }
|
||||
void setTextures(const QString& textures) { _textures = textures; }
|
||||
|
||||
virtual bool shouldBePhysical() const;
|
||||
|
||||
static void cleanupLoadedAnimations();
|
||||
|
||||
|
|
|
@ -18,32 +18,25 @@
|
|||
const quint64 AUTO_REMOVE_SIMULATION_OWNER_USEC = 2 * USECS_PER_SECOND;
|
||||
|
||||
void SimpleEntitySimulation::updateEntitiesInternal(const quint64& now) {
|
||||
// now is usecTimestampNow()
|
||||
QSet<EntityItem*>::iterator itemItr = _movingEntities.begin();
|
||||
while (itemItr != _movingEntities.end()) {
|
||||
EntityItem* entity = *itemItr;
|
||||
if (!entity->isMoving()) {
|
||||
itemItr = _movingEntities.erase(itemItr);
|
||||
_movableButStoppedEntities.insert(entity);
|
||||
} else {
|
||||
entity->simulate(now);
|
||||
_entitiesToBeSorted.insert(entity);
|
||||
++itemItr;
|
||||
}
|
||||
}
|
||||
|
||||
// If an Entity has a simulation owner and we don't get an update for some amount of time,
|
||||
// clear the owner. This guards against an interface failing to release the Entity when it
|
||||
// has finished simulating it.
|
||||
itemItr = _hasSimulationOwnerEntities.begin();
|
||||
auto nodeList = DependencyManager::get<LimitedNodeList>();
|
||||
|
||||
SetOfEntities::iterator itemItr = _hasSimulationOwnerEntities.begin();
|
||||
while (itemItr != _hasSimulationOwnerEntities.end()) {
|
||||
EntityItem* entity = *itemItr;
|
||||
if (entity->getSimulatorID().isNull()) {
|
||||
itemItr = _hasSimulationOwnerEntities.erase(itemItr);
|
||||
} else if (usecTimestampNow() - entity->getLastChangedOnServer() >= AUTO_REMOVE_SIMULATION_OWNER_USEC) {
|
||||
qCDebug(entities) << "auto-removing simulation owner" << entity->getSimulatorID();
|
||||
entity->setSimulatorID(QUuid());
|
||||
itemItr = _hasSimulationOwnerEntities.erase(itemItr);
|
||||
} else if (now - entity->getLastChangedOnServer() >= AUTO_REMOVE_SIMULATION_OWNER_USEC) {
|
||||
SharedNodePointer ownerNode = nodeList->nodeWithUUID(entity->getSimulatorID());
|
||||
if (ownerNode.isNull() || !ownerNode->isAlive()) {
|
||||
qCDebug(entities) << "auto-removing simulation owner" << entity->getSimulatorID();
|
||||
entity->setSimulatorID(QUuid());
|
||||
itemItr = _hasSimulationOwnerEntities.erase(itemItr);
|
||||
} else {
|
||||
++itemItr;
|
||||
}
|
||||
} else {
|
||||
++itemItr;
|
||||
}
|
||||
|
@ -51,45 +44,27 @@ void SimpleEntitySimulation::updateEntitiesInternal(const quint64& now) {
|
|||
}
|
||||
|
||||
void SimpleEntitySimulation::addEntityInternal(EntityItem* entity) {
|
||||
if (entity->isMoving()) {
|
||||
_movingEntities.insert(entity);
|
||||
} else if (entity->getCollisionsWillMove()) {
|
||||
_movableButStoppedEntities.insert(entity);
|
||||
}
|
||||
EntitySimulation::addEntityInternal(entity);
|
||||
if (!entity->getSimulatorID().isNull()) {
|
||||
_hasSimulationOwnerEntities.insert(entity);
|
||||
}
|
||||
}
|
||||
|
||||
void SimpleEntitySimulation::removeEntityInternal(EntityItem* entity) {
|
||||
_movingEntities.remove(entity);
|
||||
_movableButStoppedEntities.remove(entity);
|
||||
_hasSimulationOwnerEntities.remove(entity);
|
||||
}
|
||||
|
||||
const int SIMPLE_SIMULATION_DIRTY_FLAGS = EntityItem::DIRTY_VELOCITY | EntityItem::DIRTY_MOTION_TYPE;
|
||||
const int SIMPLE_SIMULATION_DIRTY_FLAGS = EntityItem::DIRTY_VELOCITIES | EntityItem::DIRTY_MOTION_TYPE;
|
||||
|
||||
void SimpleEntitySimulation::entityChangedInternal(EntityItem* entity) {
|
||||
int dirtyFlags = entity->getDirtyFlags();
|
||||
if (dirtyFlags & SIMPLE_SIMULATION_DIRTY_FLAGS) {
|
||||
if (entity->isMoving()) {
|
||||
_movingEntities.insert(entity);
|
||||
} else if (entity->getCollisionsWillMove()) {
|
||||
_movableButStoppedEntities.remove(entity);
|
||||
} else {
|
||||
_movingEntities.remove(entity);
|
||||
_movableButStoppedEntities.remove(entity);
|
||||
}
|
||||
if (!entity->getSimulatorID().isNull()) {
|
||||
_hasSimulationOwnerEntities.insert(entity);
|
||||
}
|
||||
void SimpleEntitySimulation::changeEntityInternal(EntityItem* entity) {
|
||||
EntitySimulation::changeEntityInternal(entity);
|
||||
if (!entity->getSimulatorID().isNull()) {
|
||||
_hasSimulationOwnerEntities.insert(entity);
|
||||
}
|
||||
entity->clearDirtyFlags();
|
||||
}
|
||||
|
||||
void SimpleEntitySimulation::clearEntitiesInternal() {
|
||||
_movingEntities.clear();
|
||||
_movableButStoppedEntities.clear();
|
||||
_hasSimulationOwnerEntities.clear();
|
||||
}
|
||||
|
||||
|
|
|
@ -25,12 +25,10 @@ protected:
|
|||
virtual void updateEntitiesInternal(const quint64& now);
|
||||
virtual void addEntityInternal(EntityItem* entity);
|
||||
virtual void removeEntityInternal(EntityItem* entity);
|
||||
virtual void entityChangedInternal(EntityItem* entity);
|
||||
virtual void changeEntityInternal(EntityItem* entity);
|
||||
virtual void clearEntitiesInternal();
|
||||
|
||||
QSet<EntityItem*> _movingEntities;
|
||||
QSet<EntityItem*> _movableButStoppedEntities;
|
||||
QSet<EntityItem*> _hasSimulationOwnerEntities;
|
||||
SetOfEntities _hasSimulationOwnerEntities;
|
||||
};
|
||||
|
||||
#endif // hifi_SimpleEntitySimulation_h
|
||||
|
|
|
@ -59,7 +59,10 @@ bool SkyboxPropertyGroup::decodeFromEditPacket(EntityPropertyFlags& propertyFlag
|
|||
|
||||
READ_ENTITY_PROPERTY_XCOLOR(PROP_SKYBOX_COLOR, _color);
|
||||
READ_ENTITY_PROPERTY_STRING(PROP_SKYBOX_URL, setURL);
|
||||
|
||||
|
||||
DECODE_GROUP_PROPERTY_HAS_CHANGED(PROP_SKYBOX_COLOR, Color);
|
||||
DECODE_GROUP_PROPERTY_HAS_CHANGED(PROP_SKYBOX_URL, URL);
|
||||
|
||||
processedBytes += bytesRead;
|
||||
|
||||
return true;
|
||||
|
|
|
@ -484,7 +484,7 @@ FBXGeometry OBJReader::readOBJ(QIODevice* device, const QVariantHash& mapping, Q
|
|||
meshPart._material->setShininess(material->shininess);
|
||||
meshPart._material->setOpacity(material->opacity);
|
||||
}
|
||||
qCDebug(modelformat) << "OBJ Reader part:" << meshPartCount << "name:" << leadFace.groupName << "material:" << groupMaterialName << "diffuse:" << meshPart._material->getDiffuse() << "faces:" << faceGroup.count() << "triangle indices will start with:" << mesh.vertices.count();
|
||||
// qCDebug(modelformat) << "OBJ Reader part:" << meshPartCount << "name:" << leadFace.groupName << "material:" << groupMaterialName << "diffuse:" << meshPart._material->getDiffuse() << "faces:" << faceGroup.count() << "triangle indices will start with:" << mesh.vertices.count();
|
||||
foreach(OBJFace face, faceGroup) {
|
||||
glm::vec3 v0 = vertices[face.vertexIndices[0]];
|
||||
glm::vec3 v1 = vertices[face.vertexIndices[1]];
|
||||
|
@ -510,6 +510,9 @@ FBXGeometry OBJReader::readOBJ(QIODevice* device, const QVariantHash& mapping, Q
|
|||
<< textureUVs[face.textureUVIndices[0]]
|
||||
<< textureUVs[face.textureUVIndices[1]]
|
||||
<< textureUVs[face.textureUVIndices[2]];
|
||||
} else {
|
||||
glm::vec2 corner(0.0f, 1.0f);
|
||||
mesh.texCoords << corner << corner << corner;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -526,7 +529,7 @@ FBXGeometry OBJReader::readOBJ(QIODevice* device, const QVariantHash& mapping, Q
|
|||
mesh.meshExtents.addPoint(vertex);
|
||||
geometry.meshExtents.addPoint(vertex);
|
||||
}
|
||||
fbxDebugDump(geometry);
|
||||
// fbxDebugDump(geometry);
|
||||
} catch(const std::exception& e) {
|
||||
qCDebug(modelformat) << "OBJ reader fail: " << e.what();
|
||||
}
|
||||
|
|
|
@ -56,7 +56,7 @@ public:
|
|||
glm::vec3 specularColor;
|
||||
QByteArray diffuseTextureFilename;
|
||||
QByteArray specularTextureFilename;
|
||||
OBJMaterial() : opacity(1.0f) {}
|
||||
OBJMaterial() : shininess(96.0f), opacity(1.0f), diffuseColor(1.0f), specularColor(1.0f) {}
|
||||
};
|
||||
|
||||
class OBJReader: public QObject { // QObject so we can make network requests.
|
||||
|
|
|
@ -479,6 +479,7 @@ void GLBackend::getCurrentGLState(State::Data& state) {
|
|||
void GLBackend::syncPipelineStateCache() {
|
||||
State::Data state;
|
||||
|
||||
glEnable(GL_TEXTURE_CUBE_MAP_SEAMLESS);
|
||||
getCurrentGLState(state);
|
||||
State::Signature signature = State::evalSignature(state);
|
||||
|
||||
|
|
|
@ -373,7 +373,6 @@ GLBackend::GLTexture* GLBackend::syncGPUObject(const Texture& texture) {
|
|||
GL_TEXTURE_CUBE_MAP_POSITIVE_X, GL_TEXTURE_CUBE_MAP_NEGATIVE_X,
|
||||
GL_TEXTURE_CUBE_MAP_POSITIVE_Y, GL_TEXTURE_CUBE_MAP_NEGATIVE_Y,
|
||||
GL_TEXTURE_CUBE_MAP_POSITIVE_Z, GL_TEXTURE_CUBE_MAP_NEGATIVE_Z };
|
||||
|
||||
if (needUpdate) {
|
||||
if (texture.isStoredMipAvailable(0)) {
|
||||
Texture::PixelsPointer mip = texture.accessStoredMip(0);
|
||||
|
|
|
@ -68,11 +68,11 @@ public:
|
|||
uint8 _maxMip = MAX_MIP_LEVEL;
|
||||
|
||||
Desc() {}
|
||||
Desc(const Filter filter) : _filter(filter) {}
|
||||
Desc(const Filter filter, const WrapMode wrap = WRAP_REPEAT) : _filter(filter), _wrapModeU(wrap), _wrapModeV(wrap), _wrapModeW(wrap) {}
|
||||
};
|
||||
|
||||
Sampler() {}
|
||||
Sampler(const Filter filter) : _desc(filter) {}
|
||||
Sampler(const Filter filter, const WrapMode wrap = WRAP_REPEAT) : _desc(filter, wrap) {}
|
||||
Sampler(const Desc& desc) : _desc(desc) {}
|
||||
~Sampler() {}
|
||||
|
||||
|
|
|
@ -92,15 +92,3 @@ void Material::setTextureView(MapChannel channel, const gpu::TextureView& view)
|
|||
_textureMap[channel] = view;
|
||||
}
|
||||
|
||||
// TextureStorage
|
||||
TextureStorage::TextureStorage(const QUrl& url) : gpu::Texture::Storage(), _url(url) {
|
||||
init();
|
||||
}
|
||||
|
||||
TextureStorage::~TextureStorage() {
|
||||
}
|
||||
|
||||
void TextureStorage::init() {
|
||||
_gpuTexture = TexturePointer(Texture::createFromStorage(this));
|
||||
}
|
||||
|
||||
|
|
|
@ -23,27 +23,6 @@
|
|||
|
||||
namespace model {
|
||||
|
||||
typedef glm::vec3 Color;
|
||||
|
||||
// TextureStorage is a specialized version of the gpu::Texture::Storage
|
||||
// It adds the URL and the notion that it owns the gpu::Texture
|
||||
class TextureStorage : public gpu::Texture::Storage {
|
||||
public:
|
||||
TextureStorage(const QUrl& url);
|
||||
~TextureStorage();
|
||||
|
||||
const QUrl& getUrl() const { return _url; }
|
||||
const gpu::TexturePointer& getGPUTexture() const { return _gpuTexture; }
|
||||
|
||||
protected:
|
||||
gpu::TexturePointer _gpuTexture;
|
||||
QUrl _url;
|
||||
void init();
|
||||
};
|
||||
typedef std::shared_ptr< TextureStorage > TextureStoragePointer;
|
||||
|
||||
|
||||
|
||||
class Material {
|
||||
public:
|
||||
typedef gpu::BufferView UniformBufferView;
|
||||
|
@ -62,6 +41,7 @@ public:
|
|||
NUM_MAPS,
|
||||
};
|
||||
typedef std::map<MapChannel, TextureView> TextureMap;
|
||||
typedef std::bitset<NUM_MAPS> MapFlags;
|
||||
|
||||
enum FlagBit {
|
||||
DIFFUSE_BIT = 0,
|
||||
|
|
|
@ -21,6 +21,7 @@ using namespace model;
|
|||
|
||||
Skybox::Skybox() {
|
||||
|
||||
/* // PLease create a default engineer skybox
|
||||
_cubemap.reset( gpu::Texture::createCube(gpu::Element::COLOR_RGBA_32, 1));
|
||||
unsigned char texels[] = {
|
||||
255, 0, 0, 255,
|
||||
|
@ -30,7 +31,7 @@ Skybox::Skybox() {
|
|||
0, 255, 0, 255,
|
||||
255, 0, 255, 255,
|
||||
};
|
||||
_cubemap->assignStoredMip(0, gpu::Element::COLOR_RGBA_32, sizeof(texels), texels);
|
||||
_cubemap->assignStoredMip(0, gpu::Element::COLOR_RGBA_32, sizeof(texels), texels);*/
|
||||
}
|
||||
|
||||
void Skybox::setColor(const Color& color) {
|
||||
|
@ -47,7 +48,7 @@ void Skybox::clearCubemap() {
|
|||
|
||||
void Skybox::render(gpu::Batch& batch, const ViewFrustum& viewFrustum, const Skybox& skybox) {
|
||||
|
||||
if (skybox.getCubemap()) {
|
||||
if (skybox.getCubemap() && skybox.getCubemap()->isDefined()) {
|
||||
static gpu::PipelinePointer thePipeline;
|
||||
static gpu::BufferPointer theBuffer;
|
||||
static gpu::Stream::FormatPointer theFormat;
|
||||
|
|
|
@ -18,7 +18,8 @@ varying vec3 color;
|
|||
|
||||
|
||||
void main(void) {
|
||||
vec4 texel = textureCube(cubeMap, normalize(normal));
|
||||
gl_FragData[0] = texel;
|
||||
// gl_FragData[0] = vec4(normal, 1.0);
|
||||
vec3 coord = normalize(normal);
|
||||
vec4 texel = textureCube(cubeMap, coord);
|
||||
// gl_FragData[0] = vec4(texel.xyz * color, texel.w);
|
||||
gl_FragData[0] = vec4(texel.xyz, 1.0);
|
||||
}
|
||||
|
|
|
@ -190,7 +190,8 @@ const float NUM_HOURS_PER_DAY = 24.0f;
|
|||
const float NUM_HOURS_PER_HALF_DAY = NUM_HOURS_PER_DAY * 0.5f;
|
||||
|
||||
SunSkyStage::SunSkyStage() :
|
||||
_sunLight(new Light())
|
||||
_sunLight(new Light()),
|
||||
_skybox(new Skybox())
|
||||
{
|
||||
_sunLight->setType(Light::SUN);
|
||||
|
||||
|
@ -290,6 +291,19 @@ void SunSkyStage::updateGraphicsObject() const {
|
|||
_sunLight->setPosition(Vec3(0.0f, originAlt, 0.0f));
|
||||
}
|
||||
|
||||
// Background
|
||||
switch (getBackgroundMode()) {
|
||||
case NO_BACKGROUND: {
|
||||
break;
|
||||
}
|
||||
case SKY_DOME: {
|
||||
break;
|
||||
}
|
||||
case SKY_BOX: {
|
||||
break;
|
||||
}
|
||||
};
|
||||
|
||||
static int firstTime = 0;
|
||||
if (firstTime == 0) {
|
||||
firstTime++;
|
||||
|
|
|
@ -223,11 +223,11 @@ public:
|
|||
const SkyboxPointer& getSkybox() const { valid(); return _skybox; }
|
||||
|
||||
protected:
|
||||
BackgroundMode _backgroundMode = SKY_DOME;
|
||||
BackgroundMode _backgroundMode = SKY_BOX;
|
||||
|
||||
LightPointer _sunLight;
|
||||
AtmospherePointer _atmosphere;
|
||||
SkyboxPointer _skybox;
|
||||
mutable SkyboxPointer _skybox;
|
||||
|
||||
gpu::PipelinePointer _skyPipeline;
|
||||
|
||||
|
|
28
libraries/model/src/model/TextureStorage.cpp
Executable file
28
libraries/model/src/model/TextureStorage.cpp
Executable file
|
@ -0,0 +1,28 @@
|
|||
//
|
||||
// TextureStorage.cpp
|
||||
// libraries/model/src/model
|
||||
//
|
||||
// Created by Sam Gateau on 5/6/2015.
|
||||
// Copyright 2014 High Fidelity, Inc.
|
||||
//
|
||||
// Distributed under the Apache License, Version 2.0.
|
||||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
//
|
||||
#include "TextureStorage.h"
|
||||
|
||||
using namespace model;
|
||||
using namespace gpu;
|
||||
|
||||
// TextureStorage
|
||||
TextureStorage::TextureStorage() : Texture::Storage(),
|
||||
_gpuTexture(Texture::createFromStorage(this))
|
||||
{}
|
||||
|
||||
TextureStorage::~TextureStorage() {
|
||||
}
|
||||
|
||||
void TextureStorage::reset(const QUrl& url, const TextureUsage& usage) {
|
||||
_url = url;
|
||||
_usage = usage;
|
||||
}
|
||||
|
56
libraries/model/src/model/TextureStorage.h
Executable file
56
libraries/model/src/model/TextureStorage.h
Executable file
|
@ -0,0 +1,56 @@
|
|||
//
|
||||
// TextureStorage.h
|
||||
// libraries/model/src/model
|
||||
//
|
||||
// Created by Sam Gateau on 5/6/2015.
|
||||
// Copyright 2014 High Fidelity, Inc.
|
||||
//
|
||||
// Distributed under the Apache License, Version 2.0.
|
||||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
//
|
||||
#ifndef hifi_model_TextureStorage_h
|
||||
#define hifi_model_TextureStorage_h
|
||||
|
||||
#include "gpu/Texture.h"
|
||||
|
||||
#include "Material.h"
|
||||
|
||||
#include <qurl.h>
|
||||
|
||||
namespace model {
|
||||
|
||||
typedef glm::vec3 Color;
|
||||
|
||||
class TextureUsage {
|
||||
public:
|
||||
gpu::Texture::Type _type{ gpu::Texture::TEX_2D };
|
||||
Material::MapFlags _materialUsage{ Material::DIFFUSE_MAP };
|
||||
|
||||
int _environmentUsage = 0;
|
||||
};
|
||||
|
||||
// TextureStorage is a specialized version of the gpu::Texture::Storage
|
||||
// It provides the mechanism to create a texture from a Url and the intended usage
|
||||
// that guides the internal format used
|
||||
class TextureStorage : public gpu::Texture::Storage {
|
||||
public:
|
||||
TextureStorage();
|
||||
~TextureStorage();
|
||||
|
||||
const QUrl& getUrl() const { return _url; }
|
||||
const gpu::Texture::Type getType() const { return _usage._type; }
|
||||
const gpu::TexturePointer& getGPUTexture() const { return _gpuTexture; }
|
||||
|
||||
void reset(const QUrl& url, const TextureUsage& usage);
|
||||
|
||||
protected:
|
||||
gpu::TexturePointer _gpuTexture;
|
||||
TextureUsage _usage;
|
||||
QUrl _url;
|
||||
};
|
||||
typedef std::shared_ptr< TextureStorage > TextureStoragePointer;
|
||||
|
||||
};
|
||||
|
||||
#endif // hifi_model_TextureStorage_h
|
||||
|
|
@ -16,7 +16,7 @@
|
|||
#include "OctreeConstants.h"
|
||||
#include "OctreeQuery.h"
|
||||
|
||||
Setting::Handle<int> maxOctreePacketsPerSecond("maxOctreePPS", DEFAULT_MAX_OCTREE_PPS);
|
||||
Setting::Handle<int> maxOctreePacketsPerSecond("maxOctreePPSSpin", DEFAULT_MAX_OCTREE_PPS);
|
||||
|
||||
OctreeQuery::OctreeQuery() {
|
||||
_maxOctreePPS = maxOctreePacketsPerSecond.get();
|
||||
|
|
|
@ -9,16 +9,14 @@
|
|||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
//
|
||||
|
||||
#include "BulletUtil.h"
|
||||
#include "ContactInfo.h"
|
||||
|
||||
void ContactInfo::update(uint32_t currentStep, btManifoldPoint& p, const glm::vec3& worldOffset) {
|
||||
void ContactInfo::update(uint32_t currentStep, const btManifoldPoint& p) {
|
||||
_lastStep = currentStep;
|
||||
++_numSteps;
|
||||
contactPoint = bulletToGLM(p.m_positionWorldOnB) + worldOffset;
|
||||
penetration = bulletToGLM(p.m_distance1 * p.m_normalWorldOnB);
|
||||
// TODO: also report normal
|
||||
//_normal = bulletToGLM(p.m_normalWorldOnB);
|
||||
positionWorldOnB = p.m_positionWorldOnB;
|
||||
normalWorldOnB = p.m_normalWorldOnB;
|
||||
distance = p.m_distance1;
|
||||
}
|
||||
|
||||
ContactEventType ContactInfo::computeType(uint32_t thisStep) {
|
||||
|
|
|
@ -17,17 +17,18 @@
|
|||
|
||||
#include "RegisteredMetaTypes.h"
|
||||
|
||||
enum ContactEventType {
|
||||
CONTACT_EVENT_TYPE_START,
|
||||
CONTACT_EVENT_TYPE_CONTINUE,
|
||||
CONTACT_EVENT_TYPE_END
|
||||
};
|
||||
|
||||
class ContactInfo : public Collision
|
||||
{
|
||||
class ContactInfo {
|
||||
public:
|
||||
void update(uint32_t currentStep, btManifoldPoint& p, const glm::vec3& worldOffset);
|
||||
void update(uint32_t currentStep, const btManifoldPoint& p);
|
||||
ContactEventType computeType(uint32_t thisStep);
|
||||
|
||||
const btVector3& getPositionWorldOnB() const { return positionWorldOnB; }
|
||||
btVector3 getPositionWorldOnA() const { return positionWorldOnB + normalWorldOnB * distance; }
|
||||
|
||||
btVector3 positionWorldOnB;
|
||||
btVector3 normalWorldOnB;
|
||||
btScalar distance;
|
||||
private:
|
||||
uint32_t _lastStep = 0;
|
||||
uint32_t _numSteps = 0;
|
||||
|
|
|
@ -21,60 +21,87 @@
|
|||
static const float ACCELERATION_EQUIVALENT_EPSILON_RATIO = 0.1f;
|
||||
static const quint8 STEPS_TO_DECIDE_BALLISTIC = 4;
|
||||
|
||||
QSet<EntityItem*>* _outgoingEntityList;
|
||||
|
||||
// static
|
||||
void EntityMotionState::setOutgoingEntityList(QSet<EntityItem*>* list) {
|
||||
assert(list);
|
||||
_outgoingEntityList = list;
|
||||
}
|
||||
|
||||
// static
|
||||
void EntityMotionState::enqueueOutgoingEntity(EntityItem* entity) {
|
||||
assert(_outgoingEntityList);
|
||||
_outgoingEntityList->insert(entity);
|
||||
}
|
||||
|
||||
EntityMotionState::EntityMotionState(EntityItem* entity) :
|
||||
EntityMotionState::EntityMotionState(btCollisionShape* shape, EntityItem* entity) :
|
||||
ObjectMotionState(shape),
|
||||
_entity(entity),
|
||||
_sentMoving(false),
|
||||
_numNonMovingUpdates(0),
|
||||
_lastStep(0),
|
||||
_serverPosition(0.0f),
|
||||
_serverRotation(),
|
||||
_serverVelocity(0.0f),
|
||||
_serverAngularVelocity(0.0f),
|
||||
_serverGravity(0.0f),
|
||||
_serverAcceleration(0.0f),
|
||||
_accelerationNearlyGravityCount(0),
|
||||
_shouldClaimSimulationOwnership(false),
|
||||
_movingStepsWithoutSimulationOwner(0)
|
||||
{
|
||||
_type = MOTION_STATE_TYPE_ENTITY;
|
||||
assert(entity != NULL);
|
||||
assert(entity != nullptr);
|
||||
}
|
||||
|
||||
EntityMotionState::~EntityMotionState() {
|
||||
assert(_entity);
|
||||
_entity->setPhysicsInfo(NULL);
|
||||
_entity = NULL;
|
||||
// be sure to clear _entity before calling the destructor
|
||||
assert(!_entity);
|
||||
}
|
||||
|
||||
void EntityMotionState::updateServerPhysicsVariables(uint32_t flags) {
|
||||
if (flags & EntityItem::DIRTY_POSITION) {
|
||||
_serverPosition = _entity->getPosition();
|
||||
}
|
||||
if (flags & EntityItem::DIRTY_ROTATION) {
|
||||
_serverRotation = _entity->getRotation();
|
||||
}
|
||||
if (flags & EntityItem::DIRTY_LINEAR_VELOCITY) {
|
||||
_serverVelocity = _entity->getVelocity();
|
||||
}
|
||||
if (flags & EntityItem::DIRTY_ANGULAR_VELOCITY) {
|
||||
_serverAngularVelocity = _entity->getAngularVelocity();
|
||||
}
|
||||
}
|
||||
|
||||
// virtual
|
||||
void EntityMotionState::handleEasyChanges(uint32_t flags) {
|
||||
updateServerPhysicsVariables(flags);
|
||||
ObjectMotionState::handleEasyChanges(flags);
|
||||
}
|
||||
|
||||
|
||||
// virtual
|
||||
void EntityMotionState::handleHardAndEasyChanges(uint32_t flags, PhysicsEngine* engine) {
|
||||
updateServerPhysicsVariables(flags);
|
||||
ObjectMotionState::handleHardAndEasyChanges(flags, engine);
|
||||
}
|
||||
|
||||
void EntityMotionState::clearEntity() {
|
||||
_entity = nullptr;
|
||||
// set the type to INVALID so that external logic that pivots on the type won't try to access _entity
|
||||
_type = MOTION_STATE_TYPE_INVALID;
|
||||
}
|
||||
|
||||
MotionType EntityMotionState::computeObjectMotionType() const {
|
||||
if (!_entity) {
|
||||
return MOTION_TYPE_STATIC;
|
||||
}
|
||||
if (_entity->getCollisionsWillMove()) {
|
||||
return MOTION_TYPE_DYNAMIC;
|
||||
}
|
||||
return _entity->isMoving() ? MOTION_TYPE_KINEMATIC : MOTION_TYPE_STATIC;
|
||||
}
|
||||
|
||||
void EntityMotionState::updateKinematicState(uint32_t substep) {
|
||||
setKinematic(_entity->isMoving(), substep);
|
||||
}
|
||||
|
||||
void EntityMotionState::stepKinematicSimulation(quint64 now) {
|
||||
assert(_isKinematic);
|
||||
// NOTE: this is non-physical kinematic motion which steps to real run-time (now)
|
||||
// which is different from physical kinematic motion (inside getWorldTransform())
|
||||
// which steps in physics simulation time.
|
||||
_entity->simulate(now);
|
||||
// TODO: we can't use measureBodyAcceleration() here because the entity
|
||||
// has no RigidBody and the timestep is a little bit out of sync with the physics simulation anyway.
|
||||
// Hence we must manually measure kinematic velocity and acceleration.
|
||||
}
|
||||
|
||||
bool EntityMotionState::isMoving() const {
|
||||
return _entity->isMoving();
|
||||
return _entity && _entity->isMoving();
|
||||
}
|
||||
|
||||
bool EntityMotionState::isMovingVsServer() const {
|
||||
auto alignmentDot = glm::abs(glm::dot(_serverRotation, _entity->getRotation()));
|
||||
if (glm::distance(_serverPosition, _entity->getPosition()) > IGNORE_POSITION_DELTA ||
|
||||
alignmentDot < IGNORE_ALIGNMENT_DOT) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// This callback is invoked by the physics simulation in two cases:
|
||||
|
@ -83,52 +110,54 @@ bool EntityMotionState::isMoving() const {
|
|||
// (2) at the beginning of each simulation step for KINEMATIC RigidBody's --
|
||||
// it is an opportunity for outside code to update the object's simulation position
|
||||
void EntityMotionState::getWorldTransform(btTransform& worldTrans) const {
|
||||
if (_isKinematic) {
|
||||
if (!_entity) {
|
||||
return;
|
||||
}
|
||||
if (_motionType == MOTION_TYPE_KINEMATIC) {
|
||||
// This is physical kinematic motion which steps strictly by the subframe count
|
||||
// of the physics simulation.
|
||||
uint32_t substep = PhysicsEngine::getNumSubsteps();
|
||||
float dt = (substep - _lastKinematicSubstep) * PHYSICS_ENGINE_FIXED_SUBSTEP;
|
||||
uint32_t thisStep = ObjectMotionState::getWorldSimulationStep();
|
||||
float dt = (thisStep - _lastKinematicStep) * PHYSICS_ENGINE_FIXED_SUBSTEP;
|
||||
_entity->simulateKinematicMotion(dt);
|
||||
_entity->setLastSimulated(usecTimestampNow());
|
||||
|
||||
// bypass const-ness so we can remember the substep
|
||||
const_cast<EntityMotionState*>(this)->_lastKinematicSubstep = substep;
|
||||
// bypass const-ness so we can remember the step
|
||||
const_cast<EntityMotionState*>(this)->_lastKinematicStep = thisStep;
|
||||
}
|
||||
worldTrans.setOrigin(glmToBullet(_entity->getPosition() - ObjectMotionState::getWorldOffset()));
|
||||
worldTrans.setOrigin(glmToBullet(getObjectPosition()));
|
||||
worldTrans.setRotation(glmToBullet(_entity->getRotation()));
|
||||
}
|
||||
|
||||
// This callback is invoked by the physics simulation at the end of each simulation step...
|
||||
// iff the corresponding RigidBody is DYNAMIC and has moved.
|
||||
void EntityMotionState::setWorldTransform(const btTransform& worldTrans) {
|
||||
if (!_entity) {
|
||||
return;
|
||||
}
|
||||
measureBodyAcceleration();
|
||||
_entity->setPosition(bulletToGLM(worldTrans.getOrigin()) + ObjectMotionState::getWorldOffset());
|
||||
_entity->setRotation(bulletToGLM(worldTrans.getRotation()));
|
||||
|
||||
glm::vec3 v;
|
||||
getVelocity(v);
|
||||
_entity->setVelocity(v);
|
||||
|
||||
glm::vec3 av;
|
||||
getAngularVelocity(av);
|
||||
_entity->setAngularVelocity(av);
|
||||
_entity->setVelocity(getBodyLinearVelocity());
|
||||
_entity->setAngularVelocity(getBodyAngularVelocity());
|
||||
|
||||
_entity->setLastSimulated(usecTimestampNow());
|
||||
|
||||
if (_entity->getSimulatorID().isNull() && isMoving()) {
|
||||
// object is moving and has no owner. attempt to claim simulation ownership.
|
||||
// if (_entity->getSimulatorID().isNull() && isMoving()) {
|
||||
if (_entity->getSimulatorID().isNull() && isMovingVsServer()) {
|
||||
// if object is moving and has no owner, attempt to claim simulation ownership.
|
||||
_movingStepsWithoutSimulationOwner++;
|
||||
} else {
|
||||
_movingStepsWithoutSimulationOwner = 0;
|
||||
}
|
||||
|
||||
if (_movingStepsWithoutSimulationOwner > 4) { // XXX maybe meters from our characterController ?
|
||||
uint32_t ownershipClaimDelay = 50; // TODO -- how to pick this? based on meters from our characterController?
|
||||
|
||||
if (_movingStepsWithoutSimulationOwner > ownershipClaimDelay) {
|
||||
//qDebug() << "Warning -- claiming something I saw moving." << getName();
|
||||
setShouldClaimSimulationOwnership(true);
|
||||
}
|
||||
|
||||
_outgoingPacketFlags = DIRTY_PHYSICS_FLAGS;
|
||||
EntityMotionState::enqueueOutgoingEntity(_entity);
|
||||
|
||||
#ifdef WANT_DEBUG
|
||||
quint64 now = usecTimestampNow();
|
||||
qCDebug(physics) << "EntityMotionState::setWorldTransform()... changed entity:" << _entity->getEntityItemID();
|
||||
|
@ -138,76 +167,126 @@ void EntityMotionState::setWorldTransform(const btTransform& worldTrans) {
|
|||
#endif
|
||||
}
|
||||
|
||||
void EntityMotionState::updateBodyEasy(uint32_t flags, uint32_t step) {
|
||||
if (flags & (EntityItem::DIRTY_POSITION | EntityItem::DIRTY_VELOCITY | EntityItem::DIRTY_PHYSICS_NO_WAKE)) {
|
||||
if (flags & EntityItem::DIRTY_POSITION) {
|
||||
_sentPosition = getObjectPosition() - ObjectMotionState::getWorldOffset();
|
||||
btTransform worldTrans;
|
||||
worldTrans.setOrigin(glmToBullet(_sentPosition));
|
||||
|
||||
_sentRotation = getObjectRotation();
|
||||
worldTrans.setRotation(glmToBullet(_sentRotation));
|
||||
|
||||
_body->setWorldTransform(worldTrans);
|
||||
}
|
||||
if (flags & EntityItem::DIRTY_VELOCITY) {
|
||||
updateBodyVelocities();
|
||||
}
|
||||
_sentStep = step;
|
||||
|
||||
if (flags & (EntityItem::DIRTY_POSITION | EntityItem::DIRTY_VELOCITY)) {
|
||||
_body->activate();
|
||||
}
|
||||
}
|
||||
|
||||
if (flags & EntityItem::DIRTY_MATERIAL) {
|
||||
updateBodyMaterialProperties();
|
||||
}
|
||||
|
||||
if (flags & EntityItem::DIRTY_MASS) {
|
||||
ShapeInfo shapeInfo;
|
||||
_entity->computeShapeInfo(shapeInfo);
|
||||
float mass = computeObjectMass(shapeInfo);
|
||||
btVector3 inertia(0.0f, 0.0f, 0.0f);
|
||||
_body->getCollisionShape()->calculateLocalInertia(mass, inertia);
|
||||
_body->setMassProps(mass, inertia);
|
||||
_body->updateInertiaTensor();
|
||||
}
|
||||
}
|
||||
|
||||
void EntityMotionState::updateBodyMaterialProperties() {
|
||||
_body->setRestitution(getObjectRestitution());
|
||||
_body->setFriction(getObjectFriction());
|
||||
_body->setDamping(fabsf(btMin(getObjectLinearDamping(), 1.0f)), fabsf(btMin(getObjectAngularDamping(), 1.0f)));
|
||||
}
|
||||
|
||||
void EntityMotionState::updateBodyVelocities() {
|
||||
if (_body) {
|
||||
_sentVelocity = getObjectLinearVelocity();
|
||||
setBodyVelocity(_sentVelocity);
|
||||
|
||||
_sentAngularVelocity = getObjectAngularVelocity();
|
||||
setBodyAngularVelocity(_sentAngularVelocity);
|
||||
|
||||
_sentGravity = getObjectGravity();
|
||||
setBodyGravity(_sentGravity);
|
||||
|
||||
_body->setActivationState(ACTIVE_TAG);
|
||||
}
|
||||
}
|
||||
|
||||
void EntityMotionState::computeObjectShapeInfo(ShapeInfo& shapeInfo) {
|
||||
if (_entity->isReadyToComputeShape()) {
|
||||
if (_entity) {
|
||||
_entity->computeShapeInfo(shapeInfo);
|
||||
}
|
||||
}
|
||||
|
||||
float EntityMotionState::computeObjectMass(const ShapeInfo& shapeInfo) const {
|
||||
return _entity->computeMass();
|
||||
// RELIABLE_SEND_HACK: until we have truly reliable resends of non-moving updates
|
||||
// we alwasy resend packets for objects that have stopped moving up to some max limit.
|
||||
const int MAX_NUM_NON_MOVING_UPDATES = 5;
|
||||
|
||||
bool EntityMotionState::doesNotNeedToSendUpdate() const {
|
||||
return !_body || (_body->isActive() && _numNonMovingUpdates > MAX_NUM_NON_MOVING_UPDATES);
|
||||
}
|
||||
|
||||
bool EntityMotionState::remoteSimulationOutOfSync(uint32_t simulationStep) {
|
||||
assert(_body);
|
||||
// if we've never checked before, our _lastStep will be 0, and we need to initialize our state
|
||||
if (_lastStep == 0) {
|
||||
btTransform xform = _body->getWorldTransform();
|
||||
_serverPosition = bulletToGLM(xform.getOrigin());
|
||||
_serverRotation = bulletToGLM(xform.getRotation());
|
||||
_serverVelocity = bulletToGLM(_body->getLinearVelocity());
|
||||
_serverAngularVelocity = bulletToGLM(_body->getAngularVelocity());
|
||||
_lastStep = simulationStep;
|
||||
return false;
|
||||
}
|
||||
|
||||
#ifdef WANT_DEBUG
|
||||
glm::vec3 wasPosition = _serverPosition;
|
||||
glm::quat wasRotation = _serverRotation;
|
||||
glm::vec3 wasAngularVelocity = _serverAngularVelocity;
|
||||
#endif
|
||||
|
||||
int numSteps = simulationStep - _lastStep;
|
||||
float dt = (float)(numSteps) * PHYSICS_ENGINE_FIXED_SUBSTEP;
|
||||
_lastStep = simulationStep;
|
||||
bool isActive = _body->isActive();
|
||||
|
||||
if (!isActive) {
|
||||
if (_sentMoving) {
|
||||
// this object just went inactive so send an update immediately
|
||||
return true;
|
||||
} else {
|
||||
const float NON_MOVING_UPDATE_PERIOD = 1.0f;
|
||||
if (dt > NON_MOVING_UPDATE_PERIOD && _numNonMovingUpdates < MAX_NUM_NON_MOVING_UPDATES) {
|
||||
// RELIABLE_SEND_HACK: since we're not yet using a reliable method for non-moving update packets we repeat these
|
||||
// at a faster rate than the MAX period above, and only send a limited number of them.
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Else we measure the error between current and extrapolated transform (according to expected behavior
|
||||
// of remote EntitySimulation) and return true if the error is significant.
|
||||
|
||||
// NOTE: math is done in the simulation-frame, which is NOT necessarily the same as the world-frame
|
||||
// due to _worldOffset.
|
||||
|
||||
// compute position error
|
||||
if (glm::length2(_serverVelocity) > 0.0f) {
|
||||
_serverVelocity += _serverAcceleration * dt;
|
||||
_serverVelocity *= powf(1.0f - _body->getLinearDamping(), dt);
|
||||
_serverPosition += dt * _serverVelocity;
|
||||
}
|
||||
|
||||
// TODO: compensate for simulation offset here
|
||||
btTransform worldTrans = _body->getWorldTransform();
|
||||
glm::vec3 position = bulletToGLM(worldTrans.getOrigin());
|
||||
|
||||
float dx2 = glm::distance2(position, _serverPosition);
|
||||
|
||||
const float MAX_POSITION_ERROR_SQUARED = 0.001f; // 0.001 m^2 ~~> 0.03 m
|
||||
if (dx2 > MAX_POSITION_ERROR_SQUARED) {
|
||||
|
||||
#ifdef WANT_DEBUG
|
||||
qCDebug(physics) << ".... (dx2 > MAX_POSITION_ERROR_SQUARED) ....";
|
||||
qCDebug(physics) << "wasPosition:" << wasPosition;
|
||||
qCDebug(physics) << "bullet position:" << position;
|
||||
qCDebug(physics) << "_serverPosition:" << _serverPosition;
|
||||
qCDebug(physics) << "dx2:" << dx2;
|
||||
#endif
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
if (glm::length2(_serverAngularVelocity) > 0.0f) {
|
||||
// compute rotation error
|
||||
float attenuation = powf(1.0f - _body->getAngularDamping(), dt);
|
||||
_serverAngularVelocity *= attenuation;
|
||||
|
||||
// Bullet caps the effective rotation velocity inside its rotation integration step, therefore
|
||||
// we must integrate with the same algorithm and timestep in order achieve similar results.
|
||||
for (int i = 0; i < numSteps; ++i) {
|
||||
_serverRotation = glm::normalize(computeBulletRotationStep(_serverAngularVelocity, PHYSICS_ENGINE_FIXED_SUBSTEP) * _serverRotation);
|
||||
}
|
||||
}
|
||||
const float MIN_ROTATION_DOT = 0.99f; // 0.99 dot threshold coresponds to about 16 degrees of slop
|
||||
glm::quat actualRotation = bulletToGLM(worldTrans.getRotation());
|
||||
|
||||
#ifdef WANT_DEBUG
|
||||
if ((fabsf(glm::dot(actualRotation, _serverRotation)) < MIN_ROTATION_DOT)) {
|
||||
qCDebug(physics) << ".... ((fabsf(glm::dot(actualRotation, _serverRotation)) < MIN_ROTATION_DOT)) ....";
|
||||
|
||||
qCDebug(physics) << "wasAngularVelocity:" << wasAngularVelocity;
|
||||
qCDebug(physics) << "_serverAngularVelocity:" << _serverAngularVelocity;
|
||||
|
||||
qCDebug(physics) << "length wasAngularVelocity:" << glm::length(wasAngularVelocity);
|
||||
qCDebug(physics) << "length _serverAngularVelocity:" << glm::length(_serverAngularVelocity);
|
||||
|
||||
qCDebug(physics) << "wasRotation:" << wasRotation;
|
||||
qCDebug(physics) << "bullet actualRotation:" << actualRotation;
|
||||
qCDebug(physics) << "_serverRotation:" << _serverRotation;
|
||||
}
|
||||
#endif
|
||||
|
||||
return (fabsf(glm::dot(actualRotation, _serverRotation)) < MIN_ROTATION_DOT);
|
||||
}
|
||||
|
||||
bool EntityMotionState::shouldSendUpdate(uint32_t simulationFrame) {
|
||||
if (!ObjectMotionState::shouldSendUpdate(simulationFrame)) {
|
||||
if (!_entity || !remoteSimulationOutOfSync(simulationFrame)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
|
@ -228,12 +307,21 @@ bool EntityMotionState::shouldSendUpdate(uint32_t simulationFrame) {
|
|||
}
|
||||
|
||||
void EntityMotionState::sendUpdate(OctreeEditPacketSender* packetSender, uint32_t step) {
|
||||
if (!_entity->isKnownID()) {
|
||||
if (!_entity || !_entity->isKnownID()) {
|
||||
return; // never update entities that are unknown
|
||||
}
|
||||
if (_outgoingPacketFlags) {
|
||||
EntityItemProperties properties = _entity->getProperties();
|
||||
|
||||
bool active = _body->isActive();
|
||||
if (!active) {
|
||||
if (_sentMoving) {
|
||||
// make sure all derivatives are zero
|
||||
glm::vec3 zero(0.0f);
|
||||
_entity->setVelocity(zero);
|
||||
_entity->setAngularVelocity(zero);
|
||||
_entity->setAcceleration(zero);
|
||||
}
|
||||
|
||||
} else {
|
||||
float gravityLength = glm::length(_entity->getGravity());
|
||||
float accVsGravity = glm::abs(glm::length(_measuredAcceleration) - gravityLength);
|
||||
if (accVsGravity < ACCELERATION_EQUIVALENT_EPSILON_RATIO * gravityLength) {
|
||||
|
@ -255,117 +343,93 @@ void EntityMotionState::sendUpdate(OctreeEditPacketSender* packetSender, uint32_
|
|||
} else {
|
||||
_entity->setAcceleration(glm::vec3(0.0f));
|
||||
}
|
||||
|
||||
if (_outgoingPacketFlags & EntityItem::DIRTY_POSITION) {
|
||||
btTransform worldTrans = _body->getWorldTransform();
|
||||
_sentPosition = bulletToGLM(worldTrans.getOrigin());
|
||||
properties.setPosition(_sentPosition + ObjectMotionState::getWorldOffset());
|
||||
|
||||
_sentRotation = bulletToGLM(worldTrans.getRotation());
|
||||
properties.setRotation(_sentRotation);
|
||||
}
|
||||
|
||||
bool zeroSpeed = true;
|
||||
bool zeroSpin = true;
|
||||
|
||||
if (_outgoingPacketFlags & EntityItem::DIRTY_VELOCITY) {
|
||||
if (_body->isActive()) {
|
||||
_sentVelocity = bulletToGLM(_body->getLinearVelocity());
|
||||
_sentAngularVelocity = bulletToGLM(_body->getAngularVelocity());
|
||||
|
||||
// if the speeds are very small we zero them out
|
||||
const float MINIMUM_EXTRAPOLATION_SPEED_SQUARED = 1.0e-4f; // 1cm/sec
|
||||
zeroSpeed = (glm::length2(_sentVelocity) < MINIMUM_EXTRAPOLATION_SPEED_SQUARED);
|
||||
if (zeroSpeed) {
|
||||
_sentVelocity = glm::vec3(0.0f);
|
||||
}
|
||||
const float MINIMUM_EXTRAPOLATION_SPIN_SQUARED = 0.004f; // ~0.01 rotation/sec
|
||||
zeroSpin = glm::length2(_sentAngularVelocity) < MINIMUM_EXTRAPOLATION_SPIN_SQUARED;
|
||||
if (zeroSpin) {
|
||||
_sentAngularVelocity = glm::vec3(0.0f);
|
||||
}
|
||||
|
||||
_sentMoving = ! (zeroSpeed && zeroSpin);
|
||||
} else {
|
||||
_sentVelocity = _sentAngularVelocity = glm::vec3(0.0f);
|
||||
_sentMoving = false;
|
||||
}
|
||||
properties.setVelocity(_sentVelocity);
|
||||
_sentGravity = _entity->getGravity();
|
||||
properties.setGravity(_entity->getGravity());
|
||||
_sentAcceleration = _entity->getAcceleration();
|
||||
properties.setAcceleration(_sentAcceleration);
|
||||
properties.setAngularVelocity(_sentAngularVelocity);
|
||||
}
|
||||
|
||||
auto nodeList = DependencyManager::get<NodeList>();
|
||||
QUuid myNodeID = nodeList->getSessionUUID();
|
||||
QUuid simulatorID = _entity->getSimulatorID();
|
||||
|
||||
if (getShouldClaimSimulationOwnership()) {
|
||||
properties.setSimulatorID(myNodeID);
|
||||
setShouldClaimSimulationOwnership(false);
|
||||
} else if (simulatorID == myNodeID && zeroSpeed && zeroSpin) {
|
||||
// we are the simulator and the entity has stopped. give up "simulator" status
|
||||
_entity->setSimulatorID(QUuid());
|
||||
properties.setSimulatorID(QUuid());
|
||||
} else if (simulatorID == myNodeID && !_body->isActive()) {
|
||||
// it's not active. don't keep simulation ownership.
|
||||
_entity->setSimulatorID(QUuid());
|
||||
properties.setSimulatorID(QUuid());
|
||||
}
|
||||
|
||||
// RELIABLE_SEND_HACK: count number of updates for entities at rest so we can stop sending them after some limit.
|
||||
if (_sentMoving) {
|
||||
_numNonMovingUpdates = 0;
|
||||
} else {
|
||||
_numNonMovingUpdates++;
|
||||
}
|
||||
if (_numNonMovingUpdates <= 1) {
|
||||
// we only update lastEdited when we're sending new physics data
|
||||
// (i.e. NOT when we just simulate the positions forward, nor when we resend non-moving data)
|
||||
// NOTE: Andrew & Brad to discuss. Let's make sure we're using lastEdited, lastSimulated, and lastUpdated correctly
|
||||
quint64 lastSimulated = _entity->getLastSimulated();
|
||||
_entity->setLastEdited(lastSimulated);
|
||||
properties.setLastEdited(lastSimulated);
|
||||
|
||||
#ifdef WANT_DEBUG
|
||||
quint64 now = usecTimestampNow();
|
||||
qCDebug(physics) << "EntityMotionState::sendUpdate()";
|
||||
qCDebug(physics) << " EntityItemId:" << _entity->getEntityItemID()
|
||||
<< "---------------------------------------------";
|
||||
qCDebug(physics) << " lastSimulated:" << debugTime(lastSimulated, now);
|
||||
#endif //def WANT_DEBUG
|
||||
|
||||
} else {
|
||||
properties.setLastEdited(_entity->getLastEdited());
|
||||
}
|
||||
|
||||
if (EntityItem::getSendPhysicsUpdates()) {
|
||||
EntityItemID id(_entity->getID());
|
||||
EntityEditPacketSender* entityPacketSender = static_cast<EntityEditPacketSender*>(packetSender);
|
||||
#ifdef WANT_DEBUG
|
||||
qCDebug(physics) << "EntityMotionState::sendUpdate()... calling queueEditEntityMessage()...";
|
||||
#endif
|
||||
|
||||
entityPacketSender->queueEditEntityMessage(PacketTypeEntityAddOrEdit, id, properties);
|
||||
} else {
|
||||
#ifdef WANT_DEBUG
|
||||
qCDebug(physics) << "EntityMotionState::sendUpdate()... NOT sending update as requested.";
|
||||
#endif
|
||||
}
|
||||
|
||||
// The outgoing flags only itemized WHAT to send, not WHETHER to send, hence we always set them
|
||||
// to the full set. These flags may be momentarily cleared by incoming external changes.
|
||||
_outgoingPacketFlags = DIRTY_PHYSICS_FLAGS;
|
||||
_sentStep = step;
|
||||
}
|
||||
|
||||
// remember properties for local server prediction
|
||||
_serverPosition = _entity->getPosition();
|
||||
_serverRotation = _entity->getRotation();
|
||||
_serverVelocity = _entity->getVelocity();
|
||||
_serverAcceleration = _entity->getAcceleration();
|
||||
_serverAngularVelocity = _entity->getAngularVelocity();
|
||||
|
||||
_sentMoving = _serverVelocity != glm::vec3(0.0f) || _serverAngularVelocity != glm::vec3(0.0f);
|
||||
|
||||
EntityItemProperties properties = _entity->getProperties();
|
||||
|
||||
// explicitly set the properties that changed
|
||||
properties.setPosition(_serverPosition);
|
||||
properties.setRotation(_serverRotation);
|
||||
properties.setVelocity(_serverVelocity);
|
||||
properties.setAcceleration(_serverAcceleration);
|
||||
properties.setAngularVelocity(_serverAngularVelocity);
|
||||
|
||||
// RELIABLE_SEND_HACK: count number of updates for entities at rest
|
||||
// so we can stop sending them after some limit.
|
||||
if (_sentMoving) {
|
||||
_numNonMovingUpdates = 0;
|
||||
} else {
|
||||
_numNonMovingUpdates++;
|
||||
}
|
||||
if (_numNonMovingUpdates <= 1) {
|
||||
// we only update lastEdited when we're sending new physics data
|
||||
quint64 lastSimulated = _entity->getLastSimulated();
|
||||
_entity->setLastEdited(lastSimulated);
|
||||
properties.setLastEdited(lastSimulated);
|
||||
|
||||
#ifdef WANT_DEBUG
|
||||
quint64 now = usecTimestampNow();
|
||||
qCDebug(physics) << "EntityMotionState::sendUpdate()";
|
||||
qCDebug(physics) << " EntityItemId:" << _entity->getEntityItemID()
|
||||
<< "---------------------------------------------";
|
||||
qCDebug(physics) << " lastSimulated:" << debugTime(lastSimulated, now);
|
||||
#endif //def WANT_DEBUG
|
||||
|
||||
} else {
|
||||
properties.setLastEdited(_entity->getLastEdited());
|
||||
}
|
||||
|
||||
auto nodeList = DependencyManager::get<NodeList>();
|
||||
QUuid myNodeID = nodeList->getSessionUUID();
|
||||
QUuid simulatorID = _entity->getSimulatorID();
|
||||
|
||||
if (getShouldClaimSimulationOwnership()) {
|
||||
// we think we should own it, so we tell the server that we do,
|
||||
// but we don't remember that we own it...
|
||||
// instead we expect the sever to tell us later whose ownership it has accepted
|
||||
properties.setSimulatorID(myNodeID);
|
||||
setShouldClaimSimulationOwnership(false);
|
||||
} else if (simulatorID == myNodeID
|
||||
&& !_sentMoving
|
||||
&& _numNonMovingUpdates == MAX_NUM_NON_MOVING_UPDATES) {
|
||||
// we own it, the entity has stopped, and we're sending the last non-moving update
|
||||
// --> give up ownership
|
||||
_entity->setSimulatorID(QUuid());
|
||||
properties.setSimulatorID(QUuid());
|
||||
}
|
||||
|
||||
if (EntityItem::getSendPhysicsUpdates()) {
|
||||
EntityItemID id(_entity->getID());
|
||||
EntityEditPacketSender* entityPacketSender = static_cast<EntityEditPacketSender*>(packetSender);
|
||||
#ifdef WANT_DEBUG
|
||||
qCDebug(physics) << "EntityMotionState::sendUpdate()... calling queueEditEntityMessage()...";
|
||||
#endif
|
||||
|
||||
entityPacketSender->queueEditEntityMessage(PacketTypeEntityAddOrEdit, id, properties);
|
||||
_entity->setLastBroadcast(usecTimestampNow());
|
||||
} else {
|
||||
#ifdef WANT_DEBUG
|
||||
qCDebug(physics) << "EntityMotionState::sendUpdate()... NOT sending update as requested.";
|
||||
#endif
|
||||
}
|
||||
|
||||
_lastStep = step;
|
||||
}
|
||||
|
||||
uint32_t EntityMotionState::getIncomingDirtyFlags() const {
|
||||
uint32_t dirtyFlags = _entity->getDirtyFlags();
|
||||
|
||||
if (_body) {
|
||||
uint32_t EntityMotionState::getAndClearIncomingDirtyFlags() const {
|
||||
uint32_t dirtyFlags = 0;
|
||||
if (_body && _entity) {
|
||||
dirtyFlags = _entity->getDirtyFlags();
|
||||
_entity->clearDirtyFlags();
|
||||
// we add DIRTY_MOTION_TYPE if the body's motion type disagrees with entity velocity settings
|
||||
int bodyFlags = _body->getCollisionFlags();
|
||||
bool isMoving = _entity->isMoving();
|
||||
|
@ -376,3 +440,60 @@ uint32_t EntityMotionState::getIncomingDirtyFlags() const {
|
|||
}
|
||||
return dirtyFlags;
|
||||
}
|
||||
|
||||
|
||||
// virtual
|
||||
QUuid EntityMotionState::getSimulatorID() const {
|
||||
if (_entity) {
|
||||
return _entity->getSimulatorID();
|
||||
}
|
||||
return QUuid();
|
||||
}
|
||||
|
||||
|
||||
// virtual
|
||||
void EntityMotionState::bump() {
|
||||
setShouldClaimSimulationOwnership(true);
|
||||
}
|
||||
|
||||
void EntityMotionState::resetMeasuredBodyAcceleration() {
|
||||
_lastMeasureStep = ObjectMotionState::getWorldSimulationStep();
|
||||
if (_body) {
|
||||
_lastVelocity = bulletToGLM(_body->getLinearVelocity());
|
||||
} else {
|
||||
_lastVelocity = glm::vec3(0.0f);
|
||||
}
|
||||
_measuredAcceleration = glm::vec3(0.0f);
|
||||
}
|
||||
|
||||
void EntityMotionState::measureBodyAcceleration() {
|
||||
// try to manually measure the true acceleration of the object
|
||||
uint32_t thisStep = ObjectMotionState::getWorldSimulationStep();
|
||||
uint32_t numSubsteps = thisStep - _lastMeasureStep;
|
||||
if (numSubsteps > 0) {
|
||||
float dt = ((float)numSubsteps * PHYSICS_ENGINE_FIXED_SUBSTEP);
|
||||
float invDt = 1.0f / dt;
|
||||
_lastMeasureStep = thisStep;
|
||||
|
||||
// Note: the integration equation for velocity uses damping: v1 = (v0 + a * dt) * (1 - D)^dt
|
||||
// hence the equation for acceleration is: a = (v1 / (1 - D)^dt - v0) / dt
|
||||
glm::vec3 velocity = bulletToGLM(_body->getLinearVelocity());
|
||||
_measuredAcceleration = (velocity / powf(1.0f - _body->getLinearDamping(), dt) - _lastVelocity) * invDt;
|
||||
_lastVelocity = velocity;
|
||||
}
|
||||
}
|
||||
|
||||
// virtual
|
||||
void EntityMotionState::setMotionType(MotionType motionType) {
|
||||
ObjectMotionState::setMotionType(motionType);
|
||||
resetMeasuredBodyAcceleration();
|
||||
}
|
||||
|
||||
|
||||
// virtual
|
||||
QString EntityMotionState::getName() {
|
||||
if (_entity) {
|
||||
return _entity->getName();
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
|
|
@ -25,22 +25,18 @@ class EntityItem;
|
|||
class EntityMotionState : public ObjectMotionState {
|
||||
public:
|
||||
|
||||
// The OutgoingEntityQueue is a pointer to a QSet (owned by an EntitySimulation) of EntityItem*'s
|
||||
// that have been changed by the physics simulation.
|
||||
// All ObjectMotionState's with outgoing changes put themselves on the list.
|
||||
static void setOutgoingEntityList(QSet<EntityItem*>* list);
|
||||
static void enqueueOutgoingEntity(EntityItem* entity);
|
||||
|
||||
EntityMotionState(EntityItem* item);
|
||||
EntityMotionState(btCollisionShape* shape, EntityItem* item);
|
||||
virtual ~EntityMotionState();
|
||||
|
||||
void updateServerPhysicsVariables(uint32_t flags);
|
||||
virtual void handleEasyChanges(uint32_t flags);
|
||||
virtual void handleHardAndEasyChanges(uint32_t flags, PhysicsEngine* engine);
|
||||
|
||||
/// \return MOTION_TYPE_DYNAMIC or MOTION_TYPE_STATIC based on params set in EntityItem
|
||||
virtual MotionType computeObjectMotionType() const;
|
||||
|
||||
virtual void updateKinematicState(uint32_t substep);
|
||||
virtual void stepKinematicSimulation(quint64 now);
|
||||
|
||||
virtual bool isMoving() const;
|
||||
virtual bool isMovingVsServer() const;
|
||||
|
||||
// this relays incoming position/rotation to the RigidBody
|
||||
virtual void getWorldTransform(btTransform& worldTrans) const;
|
||||
|
@ -48,41 +44,70 @@ public:
|
|||
// this relays outgoing position/rotation to the EntityItem
|
||||
virtual void setWorldTransform(const btTransform& worldTrans);
|
||||
|
||||
// these relay incoming values to the RigidBody
|
||||
virtual void updateBodyEasy(uint32_t flags, uint32_t step);
|
||||
virtual void updateBodyMaterialProperties();
|
||||
virtual void updateBodyVelocities();
|
||||
|
||||
virtual void computeObjectShapeInfo(ShapeInfo& shapeInfo);
|
||||
virtual float computeObjectMass(const ShapeInfo& shapeInfo) const;
|
||||
|
||||
virtual bool shouldSendUpdate(uint32_t simulationFrame);
|
||||
virtual void sendUpdate(OctreeEditPacketSender* packetSender, uint32_t step);
|
||||
bool doesNotNeedToSendUpdate() const;
|
||||
bool remoteSimulationOutOfSync(uint32_t simulationStep);
|
||||
bool shouldSendUpdate(uint32_t simulationFrame);
|
||||
void sendUpdate(OctreeEditPacketSender* packetSender, uint32_t step);
|
||||
|
||||
virtual uint32_t getIncomingDirtyFlags() const;
|
||||
virtual void clearIncomingDirtyFlags(uint32_t flags) { _entity->clearDirtyFlags(flags); }
|
||||
void setShouldClaimSimulationOwnership(bool value) { _shouldClaimSimulationOwnership = value; }
|
||||
bool getShouldClaimSimulationOwnership() { return _shouldClaimSimulationOwnership; }
|
||||
|
||||
virtual uint32_t getAndClearIncomingDirtyFlags() const;
|
||||
|
||||
void incrementAccelerationNearlyGravityCount() { _accelerationNearlyGravityCount++; }
|
||||
void resetAccelerationNearlyGravityCount() { _accelerationNearlyGravityCount = 0; }
|
||||
quint8 getAccelerationNearlyGravityCount() { return _accelerationNearlyGravityCount; }
|
||||
|
||||
virtual EntityItem* getEntity() const { return _entity; }
|
||||
virtual void setShouldClaimSimulationOwnership(bool value) { _shouldClaimSimulationOwnership = value; }
|
||||
virtual bool getShouldClaimSimulationOwnership() { return _shouldClaimSimulationOwnership; }
|
||||
|
||||
virtual float getObjectRestitution() const { return _entity->getRestitution(); }
|
||||
virtual float getObjectFriction() const { return _entity->getFriction(); }
|
||||
virtual float getObjectLinearDamping() const { return _entity->getDamping(); }
|
||||
virtual float getObjectAngularDamping() const { return _entity->getAngularDamping(); }
|
||||
|
||||
virtual const glm::vec3& getObjectPosition() const { return _entity->getPosition(); }
|
||||
virtual glm::vec3 getObjectPosition() const { return _entity->getPosition() - ObjectMotionState::getWorldOffset(); }
|
||||
virtual const glm::quat& getObjectRotation() const { return _entity->getRotation(); }
|
||||
virtual const glm::vec3& getObjectLinearVelocity() const { return _entity->getVelocity(); }
|
||||
virtual const glm::vec3& getObjectAngularVelocity() const { return _entity->getAngularVelocity(); }
|
||||
virtual const glm::vec3& getObjectGravity() const { return _entity->getGravity(); }
|
||||
|
||||
virtual const QUuid& getObjectID() const { return _entity->getID(); }
|
||||
|
||||
virtual QUuid getSimulatorID() const;
|
||||
virtual void bump();
|
||||
|
||||
EntityItem* getEntity() const { return _entity; }
|
||||
|
||||
void resetMeasuredBodyAcceleration();
|
||||
void measureBodyAcceleration();
|
||||
|
||||
virtual QString getName();
|
||||
|
||||
friend class PhysicalEntitySimulation;
|
||||
|
||||
protected:
|
||||
void clearEntity();
|
||||
|
||||
virtual void setMotionType(MotionType motionType);
|
||||
|
||||
EntityItem* _entity;
|
||||
|
||||
bool _sentMoving; // true if last update was moving
|
||||
int _numNonMovingUpdates; // RELIABLE_SEND_HACK for "not so reliable" resends of packets for non-moving objects
|
||||
|
||||
// these are for the prediction of the remote server's simple extrapolation
|
||||
uint32_t _lastStep; // last step of server extrapolation
|
||||
glm::vec3 _serverPosition; // in simulation-frame (not world-frame)
|
||||
glm::quat _serverRotation;
|
||||
glm::vec3 _serverVelocity;
|
||||
glm::vec3 _serverAngularVelocity; // radians per second
|
||||
glm::vec3 _serverGravity;
|
||||
glm::vec3 _serverAcceleration;
|
||||
|
||||
uint32_t _lastMeasureStep;
|
||||
glm::vec3 _lastVelocity;
|
||||
glm::vec3 _measuredAcceleration;
|
||||
|
||||
quint8 _accelerationNearlyGravityCount;
|
||||
bool _shouldClaimSimulationOwnership;
|
||||
quint32 _movingStepsWithoutSimulationOwner;
|
||||
|
|
|
@ -31,57 +31,43 @@ const glm::vec3& ObjectMotionState::getWorldOffset() {
|
|||
}
|
||||
|
||||
// static
|
||||
uint32_t _worldSimulationStep = 0;
|
||||
uint32_t worldSimulationStep = 0;
|
||||
void ObjectMotionState::setWorldSimulationStep(uint32_t step) {
|
||||
assert(step > _worldSimulationStep);
|
||||
_worldSimulationStep = step;
|
||||
assert(step > worldSimulationStep);
|
||||
worldSimulationStep = step;
|
||||
}
|
||||
|
||||
ObjectMotionState::ObjectMotionState() :
|
||||
uint32_t ObjectMotionState::getWorldSimulationStep() {
|
||||
return worldSimulationStep;
|
||||
}
|
||||
|
||||
// static
|
||||
ShapeManager* shapeManager = nullptr;
|
||||
void ObjectMotionState::setShapeManager(ShapeManager* manager) {
|
||||
assert(manager);
|
||||
shapeManager = manager;
|
||||
}
|
||||
|
||||
ShapeManager* ObjectMotionState::getShapeManager() {
|
||||
assert(shapeManager); // you must properly set shapeManager before calling getShapeManager()
|
||||
return shapeManager;
|
||||
}
|
||||
|
||||
ObjectMotionState::ObjectMotionState(btCollisionShape* shape) :
|
||||
_motionType(MOTION_TYPE_STATIC),
|
||||
_body(NULL),
|
||||
_sentMoving(false),
|
||||
_numNonMovingUpdates(0),
|
||||
_outgoingPacketFlags(DIRTY_PHYSICS_FLAGS),
|
||||
_sentStep(0),
|
||||
_sentPosition(0.0f),
|
||||
_sentRotation(),
|
||||
_sentVelocity(0.0f),
|
||||
_sentAngularVelocity(0.0f),
|
||||
_sentGravity(0.0f),
|
||||
_sentAcceleration(0.0f),
|
||||
_lastSimulationStep(0),
|
||||
_lastVelocity(0.0f),
|
||||
_measuredAcceleration(0.0f) {
|
||||
_shape(shape),
|
||||
_body(nullptr),
|
||||
_mass(0.0f),
|
||||
_lastKinematicStep(worldSimulationStep)
|
||||
{
|
||||
}
|
||||
|
||||
ObjectMotionState::~ObjectMotionState() {
|
||||
// NOTE: you MUST remove this MotionState from the world before you call the dtor.
|
||||
assert(_body == NULL);
|
||||
assert(!_body);
|
||||
assert(!_shape);
|
||||
}
|
||||
|
||||
void ObjectMotionState::measureBodyAcceleration() {
|
||||
// try to manually measure the true acceleration of the object
|
||||
uint32_t numSubsteps = _worldSimulationStep - _lastSimulationStep;
|
||||
if (numSubsteps > 0) {
|
||||
float dt = ((float)numSubsteps * PHYSICS_ENGINE_FIXED_SUBSTEP);
|
||||
float invDt = 1.0f / dt;
|
||||
_lastSimulationStep = _worldSimulationStep;
|
||||
|
||||
// Note: the integration equation for velocity uses damping: v1 = (v0 + a * dt) * (1 - D)^dt
|
||||
// hence the equation for acceleration is: a = (v1 / (1 - D)^dt - v0) / dt
|
||||
glm::vec3 velocity = bulletToGLM(_body->getLinearVelocity());
|
||||
_measuredAcceleration = (velocity / powf(1.0f - _body->getLinearDamping(), dt) - _lastVelocity) * invDt;
|
||||
_lastVelocity = velocity;
|
||||
}
|
||||
}
|
||||
|
||||
void ObjectMotionState::resetMeasuredBodyAcceleration() {
|
||||
_lastSimulationStep = _worldSimulationStep;
|
||||
_lastVelocity = bulletToGLM(_body->getLinearVelocity());
|
||||
}
|
||||
|
||||
void ObjectMotionState::setBodyVelocity(const glm::vec3& velocity) const {
|
||||
void ObjectMotionState::setBodyLinearVelocity(const glm::vec3& velocity) const {
|
||||
_body->setLinearVelocity(glmToBullet(velocity));
|
||||
}
|
||||
|
||||
|
@ -93,130 +79,30 @@ void ObjectMotionState::setBodyGravity(const glm::vec3& gravity) const {
|
|||
_body->setGravity(glmToBullet(gravity));
|
||||
}
|
||||
|
||||
void ObjectMotionState::getVelocity(glm::vec3& velocityOut) const {
|
||||
velocityOut = bulletToGLM(_body->getLinearVelocity());
|
||||
glm::vec3 ObjectMotionState::getBodyLinearVelocity() const {
|
||||
return bulletToGLM(_body->getLinearVelocity());
|
||||
}
|
||||
|
||||
void ObjectMotionState::getAngularVelocity(glm::vec3& angularVelocityOut) const {
|
||||
angularVelocityOut = bulletToGLM(_body->getAngularVelocity());
|
||||
glm::vec3 ObjectMotionState::getBodyAngularVelocity() const {
|
||||
return bulletToGLM(_body->getAngularVelocity());
|
||||
}
|
||||
|
||||
// RELIABLE_SEND_HACK: until we have truly reliable resends of non-moving updates
|
||||
// we alwasy resend packets for objects that have stopped moving up to some max limit.
|
||||
const int MAX_NUM_NON_MOVING_UPDATES = 5;
|
||||
|
||||
bool ObjectMotionState::doesNotNeedToSendUpdate() const {
|
||||
return !_body->isActive() && _numNonMovingUpdates > MAX_NUM_NON_MOVING_UPDATES;
|
||||
void ObjectMotionState::releaseShape() {
|
||||
if (_shape) {
|
||||
shapeManager->releaseShape(_shape);
|
||||
_shape = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
bool ObjectMotionState::shouldSendUpdate(uint32_t simulationStep) {
|
||||
assert(_body);
|
||||
// if we've never checked before, our _sentStep will be 0, and we need to initialize our state
|
||||
if (_sentStep == 0) {
|
||||
btTransform xform = _body->getWorldTransform();
|
||||
_sentPosition = bulletToGLM(xform.getOrigin());
|
||||
_sentRotation = bulletToGLM(xform.getRotation());
|
||||
_sentVelocity = bulletToGLM(_body->getLinearVelocity());
|
||||
_sentAngularVelocity = bulletToGLM(_body->getAngularVelocity());
|
||||
_sentStep = simulationStep;
|
||||
return false;
|
||||
}
|
||||
|
||||
#ifdef WANT_DEBUG
|
||||
glm::vec3 wasPosition = _sentPosition;
|
||||
glm::quat wasRotation = _sentRotation;
|
||||
glm::vec3 wasAngularVelocity = _sentAngularVelocity;
|
||||
#endif
|
||||
|
||||
int numSteps = simulationStep - _sentStep;
|
||||
float dt = (float)(numSteps) * PHYSICS_ENGINE_FIXED_SUBSTEP;
|
||||
_sentStep = simulationStep;
|
||||
bool isActive = _body->isActive();
|
||||
|
||||
if (!isActive) {
|
||||
if (_sentMoving) {
|
||||
// this object just went inactive so send an update immediately
|
||||
return true;
|
||||
} else {
|
||||
const float NON_MOVING_UPDATE_PERIOD = 1.0f;
|
||||
if (dt > NON_MOVING_UPDATE_PERIOD && _numNonMovingUpdates < MAX_NUM_NON_MOVING_UPDATES) {
|
||||
// RELIABLE_SEND_HACK: since we're not yet using a reliable method for non-moving update packets we repeat these
|
||||
// at a faster rate than the MAX period above, and only send a limited number of them.
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Else we measure the error between current and extrapolated transform (according to expected behavior
|
||||
// of remote EntitySimulation) and return true if the error is significant.
|
||||
|
||||
// NOTE: math is done in the simulation-frame, which is NOT necessarily the same as the world-frame
|
||||
// due to _worldOffset.
|
||||
|
||||
// compute position error
|
||||
if (glm::length2(_sentVelocity) > 0.0f) {
|
||||
_sentVelocity += _sentAcceleration * dt;
|
||||
_sentVelocity *= powf(1.0f - _body->getLinearDamping(), dt);
|
||||
_sentPosition += dt * _sentVelocity;
|
||||
}
|
||||
|
||||
btTransform worldTrans = _body->getWorldTransform();
|
||||
glm::vec3 position = bulletToGLM(worldTrans.getOrigin());
|
||||
|
||||
float dx2 = glm::distance2(position, _sentPosition);
|
||||
|
||||
const float MAX_POSITION_ERROR_SQUARED = 0.001f; // 0.001 m^2 ~~> 0.03 m
|
||||
if (dx2 > MAX_POSITION_ERROR_SQUARED) {
|
||||
|
||||
#ifdef WANT_DEBUG
|
||||
qCDebug(physics) << ".... (dx2 > MAX_POSITION_ERROR_SQUARED) ....";
|
||||
qCDebug(physics) << "wasPosition:" << wasPosition;
|
||||
qCDebug(physics) << "bullet position:" << position;
|
||||
qCDebug(physics) << "_sentPosition:" << _sentPosition;
|
||||
qCDebug(physics) << "dx2:" << dx2;
|
||||
#endif
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
if (glm::length2(_sentAngularVelocity) > 0.0f) {
|
||||
// compute rotation error
|
||||
float attenuation = powf(1.0f - _body->getAngularDamping(), dt);
|
||||
_sentAngularVelocity *= attenuation;
|
||||
|
||||
// Bullet caps the effective rotation velocity inside its rotation integration step, therefore
|
||||
// we must integrate with the same algorithm and timestep in order achieve similar results.
|
||||
for (int i = 0; i < numSteps; ++i) {
|
||||
_sentRotation = glm::normalize(computeBulletRotationStep(_sentAngularVelocity, PHYSICS_ENGINE_FIXED_SUBSTEP) * _sentRotation);
|
||||
}
|
||||
}
|
||||
const float MIN_ROTATION_DOT = 0.99f; // 0.99 dot threshold coresponds to about 16 degrees of slop
|
||||
glm::quat actualRotation = bulletToGLM(worldTrans.getRotation());
|
||||
|
||||
#ifdef WANT_DEBUG
|
||||
if ((fabsf(glm::dot(actualRotation, _sentRotation)) < MIN_ROTATION_DOT)) {
|
||||
qCDebug(physics) << ".... ((fabsf(glm::dot(actualRotation, _sentRotation)) < MIN_ROTATION_DOT)) ....";
|
||||
|
||||
qCDebug(physics) << "wasAngularVelocity:" << wasAngularVelocity;
|
||||
qCDebug(physics) << "_sentAngularVelocity:" << _sentAngularVelocity;
|
||||
|
||||
qCDebug(physics) << "length wasAngularVelocity:" << glm::length(wasAngularVelocity);
|
||||
qCDebug(physics) << "length _sentAngularVelocity:" << glm::length(_sentAngularVelocity);
|
||||
|
||||
qCDebug(physics) << "wasRotation:" << wasRotation;
|
||||
qCDebug(physics) << "bullet actualRotation:" << actualRotation;
|
||||
qCDebug(physics) << "_sentRotation:" << _sentRotation;
|
||||
}
|
||||
#endif
|
||||
|
||||
return (fabsf(glm::dot(actualRotation, _sentRotation)) < MIN_ROTATION_DOT);
|
||||
void ObjectMotionState::setMotionType(MotionType motionType) {
|
||||
_motionType = motionType;
|
||||
}
|
||||
|
||||
void ObjectMotionState::setRigidBody(btRigidBody* body) {
|
||||
// give the body a (void*) back-pointer to this ObjectMotionState
|
||||
if (_body != body) {
|
||||
if (_body) {
|
||||
_body->setUserPointer(NULL);
|
||||
_body->setUserPointer(nullptr);
|
||||
}
|
||||
_body = body;
|
||||
if (_body) {
|
||||
|
@ -225,7 +111,89 @@ void ObjectMotionState::setRigidBody(btRigidBody* body) {
|
|||
}
|
||||
}
|
||||
|
||||
void ObjectMotionState::setKinematic(bool kinematic, uint32_t substep) {
|
||||
_isKinematic = kinematic;
|
||||
_lastKinematicSubstep = substep;
|
||||
void ObjectMotionState::handleEasyChanges(uint32_t flags) {
|
||||
if (flags & EntityItem::DIRTY_POSITION) {
|
||||
btTransform worldTrans;
|
||||
if (flags & EntityItem::DIRTY_ROTATION) {
|
||||
worldTrans.setRotation(glmToBullet(getObjectRotation()));
|
||||
} else {
|
||||
worldTrans = _body->getWorldTransform();
|
||||
}
|
||||
worldTrans.setOrigin(glmToBullet(getObjectPosition()));
|
||||
_body->setWorldTransform(worldTrans);
|
||||
} else if (flags & EntityItem::DIRTY_ROTATION) {
|
||||
btTransform worldTrans = _body->getWorldTransform();
|
||||
worldTrans.setRotation(glmToBullet(getObjectRotation()));
|
||||
_body->setWorldTransform(worldTrans);
|
||||
}
|
||||
|
||||
if (flags & EntityItem::DIRTY_LINEAR_VELOCITY) {
|
||||
_body->setLinearVelocity(glmToBullet(getObjectLinearVelocity()));
|
||||
_body->setGravity(glmToBullet(getObjectGravity()));
|
||||
}
|
||||
if (flags & EntityItem::DIRTY_ANGULAR_VELOCITY) {
|
||||
_body->setAngularVelocity(glmToBullet(getObjectAngularVelocity()));
|
||||
}
|
||||
|
||||
if (flags & EntityItem::DIRTY_MATERIAL) {
|
||||
updateBodyMaterialProperties();
|
||||
}
|
||||
|
||||
if (flags & EntityItem::DIRTY_MASS) {
|
||||
float mass = getMass();
|
||||
btVector3 inertia(0.0f, 0.0f, 0.0f);
|
||||
_body->getCollisionShape()->calculateLocalInertia(mass, inertia);
|
||||
_body->setMassProps(mass, inertia);
|
||||
_body->updateInertiaTensor();
|
||||
}
|
||||
|
||||
if (flags & EntityItem::DIRTY_PHYSICS_ACTIVATION) {
|
||||
_body->activate();
|
||||
}
|
||||
}
|
||||
|
||||
void ObjectMotionState::handleHardAndEasyChanges(uint32_t flags, PhysicsEngine* engine) {
|
||||
if (flags & EntityItem::DIRTY_SHAPE) {
|
||||
// make sure the new shape is valid
|
||||
ShapeInfo shapeInfo;
|
||||
computeObjectShapeInfo(shapeInfo);
|
||||
btCollisionShape* newShape = getShapeManager()->getShape(shapeInfo);
|
||||
if (!newShape) {
|
||||
qCDebug(physics) << "Warning: failed to generate new shape!";
|
||||
// failed to generate new shape! --> keep old shape and remove shape-change flag
|
||||
flags &= ~EntityItem::DIRTY_SHAPE;
|
||||
// TODO: force this object out of PhysicsEngine rather than just use the old shape
|
||||
if ((flags & HARD_DIRTY_PHYSICS_FLAGS) == 0) {
|
||||
// no HARD flags remain, so do any EASY changes
|
||||
if (flags & EASY_DIRTY_PHYSICS_FLAGS) {
|
||||
handleEasyChanges(flags);
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
getShapeManager()->releaseShape(_shape);
|
||||
_shape = newShape;
|
||||
_body->setCollisionShape(_shape);
|
||||
if (flags & EASY_DIRTY_PHYSICS_FLAGS) {
|
||||
handleEasyChanges(flags);
|
||||
}
|
||||
} else {
|
||||
if (flags & EASY_DIRTY_PHYSICS_FLAGS) {
|
||||
handleEasyChanges(flags);
|
||||
}
|
||||
}
|
||||
engine->reinsertObject(this);
|
||||
}
|
||||
|
||||
void ObjectMotionState::updateBodyMaterialProperties() {
|
||||
_body->setRestitution(getObjectRestitution());
|
||||
_body->setFriction(getObjectFriction());
|
||||
_body->setDamping(fabsf(btMin(getObjectLinearDamping(), 1.0f)), fabsf(btMin(getObjectAngularDamping(), 1.0f)));
|
||||
}
|
||||
|
||||
void ObjectMotionState::updateBodyVelocities() {
|
||||
setBodyLinearVelocity(getObjectLinearVelocity());
|
||||
setBodyAngularVelocity(getObjectAngularVelocity());
|
||||
setBodyGravity(getObjectGravity());
|
||||
_body->setActivationState(ACTIVE_TAG);
|
||||
}
|
||||
|
|
|
@ -18,7 +18,7 @@
|
|||
#include <EntityItem.h>
|
||||
|
||||
#include "ContactInfo.h"
|
||||
#include "ShapeInfo.h"
|
||||
#include "ShapeManager.h"
|
||||
|
||||
enum MotionType {
|
||||
MOTION_TYPE_STATIC, // no motion
|
||||
|
@ -27,7 +27,7 @@ enum MotionType {
|
|||
};
|
||||
|
||||
enum MotionStateType {
|
||||
MOTION_STATE_TYPE_UNKNOWN,
|
||||
MOTION_STATE_TYPE_INVALID,
|
||||
MOTION_STATE_TYPE_ENTITY,
|
||||
MOTION_STATE_TYPE_AVATAR
|
||||
};
|
||||
|
@ -35,86 +35,71 @@ enum MotionStateType {
|
|||
// The update flags trigger two varieties of updates: "hard" which require the body to be pulled
|
||||
// and re-added to the physics engine and "easy" which just updates the body properties.
|
||||
const uint32_t HARD_DIRTY_PHYSICS_FLAGS = (uint32_t)(EntityItem::DIRTY_MOTION_TYPE | EntityItem::DIRTY_SHAPE);
|
||||
const uint32_t EASY_DIRTY_PHYSICS_FLAGS = (uint32_t)(EntityItem::DIRTY_POSITION | EntityItem::DIRTY_VELOCITY |
|
||||
EntityItem::DIRTY_MASS | EntityItem::DIRTY_COLLISION_GROUP | EntityItem::DIRTY_MATERIAL);
|
||||
const uint32_t EASY_DIRTY_PHYSICS_FLAGS = (uint32_t)(EntityItem::DIRTY_TRANSFORM | EntityItem::DIRTY_VELOCITIES |
|
||||
EntityItem::DIRTY_MASS | EntityItem::DIRTY_COLLISION_GROUP |
|
||||
EntityItem::DIRTY_MATERIAL | EntityItem::DIRTY_PHYSICS_ACTIVATION);
|
||||
|
||||
// These are the set of incoming flags that the PhysicsEngine needs to hear about:
|
||||
const uint32_t DIRTY_PHYSICS_FLAGS = HARD_DIRTY_PHYSICS_FLAGS | EASY_DIRTY_PHYSICS_FLAGS;
|
||||
|
||||
// These are the outgoing flags that the PhysicsEngine can affect:
|
||||
const uint32_t OUTGOING_DIRTY_PHYSICS_FLAGS = EntityItem::DIRTY_POSITION | EntityItem::DIRTY_VELOCITY;
|
||||
const uint32_t OUTGOING_DIRTY_PHYSICS_FLAGS = EntityItem::DIRTY_TRANSFORM | EntityItem::DIRTY_VELOCITIES;
|
||||
|
||||
|
||||
class OctreeEditPacketSender;
|
||||
class PhysicsEngine;
|
||||
|
||||
extern const int MAX_NUM_NON_MOVING_UPDATES;
|
||||
|
||||
class ObjectMotionState : public btMotionState {
|
||||
public:
|
||||
// The WorldOffset is used to keep the positions of objects in the simulation near the origin, to
|
||||
// reduce numerical error when computing vector differences. In other words: The EntityMotionState
|
||||
// class translates between the simulation-frame and the world-frame as known by the render pipeline,
|
||||
// various object trees, etc. The EntityMotionState class uses a static "worldOffset" to help in
|
||||
// the translations.
|
||||
// These poroperties of the PhysicsEngine are "global" within the context of all ObjectMotionStates
|
||||
// (assuming just one PhysicsEngine). They are cached as statics for fast calculations in the
|
||||
// ObjectMotionState context.
|
||||
static void setWorldOffset(const glm::vec3& offset);
|
||||
static const glm::vec3& getWorldOffset();
|
||||
|
||||
// The WorldSimulationStep is a cached copy of number of SubSteps of the simulation, used for local time measurements.
|
||||
static void setWorldSimulationStep(uint32_t step);
|
||||
static uint32_t getWorldSimulationStep();
|
||||
|
||||
ObjectMotionState();
|
||||
static void setShapeManager(ShapeManager* manager);
|
||||
static ShapeManager* getShapeManager();
|
||||
|
||||
ObjectMotionState(btCollisionShape* shape);
|
||||
~ObjectMotionState();
|
||||
|
||||
void measureBodyAcceleration();
|
||||
void resetMeasuredBodyAcceleration();
|
||||
virtual void handleEasyChanges(uint32_t flags);
|
||||
virtual void handleHardAndEasyChanges(uint32_t flags, PhysicsEngine* engine);
|
||||
|
||||
// An EASY update does not require the object to be removed and then reinserted into the PhysicsEngine
|
||||
virtual void updateBodyEasy(uint32_t flags, uint32_t frame) = 0;
|
||||
|
||||
virtual void updateBodyMaterialProperties() = 0;
|
||||
virtual void updateBodyVelocities() = 0;
|
||||
virtual void updateBodyMaterialProperties();
|
||||
virtual void updateBodyVelocities();
|
||||
|
||||
MotionStateType getType() const { return _type; }
|
||||
virtual MotionType getMotionType() const { return _motionType; }
|
||||
|
||||
virtual void computeObjectShapeInfo(ShapeInfo& info) = 0;
|
||||
virtual float computeObjectMass(const ShapeInfo& shapeInfo) const = 0;
|
||||
void setMass(float mass) { _mass = fabsf(mass); }
|
||||
float getMass() { return _mass; }
|
||||
|
||||
void setBodyVelocity(const glm::vec3& velocity) const;
|
||||
void setBodyLinearVelocity(const glm::vec3& velocity) const;
|
||||
void setBodyAngularVelocity(const glm::vec3& velocity) const;
|
||||
void setBodyGravity(const glm::vec3& gravity) const;
|
||||
|
||||
void getVelocity(glm::vec3& velocityOut) const;
|
||||
void getAngularVelocity(glm::vec3& angularVelocityOut) const;
|
||||
glm::vec3 getBodyLinearVelocity() const;
|
||||
glm::vec3 getBodyAngularVelocity() const;
|
||||
|
||||
virtual uint32_t getIncomingDirtyFlags() const = 0;
|
||||
virtual void clearIncomingDirtyFlags(uint32_t flags) = 0;
|
||||
void clearOutgoingPacketFlags(uint32_t flags) { _outgoingPacketFlags &= ~flags; }
|
||||
|
||||
bool doesNotNeedToSendUpdate() const;
|
||||
virtual bool shouldSendUpdate(uint32_t simulationStep);
|
||||
virtual void sendUpdate(OctreeEditPacketSender* packetSender, uint32_t frame) = 0;
|
||||
virtual uint32_t getAndClearIncomingDirtyFlags() const = 0;
|
||||
|
||||
virtual MotionType computeObjectMotionType() const = 0;
|
||||
virtual void computeObjectShapeInfo(ShapeInfo& shapeInfo) = 0;
|
||||
|
||||
virtual void updateKinematicState(uint32_t substep) = 0;
|
||||
|
||||
btCollisionShape* getShape() const { return _shape; }
|
||||
btRigidBody* getRigidBody() const { return _body; }
|
||||
|
||||
bool isKinematic() const { return _isKinematic; }
|
||||
|
||||
void setKinematic(bool kinematic, uint32_t substep);
|
||||
virtual void stepKinematicSimulation(quint64 now) = 0;
|
||||
void releaseShape();
|
||||
|
||||
virtual bool isMoving() const = 0;
|
||||
|
||||
friend class PhysicsEngine;
|
||||
|
||||
// these are here so we can call into EntityMotionObject with a base-class pointer
|
||||
virtual EntityItem* getEntity() const { return NULL; }
|
||||
virtual void setShouldClaimSimulationOwnership(bool value) { }
|
||||
virtual bool getShouldClaimSimulationOwnership() { return false; }
|
||||
|
||||
// These pure virtual methods must be implemented for each MotionState type
|
||||
// and make it possible to implement more complicated methods in this base class.
|
||||
|
||||
|
@ -123,39 +108,33 @@ public:
|
|||
virtual float getObjectLinearDamping() const = 0;
|
||||
virtual float getObjectAngularDamping() const = 0;
|
||||
|
||||
virtual const glm::vec3& getObjectPosition() const = 0;
|
||||
virtual glm::vec3 getObjectPosition() const = 0;
|
||||
virtual const glm::quat& getObjectRotation() const = 0;
|
||||
virtual const glm::vec3& getObjectLinearVelocity() const = 0;
|
||||
virtual const glm::vec3& getObjectAngularVelocity() const = 0;
|
||||
virtual const glm::vec3& getObjectGravity() const = 0;
|
||||
|
||||
virtual const QUuid& getObjectID() const = 0;
|
||||
|
||||
virtual QUuid getSimulatorID() const = 0;
|
||||
virtual void bump() = 0;
|
||||
|
||||
virtual QString getName() { return ""; }
|
||||
|
||||
friend class PhysicsEngine;
|
||||
|
||||
protected:
|
||||
virtual void setMotionType(MotionType motionType);
|
||||
void setRigidBody(btRigidBody* body);
|
||||
|
||||
MotionStateType _type = MOTION_STATE_TYPE_UNKNOWN; // type of MotionState
|
||||
|
||||
MotionStateType _type = MOTION_STATE_TYPE_INVALID; // type of MotionState
|
||||
MotionType _motionType; // type of motion: KINEMATIC, DYNAMIC, or STATIC
|
||||
|
||||
btCollisionShape* _shape;
|
||||
btRigidBody* _body;
|
||||
float _mass;
|
||||
|
||||
bool _isKinematic = false;
|
||||
uint32_t _lastKinematicSubstep = 0;
|
||||
|
||||
bool _sentMoving; // true if last update was moving
|
||||
int _numNonMovingUpdates; // RELIABLE_SEND_HACK for "not so reliable" resends of packets for non-moving objects
|
||||
|
||||
uint32_t _outgoingPacketFlags;
|
||||
uint32_t _sentStep;
|
||||
glm::vec3 _sentPosition; // in simulation-frame (not world-frame)
|
||||
glm::quat _sentRotation;;
|
||||
glm::vec3 _sentVelocity;
|
||||
glm::vec3 _sentAngularVelocity; // radians per second
|
||||
glm::vec3 _sentGravity;
|
||||
glm::vec3 _sentAcceleration;
|
||||
|
||||
uint32_t _lastSimulationStep;
|
||||
glm::vec3 _lastVelocity;
|
||||
glm::vec3 _measuredAcceleration;
|
||||
uint32_t _lastKinematicStep;
|
||||
};
|
||||
|
||||
#endif // hifi_ObjectMotionState_h
|
||||
|
|
233
libraries/physics/src/PhysicalEntitySimulation.cpp
Normal file
233
libraries/physics/src/PhysicalEntitySimulation.cpp
Normal file
|
@ -0,0 +1,233 @@
|
|||
//
|
||||
// PhysicalEntitySimulation.cpp
|
||||
// libraries/physcis/src
|
||||
//
|
||||
// Created by Andrew Meadows 2015.04.27
|
||||
// Copyright 2015 High Fidelity, Inc.
|
||||
//
|
||||
// Distributed under the Apache License, Version 2.0.
|
||||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
//
|
||||
|
||||
#include "EntityMotionState.h"
|
||||
#include "PhysicalEntitySimulation.h"
|
||||
#include "PhysicsHelpers.h"
|
||||
#include "PhysicsLogging.h"
|
||||
#include "ShapeInfoUtil.h"
|
||||
#include "ShapeManager.h"
|
||||
|
||||
PhysicalEntitySimulation::PhysicalEntitySimulation() {
|
||||
}
|
||||
|
||||
PhysicalEntitySimulation::~PhysicalEntitySimulation() {
|
||||
}
|
||||
|
||||
void PhysicalEntitySimulation::init(
|
||||
EntityTree* tree,
|
||||
PhysicsEngine* physicsEngine,
|
||||
ShapeManager* shapeManager,
|
||||
EntityEditPacketSender* packetSender) {
|
||||
assert(tree);
|
||||
setEntityTree(tree);
|
||||
|
||||
assert(physicsEngine);
|
||||
_physicsEngine = physicsEngine;
|
||||
|
||||
assert(shapeManager);
|
||||
_shapeManager = shapeManager;
|
||||
|
||||
assert(packetSender);
|
||||
_entityPacketSender = packetSender;
|
||||
}
|
||||
|
||||
// begin EntitySimulation overrides
|
||||
void PhysicalEntitySimulation::updateEntitiesInternal(const quint64& now) {
|
||||
// TODO: add back non-physical kinematic objects and step them forward here
|
||||
}
|
||||
|
||||
void PhysicalEntitySimulation::addEntityInternal(EntityItem* entity) {
|
||||
assert(entity);
|
||||
if (entity->shouldBePhysical()) {
|
||||
EntityMotionState* motionState = static_cast<EntityMotionState*>(entity->getPhysicsInfo());
|
||||
if (!motionState) {
|
||||
_pendingAdds.insert(entity);
|
||||
}
|
||||
} else if (entity->isMoving()) {
|
||||
_simpleKinematicEntities.insert(entity);
|
||||
}
|
||||
}
|
||||
|
||||
void PhysicalEntitySimulation::removeEntityInternal(EntityItem* entity) {
|
||||
EntityMotionState* motionState = static_cast<EntityMotionState*>(entity->getPhysicsInfo());
|
||||
if (motionState) {
|
||||
motionState->clearEntity();
|
||||
entity->setPhysicsInfo(nullptr);
|
||||
_pendingRemoves.insert(motionState);
|
||||
_outgoingChanges.remove(motionState);
|
||||
}
|
||||
_pendingAdds.remove(entity);
|
||||
}
|
||||
|
||||
void PhysicalEntitySimulation::changeEntityInternal(EntityItem* entity) {
|
||||
// queue incoming changes: from external sources (script, EntityServer, etc) to physics engine
|
||||
assert(entity);
|
||||
EntityMotionState* motionState = static_cast<EntityMotionState*>(entity->getPhysicsInfo());
|
||||
if (motionState) {
|
||||
if (!entity->shouldBePhysical()) {
|
||||
// the entity should be removed from the physical simulation
|
||||
_pendingChanges.remove(motionState);
|
||||
_physicalObjects.remove(motionState);
|
||||
_pendingRemoves.insert(motionState);
|
||||
_outgoingChanges.remove(motionState);
|
||||
if (entity->isMoving()) {
|
||||
_simpleKinematicEntities.insert(entity);
|
||||
}
|
||||
} else {
|
||||
_pendingChanges.insert(motionState);
|
||||
}
|
||||
} else if (entity->shouldBePhysical()) {
|
||||
// The intent is for this object to be in the PhysicsEngine, but it has no MotionState yet.
|
||||
// Perhaps it's shape has changed and it can now be added?
|
||||
_pendingAdds.insert(entity);
|
||||
_simpleKinematicEntities.remove(entity); // just in case it's non-physical-kinematic
|
||||
} else if (entity->isMoving()) {
|
||||
_simpleKinematicEntities.insert(entity);
|
||||
} else {
|
||||
_simpleKinematicEntities.remove(entity); // just in case it's non-physical-kinematic
|
||||
}
|
||||
}
|
||||
|
||||
void PhysicalEntitySimulation::clearEntitiesInternal() {
|
||||
// TODO: we should probably wait to lock the _physicsEngine so we don't mess up data structures
|
||||
// while it is in the middle of a simulation step. As it is, we're probably in shutdown mode
|
||||
// anyway, so maybe the simulation was already properly shutdown? Cross our fingers...
|
||||
|
||||
// first disconnect each MotionStates from its Entity
|
||||
for (auto stateItr : _physicalObjects) {
|
||||
EntityMotionState* motionState = static_cast<EntityMotionState*>(&(*stateItr));
|
||||
EntityItem* entity = motionState->getEntity();
|
||||
if (entity) {
|
||||
entity->setPhysicsInfo(nullptr);
|
||||
}
|
||||
motionState->clearEntity();
|
||||
}
|
||||
|
||||
// then delete the objects (aka MotionStates)
|
||||
_physicsEngine->deleteObjects(_physicalObjects);
|
||||
|
||||
// finally clear all lists (which now have only dangling pointers)
|
||||
_physicalObjects.clear();
|
||||
_pendingRemoves.clear();
|
||||
_pendingAdds.clear();
|
||||
_pendingChanges.clear();
|
||||
}
|
||||
// end EntitySimulation overrides
|
||||
|
||||
|
||||
VectorOfMotionStates& PhysicalEntitySimulation::getObjectsToDelete() {
|
||||
_tempVector.clear();
|
||||
for (auto stateItr : _pendingRemoves) {
|
||||
EntityMotionState* motionState = &(*stateItr);
|
||||
_pendingChanges.remove(motionState);
|
||||
_physicalObjects.remove(motionState);
|
||||
|
||||
EntityItem* entity = motionState->getEntity();
|
||||
if (entity) {
|
||||
_pendingAdds.remove(entity);
|
||||
entity->setPhysicsInfo(nullptr);
|
||||
motionState->clearEntity();
|
||||
}
|
||||
_tempVector.push_back(motionState);
|
||||
}
|
||||
_pendingRemoves.clear();
|
||||
return _tempVector;
|
||||
}
|
||||
|
||||
VectorOfMotionStates& PhysicalEntitySimulation::getObjectsToAdd() {
|
||||
_tempVector.clear();
|
||||
SetOfEntities::iterator entityItr = _pendingAdds.begin();
|
||||
while (entityItr != _pendingAdds.end()) {
|
||||
EntityItem* entity = *entityItr;
|
||||
assert(!entity->getPhysicsInfo());
|
||||
if (!entity->shouldBePhysical()) {
|
||||
// this entity should no longer be on the internal _pendingAdds
|
||||
entityItr = _pendingAdds.erase(entityItr);
|
||||
if (entity->isMoving()) {
|
||||
_simpleKinematicEntities.insert(entity);
|
||||
}
|
||||
} else if (entity->isReadyToComputeShape()) {
|
||||
ShapeInfo shapeInfo;
|
||||
entity->computeShapeInfo(shapeInfo);
|
||||
btCollisionShape* shape = _shapeManager->getShape(shapeInfo);
|
||||
if (shape) {
|
||||
EntityMotionState* motionState = new EntityMotionState(shape, entity);
|
||||
entity->setPhysicsInfo(static_cast<void*>(motionState));
|
||||
motionState->setMass(entity->computeMass());
|
||||
_physicalObjects.insert(motionState);
|
||||
_tempVector.push_back(motionState);
|
||||
entityItr = _pendingAdds.erase(entityItr);
|
||||
} else {
|
||||
qDebug() << "Warning! Failed to generate new shape for entity." << entity->getName();
|
||||
++entityItr;
|
||||
}
|
||||
} else {
|
||||
++entityItr;
|
||||
}
|
||||
}
|
||||
return _tempVector;
|
||||
}
|
||||
|
||||
VectorOfMotionStates& PhysicalEntitySimulation::getObjectsToChange() {
|
||||
_tempVector.clear();
|
||||
for (auto stateItr : _pendingChanges) {
|
||||
EntityMotionState* motionState = &(*stateItr);
|
||||
_tempVector.push_back(motionState);
|
||||
}
|
||||
_pendingChanges.clear();
|
||||
return _tempVector;
|
||||
}
|
||||
|
||||
void PhysicalEntitySimulation::handleOutgoingChanges(VectorOfMotionStates& motionStates) {
|
||||
// walk the motionStates looking for those that correspond to entities
|
||||
for (auto stateItr : motionStates) {
|
||||
ObjectMotionState* state = &(*stateItr);
|
||||
if (state && state->getType() == MOTION_STATE_TYPE_ENTITY) {
|
||||
EntityMotionState* entityState = static_cast<EntityMotionState*>(state);
|
||||
EntityItem* entity = entityState->getEntity();
|
||||
if (entity) {
|
||||
_outgoingChanges.insert(entityState);
|
||||
_entitiesToSort.insert(entityState->getEntity());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// send outgoing packets
|
||||
uint32_t numSubsteps = _physicsEngine->getNumSubsteps();
|
||||
if (_lastStepSendPackets != numSubsteps) {
|
||||
_lastStepSendPackets = numSubsteps;
|
||||
|
||||
QSet<EntityMotionState*>::iterator stateItr = _outgoingChanges.begin();
|
||||
while (stateItr != _outgoingChanges.end()) {
|
||||
EntityMotionState* state = *stateItr;
|
||||
if (state->doesNotNeedToSendUpdate()) {
|
||||
stateItr = _outgoingChanges.erase(stateItr);
|
||||
} else if (state->shouldSendUpdate(numSubsteps)) {
|
||||
state->sendUpdate(_entityPacketSender, numSubsteps);
|
||||
++stateItr;
|
||||
} else {
|
||||
++stateItr;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void PhysicalEntitySimulation::handleCollisionEvents(CollisionEvents& collisionEvents) {
|
||||
for (auto collision : collisionEvents) {
|
||||
// NOTE: The collision event is always aligned such that idA is never NULL.
|
||||
// however idB may be NULL.
|
||||
if (!collision.idB.isNull()) {
|
||||
emit entityCollisionWithEntity(collision.idA, collision.idB, collision);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
73
libraries/physics/src/PhysicalEntitySimulation.h
Normal file
73
libraries/physics/src/PhysicalEntitySimulation.h
Normal file
|
@ -0,0 +1,73 @@
|
|||
//
|
||||
// PhysicalEntitySimulation.h
|
||||
// libraries/physcis/src
|
||||
//
|
||||
// Created by Andrew Meadows 2015.04.27
|
||||
// Copyright 2015 High Fidelity, Inc.
|
||||
//
|
||||
// Distributed under the Apache License, Version 2.0.
|
||||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
//
|
||||
|
||||
#ifndef hifi_PhysicalEntitySimulation_h
|
||||
#define hifi_PhysicalEntitySimulation_h
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
#include <btBulletDynamicsCommon.h>
|
||||
#include <BulletCollision/CollisionDispatch/btGhostObject.h>
|
||||
|
||||
#include <EntityItem.h>
|
||||
#include <EntitySimulation.h>
|
||||
|
||||
#include "PhysicsEngine.h"
|
||||
#include "PhysicsTypedefs.h"
|
||||
|
||||
class EntityMotionState;
|
||||
class ShapeManager;
|
||||
|
||||
typedef QSet<EntityMotionState*> SetOfEntityMotionStates;
|
||||
|
||||
class PhysicalEntitySimulation :public EntitySimulation {
|
||||
public:
|
||||
PhysicalEntitySimulation();
|
||||
~PhysicalEntitySimulation();
|
||||
|
||||
void init(EntityTree* tree, PhysicsEngine* engine, ShapeManager* shapeManager, EntityEditPacketSender* packetSender);
|
||||
|
||||
protected: // only called by EntitySimulation
|
||||
// overrides for EntitySimulation
|
||||
virtual void updateEntitiesInternal(const quint64& now);
|
||||
virtual void addEntityInternal(EntityItem* entity);
|
||||
virtual void removeEntityInternal(EntityItem* entity);
|
||||
virtual void changeEntityInternal(EntityItem* entity);
|
||||
virtual void clearEntitiesInternal();
|
||||
|
||||
public:
|
||||
VectorOfMotionStates& getObjectsToDelete();
|
||||
VectorOfMotionStates& getObjectsToAdd();
|
||||
VectorOfMotionStates& getObjectsToChange();
|
||||
|
||||
void handleOutgoingChanges(VectorOfMotionStates& motionStates);
|
||||
void handleCollisionEvents(CollisionEvents& collisionEvents);
|
||||
|
||||
private:
|
||||
// incoming changes
|
||||
SetOfEntityMotionStates _pendingRemoves; // EntityMotionStates to be removed from PhysicsEngine (and deleted)
|
||||
SetOfEntities _pendingAdds; // entities to be be added to PhysicsEngine (and a their EntityMotionState created)
|
||||
SetOfEntityMotionStates _pendingChanges; // EntityMotionStates already in PhysicsEngine that need their physics changed
|
||||
|
||||
// outgoing changes
|
||||
SetOfEntityMotionStates _outgoingChanges; // EntityMotionStates for which we need to send updates to entity-server
|
||||
|
||||
SetOfMotionStates _physicalObjects; // MotionStates of entities in PhysicsEngine
|
||||
VectorOfMotionStates _tempVector; // temporary array reference, valid immediately after getObjectsToRemove() (and friends)
|
||||
|
||||
ShapeManager* _shapeManager = nullptr;
|
||||
PhysicsEngine* _physicsEngine = nullptr;
|
||||
EntityEditPacketSender* _entityPacketSender = nullptr;
|
||||
|
||||
uint32_t _lastStepSendPackets = 0;
|
||||
};
|
||||
|
||||
#endif // hifi_PhysicalEntitySimulation_h
|
|
@ -9,10 +9,8 @@
|
|||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
//
|
||||
|
||||
#include <AABox.h>
|
||||
|
||||
#include "ObjectMotionState.h"
|
||||
#include "PhysicsEngine.h"
|
||||
#include "ShapeInfoUtil.h"
|
||||
#include "PhysicsHelpers.h"
|
||||
#include "ThreadSafeDynamicsWorld.h"
|
||||
#include "PhysicsLogging.h"
|
||||
|
@ -26,12 +24,12 @@ uint32_t PhysicsEngine::getNumSubsteps() {
|
|||
|
||||
PhysicsEngine::PhysicsEngine(const glm::vec3& offset) :
|
||||
_originOffset(offset),
|
||||
_characterController(NULL) {
|
||||
_characterController(nullptr) {
|
||||
}
|
||||
|
||||
PhysicsEngine::~PhysicsEngine() {
|
||||
if (_characterController) {
|
||||
_characterController->setDynamicsWorld(NULL);
|
||||
_characterController->setDynamicsWorld(nullptr);
|
||||
}
|
||||
// TODO: delete engine components... if we ever plan to create more than one instance
|
||||
delete _collisionConfig;
|
||||
|
@ -42,217 +40,153 @@ PhysicsEngine::~PhysicsEngine() {
|
|||
delete _ghostPairCallback;
|
||||
}
|
||||
|
||||
// begin EntitySimulation overrides
|
||||
void PhysicsEngine::updateEntitiesInternal(const quint64& now) {
|
||||
// no need to send updates unless the physics simulation has actually stepped
|
||||
if (_lastNumSubstepsAtUpdateInternal != _numSubsteps) {
|
||||
_lastNumSubstepsAtUpdateInternal = _numSubsteps;
|
||||
// NOTE: the grand order of operations is:
|
||||
// (1) relay incoming changes
|
||||
// (2) step simulation
|
||||
// (3) synchronize outgoing motion states
|
||||
// (4) send outgoing packets
|
||||
|
||||
// this is step (4)
|
||||
QSet<ObjectMotionState*>::iterator stateItr = _outgoingPackets.begin();
|
||||
while (stateItr != _outgoingPackets.end()) {
|
||||
ObjectMotionState* state = *stateItr;
|
||||
if (state->doesNotNeedToSendUpdate()) {
|
||||
stateItr = _outgoingPackets.erase(stateItr);
|
||||
} else if (state->shouldSendUpdate(_numSubsteps)) {
|
||||
state->sendUpdate(_entityPacketSender, _numSubsteps);
|
||||
++stateItr;
|
||||
void PhysicsEngine::init() {
|
||||
if (!_dynamicsWorld) {
|
||||
_collisionConfig = new btDefaultCollisionConfiguration();
|
||||
_collisionDispatcher = new btCollisionDispatcher(_collisionConfig);
|
||||
_broadphaseFilter = new btDbvtBroadphase();
|
||||
_constraintSolver = new btSequentialImpulseConstraintSolver;
|
||||
_dynamicsWorld = new ThreadSafeDynamicsWorld(_collisionDispatcher, _broadphaseFilter, _constraintSolver, _collisionConfig);
|
||||
|
||||
_ghostPairCallback = new btGhostPairCallback();
|
||||
_dynamicsWorld->getPairCache()->setInternalGhostPairCallback(_ghostPairCallback);
|
||||
|
||||
// default gravity of the world is zero, so each object must specify its own gravity
|
||||
// TODO: set up gravity zones
|
||||
_dynamicsWorld->setGravity(btVector3(0.0f, 0.0f, 0.0f));
|
||||
}
|
||||
}
|
||||
|
||||
void PhysicsEngine::addObject(ObjectMotionState* motionState) {
|
||||
assert(motionState);
|
||||
|
||||
btVector3 inertia(0.0f, 0.0f, 0.0f);
|
||||
float mass = 0.0f;
|
||||
// NOTE: the body may or may not already exist, depending on whether this corresponds to a reinsertion, or a new insertion.
|
||||
btRigidBody* body = motionState->getRigidBody();
|
||||
MotionType motionType = motionState->computeObjectMotionType();
|
||||
motionState->setMotionType(motionType);
|
||||
switch(motionType) {
|
||||
case MOTION_TYPE_KINEMATIC: {
|
||||
if (!body) {
|
||||
btCollisionShape* shape = motionState->getShape();
|
||||
assert(shape);
|
||||
body = new btRigidBody(mass, motionState, shape, inertia);
|
||||
} else {
|
||||
++stateItr;
|
||||
body->setMassProps(mass, inertia);
|
||||
}
|
||||
body->setCollisionFlags(btCollisionObject::CF_KINEMATIC_OBJECT);
|
||||
body->updateInertiaTensor();
|
||||
motionState->setRigidBody(body);
|
||||
motionState->updateBodyVelocities();
|
||||
const float KINEMATIC_LINEAR_VELOCITY_THRESHOLD = 0.01f; // 1 cm/sec
|
||||
const float KINEMATIC_ANGULAR_VELOCITY_THRESHOLD = 0.01f; // ~1 deg/sec
|
||||
body->setSleepingThresholds(KINEMATIC_LINEAR_VELOCITY_THRESHOLD, KINEMATIC_ANGULAR_VELOCITY_THRESHOLD);
|
||||
break;
|
||||
}
|
||||
case MOTION_TYPE_DYNAMIC: {
|
||||
mass = motionState->getMass();
|
||||
btCollisionShape* shape = motionState->getShape();
|
||||
assert(shape);
|
||||
shape->calculateLocalInertia(mass, inertia);
|
||||
if (!body) {
|
||||
body = new btRigidBody(mass, motionState, shape, inertia);
|
||||
} else {
|
||||
body->setMassProps(mass, inertia);
|
||||
}
|
||||
body->updateInertiaTensor();
|
||||
motionState->setRigidBody(body);
|
||||
motionState->updateBodyVelocities();
|
||||
// NOTE: Bullet will deactivate any object whose velocity is below these thresholds for longer than 2 seconds.
|
||||
// (the 2 seconds is determined by: static btRigidBody::gDeactivationTime
|
||||
const float DYNAMIC_LINEAR_VELOCITY_THRESHOLD = 0.05f; // 5 cm/sec
|
||||
const float DYNAMIC_ANGULAR_VELOCITY_THRESHOLD = 0.087266f; // ~5 deg/sec
|
||||
body->setSleepingThresholds(DYNAMIC_LINEAR_VELOCITY_THRESHOLD, DYNAMIC_ANGULAR_VELOCITY_THRESHOLD);
|
||||
if (!motionState->isMoving()) {
|
||||
// try to initialize this object as inactive
|
||||
body->forceActivationState(ISLAND_SLEEPING);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case MOTION_TYPE_STATIC:
|
||||
default: {
|
||||
if (!body) {
|
||||
assert(motionState->getShape());
|
||||
body = new btRigidBody(mass, motionState, motionState->getShape(), inertia);
|
||||
} else {
|
||||
body->setMassProps(mass, inertia);
|
||||
}
|
||||
body->setCollisionFlags(btCollisionObject::CF_STATIC_OBJECT);
|
||||
body->updateInertiaTensor();
|
||||
motionState->setRigidBody(body);
|
||||
break;
|
||||
}
|
||||
}
|
||||
body->setFlags(BT_DISABLE_WORLD_GRAVITY);
|
||||
motionState->updateBodyMaterialProperties();
|
||||
|
||||
_dynamicsWorld->addRigidBody(body);
|
||||
|
||||
motionState->getAndClearIncomingDirtyFlags();
|
||||
}
|
||||
|
||||
void PhysicsEngine::removeObject(ObjectMotionState* object) {
|
||||
// wake up anything touching this object
|
||||
bump(object);
|
||||
removeContacts(object);
|
||||
|
||||
btRigidBody* body = object->getRigidBody();
|
||||
assert(body);
|
||||
_dynamicsWorld->removeRigidBody(body);
|
||||
}
|
||||
|
||||
void PhysicsEngine::deleteObjects(VectorOfMotionStates& objects) {
|
||||
for (auto object : objects) {
|
||||
removeObject(object);
|
||||
|
||||
// NOTE: setRigidBody() modifies body->m_userPointer so we should clear the MotionState's body BEFORE deleting it.
|
||||
btRigidBody* body = object->getRigidBody();
|
||||
object->setRigidBody(nullptr);
|
||||
delete body;
|
||||
object->releaseShape();
|
||||
delete object;
|
||||
}
|
||||
}
|
||||
|
||||
// Same as above, but takes a Set instead of a Vector and ommits some cleanup operations. Only called during teardown.
|
||||
void PhysicsEngine::deleteObjects(SetOfMotionStates& objects) {
|
||||
for (auto object : objects) {
|
||||
btRigidBody* body = object->getRigidBody();
|
||||
_dynamicsWorld->removeRigidBody(body);
|
||||
|
||||
// NOTE: setRigidBody() modifies body->m_userPointer so we should clear the MotionState's body BEFORE deleting it.
|
||||
object->setRigidBody(nullptr);
|
||||
delete body;
|
||||
object->releaseShape();
|
||||
delete object;
|
||||
}
|
||||
}
|
||||
|
||||
void PhysicsEngine::addObjects(VectorOfMotionStates& objects) {
|
||||
for (auto object : objects) {
|
||||
addObject(object);
|
||||
}
|
||||
}
|
||||
|
||||
void PhysicsEngine::changeObjects(VectorOfMotionStates& objects) {
|
||||
for (auto object : objects) {
|
||||
uint32_t flags = object->getAndClearIncomingDirtyFlags() & DIRTY_PHYSICS_FLAGS;
|
||||
if (flags & HARD_DIRTY_PHYSICS_FLAGS) {
|
||||
object->handleHardAndEasyChanges(flags, this);
|
||||
} else if (flags & EASY_DIRTY_PHYSICS_FLAGS) {
|
||||
object->handleEasyChanges(flags);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void PhysicsEngine::addEntityInternal(EntityItem* entity) {
|
||||
assert(entity);
|
||||
void* physicsInfo = entity->getPhysicsInfo();
|
||||
if (!physicsInfo) {
|
||||
if (entity->isReadyToComputeShape()) {
|
||||
ShapeInfo shapeInfo;
|
||||
entity->computeShapeInfo(shapeInfo);
|
||||
btCollisionShape* shape = _shapeManager.getShape(shapeInfo);
|
||||
if (shape) {
|
||||
EntityMotionState* motionState = new EntityMotionState(entity);
|
||||
entity->setPhysicsInfo(static_cast<void*>(motionState));
|
||||
_entityMotionStates.insert(motionState);
|
||||
addObject(shapeInfo, shape, motionState);
|
||||
} else if (entity->isMoving()) {
|
||||
EntityMotionState* motionState = new EntityMotionState(entity);
|
||||
entity->setPhysicsInfo(static_cast<void*>(motionState));
|
||||
_entityMotionStates.insert(motionState);
|
||||
|
||||
motionState->setKinematic(true, _numSubsteps);
|
||||
_nonPhysicalKinematicObjects.insert(motionState);
|
||||
// We failed to add the entity to the simulation. Probably because we couldn't create a shape for it.
|
||||
//qCDebug(physics) << "failed to add entity " << entity->getEntityItemID() << " to physics engine";
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void PhysicsEngine::removeEntityInternal(EntityItem* entity) {
|
||||
assert(entity);
|
||||
void* physicsInfo = entity->getPhysicsInfo();
|
||||
if (physicsInfo) {
|
||||
EntityMotionState* motionState = static_cast<EntityMotionState*>(physicsInfo);
|
||||
if (motionState->getRigidBody()) {
|
||||
removeObjectFromBullet(motionState);
|
||||
} else {
|
||||
// only need to hunt in this list when there is no RigidBody
|
||||
_nonPhysicalKinematicObjects.remove(motionState);
|
||||
}
|
||||
_entityMotionStates.remove(motionState);
|
||||
_incomingChanges.remove(motionState);
|
||||
_outgoingPackets.remove(motionState);
|
||||
// NOTE: EntityMotionState dtor will remove its backpointer from EntityItem
|
||||
delete motionState;
|
||||
}
|
||||
}
|
||||
|
||||
void PhysicsEngine::entityChangedInternal(EntityItem* entity) {
|
||||
// queue incoming changes: from external sources (script, EntityServer, etc) to physics engine
|
||||
assert(entity);
|
||||
void* physicsInfo = entity->getPhysicsInfo();
|
||||
if (physicsInfo) {
|
||||
if ((entity->getDirtyFlags() & (HARD_DIRTY_PHYSICS_FLAGS | EASY_DIRTY_PHYSICS_FLAGS)) > 0) {
|
||||
ObjectMotionState* motionState = static_cast<ObjectMotionState*>(physicsInfo);
|
||||
_incomingChanges.insert(motionState);
|
||||
}
|
||||
} else {
|
||||
// try to add this entity again (maybe something changed such that it will work this time)
|
||||
addEntity(entity);
|
||||
}
|
||||
}
|
||||
|
||||
void PhysicsEngine::sortEntitiesThatMovedInternal() {
|
||||
// entities that have been simulated forward (hence in the _entitiesToBeSorted list)
|
||||
// also need to be put in the outgoingPackets list
|
||||
QSet<EntityItem*>::iterator entityItr = _entitiesToBeSorted.begin();
|
||||
while (entityItr != _entitiesToBeSorted.end()) {
|
||||
EntityItem* entity = *entityItr;
|
||||
void* physicsInfo = entity->getPhysicsInfo();
|
||||
assert(physicsInfo);
|
||||
ObjectMotionState* motionState = static_cast<ObjectMotionState*>(physicsInfo);
|
||||
_outgoingPackets.insert(motionState);
|
||||
++entityItr;
|
||||
}
|
||||
}
|
||||
|
||||
void PhysicsEngine::clearEntitiesInternal() {
|
||||
// For now we assume this would only be called on shutdown in which case we can just let the memory get lost.
|
||||
QSet<EntityMotionState*>::const_iterator stateItr = _entityMotionStates.begin();
|
||||
for (stateItr = _entityMotionStates.begin(); stateItr != _entityMotionStates.end(); ++stateItr) {
|
||||
removeObjectFromBullet(*stateItr);
|
||||
delete (*stateItr);
|
||||
}
|
||||
_entityMotionStates.clear();
|
||||
_nonPhysicalKinematicObjects.clear();
|
||||
_incomingChanges.clear();
|
||||
_outgoingPackets.clear();
|
||||
}
|
||||
// end EntitySimulation overrides
|
||||
|
||||
void PhysicsEngine::relayIncomingChangesToSimulation() {
|
||||
BT_PROFILE("incomingChanges");
|
||||
// process incoming changes
|
||||
QSet<ObjectMotionState*>::iterator stateItr = _incomingChanges.begin();
|
||||
while (stateItr != _incomingChanges.end()) {
|
||||
ObjectMotionState* motionState = *stateItr;
|
||||
++stateItr;
|
||||
uint32_t flags = motionState->getIncomingDirtyFlags() & DIRTY_PHYSICS_FLAGS;
|
||||
|
||||
bool removeMotionState = false;
|
||||
btRigidBody* body = motionState->getRigidBody();
|
||||
if (body) {
|
||||
if (flags & HARD_DIRTY_PHYSICS_FLAGS) {
|
||||
// a HARD update requires the body be pulled out of physics engine, changed, then reinserted
|
||||
// but it also handles all EASY changes
|
||||
bool success = updateBodyHard(body, motionState, flags);
|
||||
if (!success) {
|
||||
// NOTE: since updateBodyHard() failed we know that motionState has been removed
|
||||
// from simulation and body has been deleted. Depending on what else has changed
|
||||
// we might need to remove motionState altogether...
|
||||
if (flags & EntityItem::DIRTY_VELOCITY) {
|
||||
motionState->updateKinematicState(_numSubsteps);
|
||||
if (motionState->isKinematic()) {
|
||||
// all is NOT lost, we still need to move this object around kinematically
|
||||
_nonPhysicalKinematicObjects.insert(motionState);
|
||||
} else {
|
||||
// no need to keep motionState around
|
||||
removeMotionState = true;
|
||||
}
|
||||
} else {
|
||||
// no need to keep motionState around
|
||||
removeMotionState = true;
|
||||
}
|
||||
}
|
||||
} else if (flags) {
|
||||
// an EASY update does NOT require that the body be pulled out of physics engine
|
||||
// hence the MotionState has all the knowledge and authority to perform the update.
|
||||
motionState->updateBodyEasy(flags, _numSubsteps);
|
||||
}
|
||||
if (flags & (EntityItem::DIRTY_POSITION | EntityItem::DIRTY_VELOCITY)) {
|
||||
motionState->resetMeasuredBodyAcceleration();
|
||||
}
|
||||
} else {
|
||||
// the only way we should ever get here (motionState exists but no body) is when the object
|
||||
// is undergoing non-physical kinematic motion.
|
||||
assert(_nonPhysicalKinematicObjects.contains(motionState));
|
||||
|
||||
// it is possible that the changes are such that the object can now be added to the physical simulation
|
||||
if (flags & EntityItem::DIRTY_SHAPE) {
|
||||
ShapeInfo shapeInfo;
|
||||
motionState->computeObjectShapeInfo(shapeInfo);
|
||||
btCollisionShape* shape = _shapeManager.getShape(shapeInfo);
|
||||
if (shape) {
|
||||
addObject(shapeInfo, shape, motionState);
|
||||
_nonPhysicalKinematicObjects.remove(motionState);
|
||||
} else if (flags & EntityItem::DIRTY_VELOCITY) {
|
||||
// although we couldn't add the object to the simulation, might need to update kinematic motion...
|
||||
motionState->updateKinematicState(_numSubsteps);
|
||||
if (!motionState->isKinematic()) {
|
||||
_nonPhysicalKinematicObjects.remove(motionState);
|
||||
removeMotionState = true;
|
||||
}
|
||||
}
|
||||
} else if (flags & EntityItem::DIRTY_VELOCITY) {
|
||||
// although we still can't add to physics simulation, might need to update kinematic motion...
|
||||
motionState->updateKinematicState(_numSubsteps);
|
||||
if (!motionState->isKinematic()) {
|
||||
_nonPhysicalKinematicObjects.remove(motionState);
|
||||
removeMotionState = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (removeMotionState) {
|
||||
// if we get here then there is no need to keep this motionState around (no physics or kinematics)
|
||||
_outgoingPackets.remove(motionState);
|
||||
if (motionState->getType() == MOTION_STATE_TYPE_ENTITY) {
|
||||
_entityMotionStates.remove(static_cast<EntityMotionState*>(motionState));
|
||||
}
|
||||
// NOTE: motionState will clean up its own backpointers in the Object
|
||||
delete motionState;
|
||||
continue;
|
||||
}
|
||||
|
||||
// NOTE: the grand order of operations is:
|
||||
// (1) relay incoming changes
|
||||
// (2) step simulation
|
||||
// (3) synchronize outgoing motion states
|
||||
// (4) send outgoing packets
|
||||
//
|
||||
// We're in the middle of step (1) hence incoming changes should trump corresponding
|
||||
// outgoing changes at this point.
|
||||
motionState->clearOutgoingPacketFlags(flags); // clear outgoing flags that were trumped
|
||||
motionState->clearIncomingDirtyFlags(flags); // clear incoming flags that were processed
|
||||
}
|
||||
_incomingChanges.clear();
|
||||
void PhysicsEngine::reinsertObject(ObjectMotionState* object) {
|
||||
removeObject(object);
|
||||
addObject(object);
|
||||
}
|
||||
|
||||
void PhysicsEngine::removeContacts(ObjectMotionState* motionState) {
|
||||
|
@ -269,33 +203,7 @@ void PhysicsEngine::removeContacts(ObjectMotionState* motionState) {
|
|||
}
|
||||
}
|
||||
|
||||
// virtual
|
||||
void PhysicsEngine::init(EntityEditPacketSender* packetSender) {
|
||||
// _entityTree should be set prior to the init() call
|
||||
assert(_entityTree);
|
||||
|
||||
if (!_dynamicsWorld) {
|
||||
_collisionConfig = new btDefaultCollisionConfiguration();
|
||||
_collisionDispatcher = new btCollisionDispatcher(_collisionConfig);
|
||||
_broadphaseFilter = new btDbvtBroadphase();
|
||||
_constraintSolver = new btSequentialImpulseConstraintSolver;
|
||||
_dynamicsWorld = new ThreadSafeDynamicsWorld(_collisionDispatcher, _broadphaseFilter, _constraintSolver, _collisionConfig);
|
||||
|
||||
_ghostPairCallback = new btGhostPairCallback();
|
||||
_dynamicsWorld->getPairCache()->setInternalGhostPairCallback(_ghostPairCallback);
|
||||
|
||||
// default gravity of the world is zero, so each object must specify its own gravity
|
||||
// TODO: set up gravity zones
|
||||
_dynamicsWorld->setGravity(btVector3(0.0f, 0.0f, 0.0f));
|
||||
}
|
||||
|
||||
assert(packetSender);
|
||||
_entityPacketSender = packetSender;
|
||||
EntityMotionState::setOutgoingEntityList(&_entitiesToBeSorted);
|
||||
}
|
||||
|
||||
void PhysicsEngine::stepSimulation() {
|
||||
lock();
|
||||
CProfileManager::Reset();
|
||||
BT_PROFILE("stepSimulation");
|
||||
// NOTE: the grand order of operations is:
|
||||
|
@ -304,9 +212,6 @@ void PhysicsEngine::stepSimulation() {
|
|||
// (3) synchronize outgoing motion states
|
||||
// (4) send outgoing packets
|
||||
|
||||
// This is step (1) pull incoming changes
|
||||
relayIncomingChangesToSimulation();
|
||||
|
||||
const int MAX_NUM_SUBSTEPS = 4;
|
||||
const float MAX_TIMESTEP = (float)MAX_NUM_SUBSTEPS * PHYSICS_ENGINE_FIXED_SUBSTEP;
|
||||
float dt = 1.0e-6f * (float)(_clock.getTimeMicroseconds());
|
||||
|
@ -316,7 +221,7 @@ void PhysicsEngine::stepSimulation() {
|
|||
// TODO: move character->preSimulation() into relayIncomingChanges
|
||||
if (_characterController) {
|
||||
if (_characterController->needsRemoval()) {
|
||||
_characterController->setDynamicsWorld(NULL);
|
||||
_characterController->setDynamicsWorld(nullptr);
|
||||
}
|
||||
_characterController->updateShapeIfNecessary();
|
||||
if (_characterController->needsAddition()) {
|
||||
|
@ -325,92 +230,49 @@ void PhysicsEngine::stepSimulation() {
|
|||
_characterController->preSimulation(timeStep);
|
||||
}
|
||||
|
||||
// This is step (2) step simulation
|
||||
int numSubsteps = _dynamicsWorld->stepSimulation(timeStep, MAX_NUM_SUBSTEPS, PHYSICS_ENGINE_FIXED_SUBSTEP);
|
||||
_numSubsteps += (uint32_t)numSubsteps;
|
||||
stepNonPhysicalKinematics(usecTimestampNow());
|
||||
unlock();
|
||||
|
||||
// TODO: make all of this harvest stuff into one function: relayOutgoingChanges()
|
||||
if (numSubsteps > 0) {
|
||||
BT_PROFILE("postSimulation");
|
||||
// This is step (3) which is done outside of stepSimulation() so we can lock _entityTree.
|
||||
//
|
||||
// Unfortunately we have to unlock the simulation (above) before we try to lock the _entityTree
|
||||
// to avoid deadlock -- the _entityTree may try to lock its EntitySimulation (from which this
|
||||
// PhysicsEngine derives) when updating/adding/deleting entities so we need to wait for our own
|
||||
// lock on the tree before we re-lock ourselves.
|
||||
//
|
||||
// TODO: untangle these lock sequences.
|
||||
_numSubsteps += (uint32_t)numSubsteps;
|
||||
ObjectMotionState::setWorldSimulationStep(_numSubsteps);
|
||||
_entityTree->lockForWrite();
|
||||
lock();
|
||||
_dynamicsWorld->synchronizeMotionStates();
|
||||
|
||||
if (_characterController) {
|
||||
_characterController->postSimulation();
|
||||
}
|
||||
|
||||
computeCollisionEvents();
|
||||
|
||||
unlock();
|
||||
_entityTree->unlock();
|
||||
updateContactMap();
|
||||
_hasOutgoingChanges = true;
|
||||
}
|
||||
}
|
||||
|
||||
void PhysicsEngine::stepNonPhysicalKinematics(const quint64& now) {
|
||||
BT_PROFILE("nonPhysicalKinematics");
|
||||
QSet<ObjectMotionState*>::iterator stateItr = _nonPhysicalKinematicObjects.begin();
|
||||
// TODO?: need to occasionally scan for stopped non-physical kinematics objects
|
||||
while (stateItr != _nonPhysicalKinematicObjects.end()) {
|
||||
ObjectMotionState* motionState = *stateItr;
|
||||
motionState->stepKinematicSimulation(now);
|
||||
++stateItr;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void PhysicsEngine::doOwnershipInfection(const btCollisionObject* objectA, const btCollisionObject* objectB) {
|
||||
assert(objectA);
|
||||
assert(objectB);
|
||||
|
||||
auto nodeList = DependencyManager::get<NodeList>();
|
||||
QUuid myNodeID = nodeList->getSessionUUID();
|
||||
|
||||
if (myNodeID.isNull()) {
|
||||
BT_PROFILE("ownershipInfection");
|
||||
if (_sessionID.isNull()) {
|
||||
return;
|
||||
}
|
||||
|
||||
const btCollisionObject* characterCollisionObject =
|
||||
_characterController ? _characterController->getCollisionObject() : NULL;
|
||||
const btCollisionObject* characterObject = _characterController ? _characterController->getCollisionObject() : nullptr;
|
||||
|
||||
ObjectMotionState* a = static_cast<ObjectMotionState*>(objectA->getUserPointer());
|
||||
ObjectMotionState* b = static_cast<ObjectMotionState*>(objectB->getUserPointer());
|
||||
EntityItem* entityA = a ? a->getEntity() : NULL;
|
||||
EntityItem* entityB = b ? b->getEntity() : NULL;
|
||||
bool aIsDynamic = entityA && !objectA->isStaticOrKinematicObject();
|
||||
bool bIsDynamic = entityB && !objectB->isStaticOrKinematicObject();
|
||||
|
||||
// collisions cause infectious spread of simulation-ownership. we also attempt to take
|
||||
// ownership of anything that collides with our avatar.
|
||||
if ((aIsDynamic && (entityA->getSimulatorID() == myNodeID)) ||
|
||||
// (a && a->getShouldClaimSimulationOwnership()) ||
|
||||
(objectA == characterCollisionObject)) {
|
||||
if (bIsDynamic) {
|
||||
b->setShouldClaimSimulationOwnership(true);
|
||||
if (b && ((a && a->getSimulatorID() == _sessionID && !objectA->isStaticObject()) || (objectA == characterObject))) {
|
||||
// NOTE: we might own the simulation of a kinematic object (A)
|
||||
// but we don't claim ownership of kinematic objects (B) based on collisions here.
|
||||
if (!objectB->isStaticOrKinematicObject()) {
|
||||
b->bump();
|
||||
}
|
||||
} else if ((bIsDynamic && (entityB->getSimulatorID() == myNodeID)) ||
|
||||
// (b && b->getShouldClaimSimulationOwnership()) ||
|
||||
(objectB == characterCollisionObject)) {
|
||||
if (aIsDynamic) {
|
||||
a->setShouldClaimSimulationOwnership(true);
|
||||
} else if (a && ((b && b->getSimulatorID() == _sessionID && !objectB->isStaticObject()) || (objectB == characterObject))) {
|
||||
// SIMILARLY: we might own the simulation of a kinematic object (B)
|
||||
// but we don't claim ownership of kinematic objects (A) based on collisions here.
|
||||
if (!objectA->isStaticOrKinematicObject()) {
|
||||
a->bump();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void PhysicsEngine::computeCollisionEvents() {
|
||||
BT_PROFILE("computeCollisionEvents");
|
||||
void PhysicsEngine::updateContactMap() {
|
||||
BT_PROFILE("updateContactMap");
|
||||
++_numContactFrames;
|
||||
|
||||
// update all contacts every frame
|
||||
int numManifolds = _collisionDispatcher->getNumManifolds();
|
||||
|
@ -432,39 +294,44 @@ void PhysicsEngine::computeCollisionEvents() {
|
|||
ObjectMotionState* b = static_cast<ObjectMotionState*>(objectB->getUserPointer());
|
||||
if (a || b) {
|
||||
// the manifold has up to 4 distinct points, but only extract info from the first
|
||||
_contactMap[ContactKey(a, b)].update(_numContactFrames, contactManifold->getContactPoint(0), _originOffset);
|
||||
_contactMap[ContactKey(a, b)].update(_numContactFrames, contactManifold->getContactPoint(0));
|
||||
}
|
||||
|
||||
doOwnershipInfection(objectA, objectB);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
CollisionEvents& PhysicsEngine::getCollisionEvents() {
|
||||
const uint32_t CONTINUE_EVENT_FILTER_FREQUENCY = 10;
|
||||
_collisionEvents.clear();
|
||||
|
||||
// scan known contacts and trigger events
|
||||
ContactMap::iterator contactItr = _contactMap.begin();
|
||||
|
||||
while (contactItr != _contactMap.end()) {
|
||||
ObjectMotionState* A = static_cast<ObjectMotionState*>(contactItr->first._a);
|
||||
ObjectMotionState* B = static_cast<ObjectMotionState*>(contactItr->first._b);
|
||||
ContactInfo& contact = contactItr->second;
|
||||
ContactEventType type = contact.computeType(_numContactFrames);
|
||||
if(type != CONTACT_EVENT_TYPE_CONTINUE || _numSubsteps % CONTINUE_EVENT_FILTER_FREQUENCY == 0) {
|
||||
ObjectMotionState* A = static_cast<ObjectMotionState*>(contactItr->first._a);
|
||||
ObjectMotionState* B = static_cast<ObjectMotionState*>(contactItr->first._b);
|
||||
|
||||
// TODO: make triggering these events clean and efficient. The code at this context shouldn't
|
||||
// have to figure out what kind of object (entity, avatar, etc) these are in order to properly
|
||||
// emit a collision event.
|
||||
// TODO: enable scripts to filter based on contact event type
|
||||
ContactEventType type = contactItr->second.computeType(_numContactFrames);
|
||||
if(type != CONTACT_EVENT_TYPE_CONTINUE || _numSubsteps % CONTINUE_EVENT_FILTER_FREQUENCY == 0){
|
||||
if (A && A->getType() == MOTION_STATE_TYPE_ENTITY) {
|
||||
EntityItemID idA = static_cast<EntityMotionState*>(A)->getEntity()->getEntityItemID();
|
||||
EntityItemID idB;
|
||||
QUuid idA = A->getObjectID();
|
||||
QUuid idB;
|
||||
if (B && B->getType() == MOTION_STATE_TYPE_ENTITY) {
|
||||
idB = static_cast<EntityMotionState*>(B)->getEntity()->getEntityItemID();
|
||||
idB = B->getObjectID();
|
||||
}
|
||||
emit entityCollisionWithEntity(idA, idB, contactItr->second);
|
||||
glm::vec3 position = bulletToGLM(contact.getPositionWorldOnB()) + _originOffset;
|
||||
glm::vec3 penetration = bulletToGLM(contact.distance * contact.normalWorldOnB);
|
||||
_collisionEvents.push_back(Collision(type, idA, idB, position, penetration));
|
||||
} else if (B && B->getType() == MOTION_STATE_TYPE_ENTITY) {
|
||||
EntityItemID idA;
|
||||
EntityItemID idB = static_cast<EntityMotionState*>(B)->getEntity()->getEntityItemID();
|
||||
emit entityCollisionWithEntity(idA, idB, contactItr->second);
|
||||
QUuid idB = B->getObjectID();
|
||||
glm::vec3 position = bulletToGLM(contact.getPositionWorldOnA()) + _originOffset;
|
||||
// NOTE: we're flipping the order of A and B (so that the first objectID is never NULL)
|
||||
// hence we must negate the penetration.
|
||||
glm::vec3 penetration = - bulletToGLM(contact.distance * contact.normalWorldOnB);
|
||||
_collisionEvents.push_back(Collision(type, idB, QUuid(), position, penetration));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -476,7 +343,14 @@ void PhysicsEngine::computeCollisionEvents() {
|
|||
++contactItr;
|
||||
}
|
||||
}
|
||||
++_numContactFrames;
|
||||
return _collisionEvents;
|
||||
}
|
||||
|
||||
VectorOfMotionStates& PhysicsEngine::getOutgoingChanges() {
|
||||
BT_PROFILE("copyOutgoingChanges");
|
||||
_dynamicsWorld->synchronizeMotionStates();
|
||||
_hasOutgoingChanges = false;
|
||||
return _dynamicsWorld->getChangedMotionStates();
|
||||
}
|
||||
|
||||
void PhysicsEngine::dumpStatsIfNecessary() {
|
||||
|
@ -495,235 +369,80 @@ void PhysicsEngine::dumpStatsIfNecessary() {
|
|||
// CF_DISABLE_VISUALIZE_OBJECT = 32, //disable debug drawing
|
||||
// CF_DISABLE_SPU_COLLISION_PROCESSING = 64//disable parallel/SPU processing
|
||||
|
||||
void PhysicsEngine::addObject(const ShapeInfo& shapeInfo, btCollisionShape* shape, ObjectMotionState* motionState) {
|
||||
assert(shape);
|
||||
void PhysicsEngine::bump(ObjectMotionState* motionState) {
|
||||
// Find all objects that touch the object corresponding to motionState and flag the other objects
|
||||
// for simulation ownership by the local simulation.
|
||||
|
||||
assert(motionState);
|
||||
btCollisionObject* object = motionState->getRigidBody();
|
||||
|
||||
btVector3 inertia(0.0f, 0.0f, 0.0f);
|
||||
float mass = 0.0f;
|
||||
btRigidBody* body = NULL;
|
||||
switch(motionState->computeObjectMotionType()) {
|
||||
case MOTION_TYPE_KINEMATIC: {
|
||||
body = new btRigidBody(mass, motionState, shape, inertia);
|
||||
body->setCollisionFlags(btCollisionObject::CF_KINEMATIC_OBJECT);
|
||||
body->updateInertiaTensor();
|
||||
motionState->setRigidBody(body);
|
||||
motionState->setKinematic(true, _numSubsteps);
|
||||
const float KINEMATIC_LINEAR_VELOCITY_THRESHOLD = 0.01f; // 1 cm/sec
|
||||
const float KINEMATIC_ANGULAR_VELOCITY_THRESHOLD = 0.01f; // ~1 deg/sec
|
||||
body->setSleepingThresholds(KINEMATIC_LINEAR_VELOCITY_THRESHOLD, KINEMATIC_ANGULAR_VELOCITY_THRESHOLD);
|
||||
break;
|
||||
}
|
||||
case MOTION_TYPE_DYNAMIC: {
|
||||
mass = motionState->computeObjectMass(shapeInfo);
|
||||
shape->calculateLocalInertia(mass, inertia);
|
||||
body = new btRigidBody(mass, motionState, shape, inertia);
|
||||
body->updateInertiaTensor();
|
||||
motionState->setRigidBody(body);
|
||||
motionState->setKinematic(false, _numSubsteps);
|
||||
motionState->updateBodyVelocities();
|
||||
// NOTE: Bullet will deactivate any object whose velocity is below these thresholds for longer than 2 seconds.
|
||||
// (the 2 seconds is determined by: static btRigidBody::gDeactivationTime
|
||||
const float DYNAMIC_LINEAR_VELOCITY_THRESHOLD = 0.05f; // 5 cm/sec
|
||||
const float DYNAMIC_ANGULAR_VELOCITY_THRESHOLD = 0.087266f; // ~5 deg/sec
|
||||
body->setSleepingThresholds(DYNAMIC_LINEAR_VELOCITY_THRESHOLD, DYNAMIC_ANGULAR_VELOCITY_THRESHOLD);
|
||||
if (!motionState->isMoving()) {
|
||||
// try to initialize this object as inactive
|
||||
body->forceActivationState(ISLAND_SLEEPING);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case MOTION_TYPE_STATIC:
|
||||
default: {
|
||||
body = new btRigidBody(mass, motionState, shape, inertia);
|
||||
body->setCollisionFlags(btCollisionObject::CF_STATIC_OBJECT);
|
||||
body->updateInertiaTensor();
|
||||
motionState->setRigidBody(body);
|
||||
motionState->setKinematic(false, _numSubsteps);
|
||||
break;
|
||||
}
|
||||
}
|
||||
body->setFlags(BT_DISABLE_WORLD_GRAVITY);
|
||||
motionState->updateBodyMaterialProperties();
|
||||
|
||||
_dynamicsWorld->addRigidBody(body);
|
||||
motionState->resetMeasuredBodyAcceleration();
|
||||
}
|
||||
|
||||
void PhysicsEngine::bump(EntityItem* bumpEntity) {
|
||||
// If this node is doing something like deleting an entity, scan for contacts involving the
|
||||
// entity. For each found, flag the other entity involved as being simulated by this node.
|
||||
lock();
|
||||
int numManifolds = _collisionDispatcher->getNumManifolds();
|
||||
for (int i = 0; i < numManifolds; ++i) {
|
||||
btPersistentManifold* contactManifold = _collisionDispatcher->getManifoldByIndexInternal(i);
|
||||
if (contactManifold->getNumContacts() > 0) {
|
||||
const btCollisionObject* objectA = static_cast<const btCollisionObject*>(contactManifold->getBody0());
|
||||
const btCollisionObject* objectB = static_cast<const btCollisionObject*>(contactManifold->getBody1());
|
||||
if (objectA && objectB) {
|
||||
void* a = objectA->getUserPointer();
|
||||
void* b = objectB->getUserPointer();
|
||||
if (a && b) {
|
||||
EntityMotionState* entityMotionStateA = static_cast<EntityMotionState*>(a);
|
||||
EntityMotionState* entityMotionStateB = static_cast<EntityMotionState*>(b);
|
||||
EntityItem* entityA = entityMotionStateA ? entityMotionStateA->getEntity() : NULL;
|
||||
EntityItem* entityB = entityMotionStateB ? entityMotionStateB->getEntity() : NULL;
|
||||
if (entityA && entityB) {
|
||||
if (entityA == bumpEntity) {
|
||||
entityMotionStateB->setShouldClaimSimulationOwnership(true);
|
||||
if (!objectB->isActive()) {
|
||||
objectB->setActivationState(ACTIVE_TAG);
|
||||
}
|
||||
}
|
||||
if (entityB == bumpEntity) {
|
||||
entityMotionStateA->setShouldClaimSimulationOwnership(true);
|
||||
if (!objectA->isActive()) {
|
||||
objectA->setActivationState(ACTIVE_TAG);
|
||||
}
|
||||
}
|
||||
if (objectB == object) {
|
||||
if (!objectA->isStaticOrKinematicObject()) {
|
||||
ObjectMotionState* motionStateA = static_cast<ObjectMotionState*>(objectA->getUserPointer());
|
||||
if (motionStateA) {
|
||||
motionStateA->bump();
|
||||
objectA->setActivationState(ACTIVE_TAG);
|
||||
}
|
||||
}
|
||||
} else if (objectA == object) {
|
||||
if (!objectB->isStaticOrKinematicObject()) {
|
||||
ObjectMotionState* motionStateB = static_cast<ObjectMotionState*>(objectB->getUserPointer());
|
||||
if (motionStateB) {
|
||||
motionStateB->bump();
|
||||
objectB->setActivationState(ACTIVE_TAG);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
unlock();
|
||||
}
|
||||
|
||||
void PhysicsEngine::removeObjectFromBullet(ObjectMotionState* motionState) {
|
||||
assert(motionState);
|
||||
btRigidBody* body = motionState->getRigidBody();
|
||||
|
||||
// wake up anything touching this object
|
||||
EntityItem* entityItem = motionState ? motionState->getEntity() : NULL;
|
||||
bump(entityItem);
|
||||
|
||||
if (body) {
|
||||
const btCollisionShape* shape = body->getCollisionShape();
|
||||
_dynamicsWorld->removeRigidBody(body);
|
||||
_shapeManager.releaseShape(shape);
|
||||
// NOTE: setRigidBody() modifies body->m_userPointer so we should clear the MotionState's body BEFORE deleting it.
|
||||
motionState->setRigidBody(NULL);
|
||||
delete body;
|
||||
motionState->setKinematic(false, _numSubsteps);
|
||||
|
||||
removeContacts(motionState);
|
||||
}
|
||||
}
|
||||
|
||||
// private
|
||||
bool PhysicsEngine::updateBodyHard(btRigidBody* body, ObjectMotionState* motionState, uint32_t flags) {
|
||||
MotionType newType = motionState->computeObjectMotionType();
|
||||
|
||||
// pull body out of physics engine
|
||||
_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 shapeInfo;
|
||||
motionState->computeObjectShapeInfo(shapeInfo);
|
||||
btCollisionShape* newShape = _shapeManager.getShape(shapeInfo);
|
||||
if (!newShape) {
|
||||
// FAIL! we are unable to support these changes!
|
||||
_shapeManager.releaseShape(oldShape);
|
||||
|
||||
// NOTE: setRigidBody() modifies body->m_userPointer so we should clear the MotionState's body BEFORE deleting it.
|
||||
motionState->setRigidBody(NULL);
|
||||
delete body;
|
||||
motionState->setKinematic(false, _numSubsteps);
|
||||
removeContacts(motionState);
|
||||
return false;
|
||||
} else 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->computeObjectMass(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);
|
||||
}
|
||||
}
|
||||
bool easyUpdate = flags & EASY_DIRTY_PHYSICS_FLAGS;
|
||||
if (easyUpdate) {
|
||||
motionState->updateBodyEasy(flags, _numSubsteps);
|
||||
}
|
||||
|
||||
// update the motion parameters
|
||||
switch (newType) {
|
||||
case MOTION_TYPE_KINEMATIC: {
|
||||
int collisionFlags = body->getCollisionFlags() | btCollisionObject::CF_KINEMATIC_OBJECT;
|
||||
collisionFlags &= ~(btCollisionObject::CF_STATIC_OBJECT);
|
||||
body->setCollisionFlags(collisionFlags);
|
||||
body->forceActivationState(DISABLE_DEACTIVATION);
|
||||
|
||||
body->setMassProps(0.0f, btVector3(0.0f, 0.0f, 0.0f));
|
||||
body->updateInertiaTensor();
|
||||
motionState->setKinematic(true, _numSubsteps);
|
||||
break;
|
||||
}
|
||||
case MOTION_TYPE_DYNAMIC: {
|
||||
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 above)
|
||||
ShapeInfo shapeInfo;
|
||||
motionState->computeObjectShapeInfo(shapeInfo);
|
||||
float mass = motionState->computeObjectMass(shapeInfo);
|
||||
btVector3 inertia(0.0f, 0.0f, 0.0f);
|
||||
body->getCollisionShape()->calculateLocalInertia(mass, inertia);
|
||||
body->setMassProps(mass, inertia);
|
||||
body->updateInertiaTensor();
|
||||
}
|
||||
body->forceActivationState(ACTIVE_TAG);
|
||||
motionState->setKinematic(false, _numSubsteps);
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
// MOTION_TYPE_STATIC
|
||||
int collisionFlags = body->getCollisionFlags() | btCollisionObject::CF_STATIC_OBJECT;
|
||||
collisionFlags &= ~(btCollisionObject::CF_KINEMATIC_OBJECT);
|
||||
body->setCollisionFlags(collisionFlags);
|
||||
body->forceActivationState(DISABLE_SIMULATION);
|
||||
|
||||
body->setMassProps(0.0f, btVector3(0.0f, 0.0f, 0.0f));
|
||||
body->updateInertiaTensor();
|
||||
|
||||
body->setLinearVelocity(btVector3(0.0f, 0.0f, 0.0f));
|
||||
body->setAngularVelocity(btVector3(0.0f, 0.0f, 0.0f));
|
||||
motionState->setKinematic(false, _numSubsteps);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// reinsert body into physics engine
|
||||
_dynamicsWorld->addRigidBody(body);
|
||||
|
||||
body->activate();
|
||||
return true;
|
||||
}
|
||||
|
||||
void PhysicsEngine::setCharacterController(DynamicCharacterController* character) {
|
||||
if (_characterController != character) {
|
||||
lock();
|
||||
if (_characterController) {
|
||||
// remove the character from the DynamicsWorld immediately
|
||||
_characterController->setDynamicsWorld(NULL);
|
||||
_characterController = NULL;
|
||||
_characterController->setDynamicsWorld(nullptr);
|
||||
_characterController = nullptr;
|
||||
}
|
||||
// the character will be added to the DynamicsWorld later
|
||||
_characterController = character;
|
||||
unlock();
|
||||
}
|
||||
}
|
||||
|
||||
bool PhysicsEngine::physicsInfoIsActive(void* physicsInfo) {
|
||||
if (!physicsInfo) {
|
||||
return false;
|
||||
}
|
||||
|
||||
ObjectMotionState* motionState = static_cast<ObjectMotionState*>(physicsInfo);
|
||||
btRigidBody* body = motionState->getRigidBody();
|
||||
if (!body) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return body->isActive();
|
||||
}
|
||||
|
||||
bool PhysicsEngine::getBodyLocation(void* physicsInfo, glm::vec3& positionReturn, glm::quat& rotationReturn) {
|
||||
if (!physicsInfo) {
|
||||
return false;
|
||||
}
|
||||
|
||||
ObjectMotionState* motionState = static_cast<ObjectMotionState*>(physicsInfo);
|
||||
btRigidBody* body = motionState->getRigidBody();
|
||||
if (!body) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const btTransform& worldTrans = body->getCenterOfMassTransform();
|
||||
positionReturn = bulletToGLM(worldTrans.getOrigin()) + ObjectMotionState::getWorldOffset();
|
||||
rotationReturn = bulletToGLM(worldTrans.getRotation());
|
||||
|
||||
return true;
|
||||
}
|
||||
|
|
|
@ -14,18 +14,15 @@
|
|||
|
||||
#include <stdint.h>
|
||||
|
||||
#include <QSet>
|
||||
#include <QUuid>
|
||||
#include <QVector>
|
||||
#include <btBulletDynamicsCommon.h>
|
||||
#include <BulletCollision/CollisionDispatch/btGhostObject.h>
|
||||
|
||||
#include <EntityItem.h>
|
||||
#include <EntitySimulation.h>
|
||||
|
||||
#include "BulletUtil.h"
|
||||
#include "DynamicCharacterController.h"
|
||||
#include "ContactInfo.h"
|
||||
#include "EntityMotionState.h"
|
||||
#include "ShapeManager.h"
|
||||
#include "DynamicCharacterController.h"
|
||||
#include "PhysicsTypedefs.h"
|
||||
#include "ThreadSafeDynamicsWorld.h"
|
||||
|
||||
const float HALF_SIMULATION_EXTENT = 512.0f; // meters
|
||||
|
@ -39,37 +36,45 @@ public:
|
|||
ContactKey(void* a, void* b) : _a(a), _b(b) {}
|
||||
bool operator<(const ContactKey& other) const { return _a < other._a || (_a == other._a && _b < other._b); }
|
||||
bool operator==(const ContactKey& other) const { return _a == other._a && _b == other._b; }
|
||||
void* _a; // EntityMotionState pointer
|
||||
void* _b; // EntityMotionState pointer
|
||||
void* _a; // ObjectMotionState pointer
|
||||
void* _b; // ObjectMotionState pointer
|
||||
};
|
||||
|
||||
typedef std::map<ContactKey, ContactInfo> ContactMap;
|
||||
typedef std::pair<ContactKey, ContactInfo> ContactMapElement;
|
||||
typedef QVector<Collision> CollisionEvents;
|
||||
|
||||
class PhysicsEngine : public EntitySimulation {
|
||||
class PhysicsEngine {
|
||||
public:
|
||||
// TODO: find a good way to make this a non-static method
|
||||
static uint32_t getNumSubsteps();
|
||||
|
||||
PhysicsEngine() = delete; // prevent compiler from creating default ctor
|
||||
PhysicsEngine(const glm::vec3& offset);
|
||||
|
||||
~PhysicsEngine();
|
||||
void init();
|
||||
|
||||
// overrides for EntitySimulation
|
||||
void updateEntitiesInternal(const quint64& now);
|
||||
void addEntityInternal(EntityItem* entity);
|
||||
void removeEntityInternal(EntityItem* entity);
|
||||
void entityChangedInternal(EntityItem* entity);
|
||||
void sortEntitiesThatMovedInternal();
|
||||
void clearEntitiesInternal();
|
||||
void setSessionUUID(const QUuid& sessionID) { _sessionID = sessionID; }
|
||||
|
||||
virtual void init(EntityEditPacketSender* packetSender);
|
||||
void addObject(ObjectMotionState* motionState);
|
||||
void removeObject(ObjectMotionState* motionState);
|
||||
|
||||
void deleteObjects(VectorOfMotionStates& objects);
|
||||
void deleteObjects(SetOfMotionStates& objects); // only called during teardown
|
||||
void addObjects(VectorOfMotionStates& objects);
|
||||
void changeObjects(VectorOfMotionStates& objects);
|
||||
void reinsertObject(ObjectMotionState* object);
|
||||
|
||||
void stepSimulation();
|
||||
void stepNonPhysicalKinematics(const quint64& now);
|
||||
void computeCollisionEvents();
|
||||
void updateContactMap();
|
||||
|
||||
bool hasOutgoingChanges() const { return _hasOutgoingChanges; }
|
||||
|
||||
/// \return reference to list of changed MotionStates. The list is only valid until beginning of next simulation loop.
|
||||
VectorOfMotionStates& getOutgoingChanges();
|
||||
|
||||
/// \return reference to list of Collision events. The list is only valid until beginning of next simulation loop.
|
||||
CollisionEvents& getCollisionEvents();
|
||||
|
||||
/// \brief prints timings for last frame if stats have been requested.
|
||||
void dumpStatsIfNecessary();
|
||||
|
||||
/// \param offset position of simulation origin in domain-frame
|
||||
|
@ -78,31 +83,23 @@ public:
|
|||
/// \return position of simulation origin in domain-frame
|
||||
const glm::vec3& getOriginOffset() const { return _originOffset; }
|
||||
|
||||
/// \param motionState pointer to Object's MotionState
|
||||
/// \return true if Object added
|
||||
void addObject(const ShapeInfo& shapeInfo, btCollisionShape* shape, ObjectMotionState* motionState);
|
||||
/// \brief call bump on any objects that touch the object corresponding to motionState
|
||||
void bump(ObjectMotionState* motionState);
|
||||
|
||||
/// process queue of changed from external sources
|
||||
void relayIncomingChangesToSimulation();
|
||||
void removeRigidBody(btRigidBody* body);
|
||||
|
||||
void setCharacterController(DynamicCharacterController* character);
|
||||
|
||||
void dumpNextStats() { _dumpNextStats = true; }
|
||||
|
||||
void bump(EntityItem* bumpEntity);
|
||||
static bool physicsInfoIsActive(void* physicsInfo);
|
||||
static bool getBodyLocation(void* physicsInfo, glm::vec3& positionReturn, glm::quat& rotationReturn);
|
||||
|
||||
private:
|
||||
/// \param motionState pointer to Object's MotionState
|
||||
void removeObjectFromBullet(ObjectMotionState* motionState);
|
||||
|
||||
void removeContacts(ObjectMotionState* motionState);
|
||||
|
||||
void doOwnershipInfection(const btCollisionObject* objectA, const btCollisionObject* objectB);
|
||||
|
||||
// return 'true' of update was successful
|
||||
bool updateBodyHard(btRigidBody* body, ObjectMotionState* motionState, uint32_t flags);
|
||||
void updateObjectEasy(btRigidBody* body, ObjectMotionState* motionState, uint32_t flags);
|
||||
|
||||
btClock _clock;
|
||||
btDefaultCollisionConfiguration* _collisionConfig = NULL;
|
||||
btCollisionDispatcher* _collisionDispatcher = NULL;
|
||||
|
@ -110,18 +107,9 @@ private:
|
|||
btSequentialImpulseConstraintSolver* _constraintSolver = NULL;
|
||||
ThreadSafeDynamicsWorld* _dynamicsWorld = NULL;
|
||||
btGhostPairCallback* _ghostPairCallback = NULL;
|
||||
ShapeManager _shapeManager;
|
||||
|
||||
glm::vec3 _originOffset;
|
||||
|
||||
// EntitySimulation stuff
|
||||
QSet<EntityMotionState*> _entityMotionStates; // all entities that we track
|
||||
QSet<ObjectMotionState*> _nonPhysicalKinematicObjects; // not in physics simulation, but still need kinematic simulation
|
||||
QSet<ObjectMotionState*> _incomingChanges; // entities with pending physics changes by script or packet
|
||||
QSet<ObjectMotionState*> _outgoingPackets; // MotionStates with pending changes that need to be sent over wire
|
||||
|
||||
EntityEditPacketSender* _entityPacketSender = NULL;
|
||||
|
||||
ContactMap _contactMap;
|
||||
uint32_t _numContactFrames = 0;
|
||||
uint32_t _lastNumSubstepsAtUpdateInternal = 0;
|
||||
|
@ -130,6 +118,10 @@ private:
|
|||
DynamicCharacterController* _characterController = NULL;
|
||||
|
||||
bool _dumpNextStats = false;
|
||||
bool _hasOutgoingChanges = false;
|
||||
|
||||
QUuid _sessionID;
|
||||
CollisionEvents _collisionEvents;
|
||||
};
|
||||
|
||||
#endif // hifi_PhysicsEngine_h
|
||||
|
|
23
libraries/physics/src/PhysicsTypedefs.h
Normal file
23
libraries/physics/src/PhysicsTypedefs.h
Normal file
|
@ -0,0 +1,23 @@
|
|||
//
|
||||
// PhysicsTypedefs.h
|
||||
// libraries/physcis/src
|
||||
//
|
||||
// Created by Andrew Meadows 2015.04.29
|
||||
// Copyright 2015 High Fidelity, Inc.
|
||||
//
|
||||
// Distributed under the Apache License, Version 2.0.
|
||||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
//
|
||||
|
||||
#ifndef hifi_PhysicsTypedefs_h
|
||||
#define hifi_PhysicsTypedefs_h
|
||||
|
||||
#include <QSet>
|
||||
#include <QVector>
|
||||
|
||||
class ObjectMotionState;
|
||||
|
||||
typedef QSet<ObjectMotionState*> SetOfMotionStates;
|
||||
typedef QVector<ObjectMotionState*> VectorOfMotionStates;
|
||||
|
||||
#endif //hifi_PhysicsTypedefs_h
|
|
@ -17,6 +17,7 @@
|
|||
|
||||
#include <LinearMath/btQuickprof.h>
|
||||
|
||||
#include "ObjectMotionState.h"
|
||||
#include "ThreadSafeDynamicsWorld.h"
|
||||
|
||||
ThreadSafeDynamicsWorld::ThreadSafeDynamicsWorld(
|
||||
|
@ -57,7 +58,7 @@ int ThreadSafeDynamicsWorld::stepSimulation( btScalar timeStep, int maxSubSteps,
|
|||
|
||||
/*//process some debugging flags
|
||||
if (getDebugDrawer()) {
|
||||
btIDebugDraw* debugDrawer = getDebugDrawer ();
|
||||
btIDebugDraw* debugDrawer = getDebugDrawer();
|
||||
gDisableDeactivation = (debugDrawer->getDebugMode() & btIDebugDraw::DBG_NoDeactivation) != 0;
|
||||
}*/
|
||||
if (subSteps) {
|
||||
|
@ -84,3 +85,33 @@ int ThreadSafeDynamicsWorld::stepSimulation( btScalar timeStep, int maxSubSteps,
|
|||
|
||||
return subSteps;
|
||||
}
|
||||
|
||||
void ThreadSafeDynamicsWorld::synchronizeMotionStates() {
|
||||
_changedMotionStates.clear();
|
||||
BT_PROFILE("synchronizeMotionStates");
|
||||
if (m_synchronizeAllMotionStates) {
|
||||
//iterate over all collision objects
|
||||
for (int i=0;i<m_collisionObjects.size();i++) {
|
||||
btCollisionObject* colObj = m_collisionObjects[i];
|
||||
btRigidBody* body = btRigidBody::upcast(colObj);
|
||||
if (body) {
|
||||
if (body->getMotionState()) {
|
||||
synchronizeSingleMotionState(body);
|
||||
_changedMotionStates.push_back(static_cast<ObjectMotionState*>(body->getMotionState()));
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
//iterate over all active rigid bodies
|
||||
for (int i=0;i<m_nonStaticRigidBodies.size();i++) {
|
||||
btRigidBody* body = m_nonStaticRigidBodies[i];
|
||||
if (body->isActive()) {
|
||||
if (body->getMotionState()) {
|
||||
synchronizeSingleMotionState(body);
|
||||
_changedMotionStates.push_back(static_cast<ObjectMotionState*>(body->getMotionState()));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -18,8 +18,11 @@
|
|||
#ifndef hifi_ThreadSafeDynamicsWorld_h
|
||||
#define hifi_ThreadSafeDynamicsWorld_h
|
||||
|
||||
#include <BulletDynamics/Dynamics/btRigidBody.h>
|
||||
#include <BulletDynamics/Dynamics/btDiscreteDynamicsWorld.h>
|
||||
|
||||
#include "PhysicsTypedefs.h"
|
||||
|
||||
ATTRIBUTE_ALIGNED16(class) ThreadSafeDynamicsWorld : public btDiscreteDynamicsWorld {
|
||||
public:
|
||||
BT_DECLARE_ALIGNED_ALLOCATOR();
|
||||
|
@ -32,11 +35,17 @@ public:
|
|||
|
||||
// virtual overrides from btDiscreteDynamicsWorld
|
||||
int stepSimulation( btScalar timeStep, int maxSubSteps=1, btScalar fixedTimeStep=btScalar(1.)/btScalar(60.));
|
||||
void synchronizeMotionStates();
|
||||
|
||||
// btDiscreteDynamicsWorld::m_localTime is the portion of real-time that has not yet been simulated
|
||||
// but is used for MotionState::setWorldTransform() extrapolation (a feature that Bullet uses to provide
|
||||
// smoother rendering of objects when the physics simulation loop is ansynchronous to the render loop).
|
||||
float getLocalTimeAccumulation() const { return m_localTime; }
|
||||
|
||||
VectorOfMotionStates& getChangedMotionStates() { return _changedMotionStates; }
|
||||
|
||||
private:
|
||||
VectorOfMotionStates _changedMotionStates;
|
||||
};
|
||||
|
||||
#endif // hifi_ThreadSafeDynamicsWorld_h
|
||||
|
|
|
@ -417,17 +417,23 @@ void ImageReader::run() {
|
|||
return;
|
||||
}
|
||||
|
||||
// enforce a fixed maximum area (1024 * 2048)
|
||||
const int MAXIMUM_AREA_SIZE = 2097152;
|
||||
int imageArea = image.width() * image.height();
|
||||
if (imageArea > MAXIMUM_AREA_SIZE) {
|
||||
float scaleRatio = sqrtf((float)MAXIMUM_AREA_SIZE) / sqrtf((float)imageArea);
|
||||
int resizeWidth = static_cast<int>(std::floor(scaleRatio * static_cast<float>(image.width())));
|
||||
int resizeHeight = static_cast<int>(std::floor(scaleRatio * static_cast<float>(image.height())));
|
||||
qCDebug(renderutils) << "Image greater than maximum size:" << _url << image.width() << image.height() <<
|
||||
" scaled to:" << resizeWidth << resizeHeight;
|
||||
image = image.scaled(resizeWidth, resizeHeight, Qt::IgnoreAspectRatio);
|
||||
imageArea = image.width() * image.height();
|
||||
auto ntex = dynamic_cast<NetworkTexture*>(&*texture);
|
||||
if (ntex && (ntex->getType() == CUBE_TEXTURE)) {
|
||||
qCDebug(renderutils) << "Cube map size:" << _url << image.width() << image.height();
|
||||
} else {
|
||||
|
||||
// enforce a fixed maximum area (1024 * 2048)
|
||||
const int MAXIMUM_AREA_SIZE = 2097152;
|
||||
if (imageArea > MAXIMUM_AREA_SIZE) {
|
||||
float scaleRatio = sqrtf((float)MAXIMUM_AREA_SIZE) / sqrtf((float)imageArea);
|
||||
int resizeWidth = static_cast<int>(std::floor(scaleRatio * static_cast<float>(image.width())));
|
||||
int resizeHeight = static_cast<int>(std::floor(scaleRatio * static_cast<float>(image.height())));
|
||||
qCDebug(renderutils) << "Image greater than maximum size:" << _url << image.width() << image.height() <<
|
||||
" scaled to:" << resizeWidth << resizeHeight;
|
||||
image = image.scaled(resizeWidth, resizeHeight, Qt::IgnoreAspectRatio);
|
||||
imageArea = image.width() * image.height();
|
||||
}
|
||||
}
|
||||
|
||||
const int EIGHT_BIT_MAXIMUM = 255;
|
||||
|
@ -507,6 +513,7 @@ void NetworkTexture::setImage(const QImage& image, bool translucent, const QColo
|
|||
imageLoaded(image);
|
||||
|
||||
if ((_width > 0) && (_height > 0)) {
|
||||
|
||||
bool isLinearRGB = true; //(_type == NORMAL_TEXTURE) || (_type == EMISSIVE_TEXTURE);
|
||||
|
||||
gpu::Element formatGPU = gpu::Element(gpu::VEC3, gpu::UINT8, (isLinearRGB ? gpu::RGB : gpu::SRGB));
|
||||
|
@ -515,9 +522,18 @@ void NetworkTexture::setImage(const QImage& image, bool translucent, const QColo
|
|||
formatGPU = gpu::Element(gpu::VEC4, gpu::UINT8, (isLinearRGB ? gpu::RGBA : gpu::SRGBA));
|
||||
formatMip = gpu::Element(gpu::VEC4, gpu::UINT8, (isLinearRGB ? gpu::BGRA : gpu::SBGRA));
|
||||
}
|
||||
_gpuTexture = gpu::TexturePointer(gpu::Texture::create2D(formatGPU, image.width(), image.height(), gpu::Sampler(gpu::Sampler::FILTER_MIN_MAG_MIP_LINEAR)));
|
||||
_gpuTexture->assignStoredMip(0, formatMip, image.byteCount(), image.constBits());
|
||||
_gpuTexture->autoGenerateMips(-1);
|
||||
|
||||
if (_type == CUBE_TEXTURE) {
|
||||
if (_height >= (6 * _width)) {
|
||||
_gpuTexture = gpu::TexturePointer(gpu::Texture::createCube(formatGPU, image.width(), gpu::Sampler(gpu::Sampler::FILTER_MIN_MAG_LINEAR, gpu::Sampler::WRAP_CLAMP)));
|
||||
_gpuTexture->assignStoredMip(0, formatMip, image.byteCount(), image.constBits());
|
||||
_gpuTexture->autoGenerateMips(-1);
|
||||
}
|
||||
} else {
|
||||
_gpuTexture = gpu::TexturePointer(gpu::Texture::create2D(formatGPU, image.width(), image.height(), gpu::Sampler(gpu::Sampler::FILTER_MIN_MAG_MIP_LINEAR)));
|
||||
_gpuTexture->assignStoredMip(0, formatMip, image.byteCount(), image.constBits());
|
||||
_gpuTexture->autoGenerateMips(-1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -27,7 +27,7 @@ class NetworkTexture;
|
|||
|
||||
typedef QSharedPointer<NetworkTexture> NetworkTexturePointer;
|
||||
|
||||
enum TextureType { DEFAULT_TEXTURE, NORMAL_TEXTURE, SPECULAR_TEXTURE, EMISSIVE_TEXTURE, SPLAT_TEXTURE };
|
||||
enum TextureType { DEFAULT_TEXTURE, NORMAL_TEXTURE, SPECULAR_TEXTURE, EMISSIVE_TEXTURE, SPLAT_TEXTURE, CUBE_TEXTURE };
|
||||
|
||||
/// Stores cached textures, including render-to-texture targets.
|
||||
class TextureCache : public ResourceCache, public Dependency {
|
||||
|
@ -164,7 +164,7 @@ public:
|
|||
int getOriginalHeight() const { return _originalHeight; }
|
||||
int getWidth() const { return _width; }
|
||||
int getHeight() const { return _height; }
|
||||
|
||||
TextureType getType() const { return _type; }
|
||||
protected:
|
||||
|
||||
virtual void downloadFinished(QNetworkReply* reply);
|
||||
|
|
|
@ -13,6 +13,9 @@
|
|||
|
||||
#include "SceneScriptingInterface.h"
|
||||
|
||||
#include "SceneScriptingInterface.h"
|
||||
|
||||
|
||||
void SceneScriptingInterface::setStageOrientation(const glm::quat& orientation) {
|
||||
_skyStage->setOriginOrientation(orientation);
|
||||
}
|
||||
|
@ -86,6 +89,29 @@ bool SceneScriptingInterface::isStageSunModelEnabled() const {
|
|||
return _skyStage->isSunModelEnabled();
|
||||
}
|
||||
|
||||
void SceneScriptingInterface::setBackgroundMode(const QString& mode) {
|
||||
if (mode == QString("inherit")) {
|
||||
_skyStage->setBackgroundMode(model::SunSkyStage::NO_BACKGROUND);
|
||||
} else if (mode == QString("atmosphere")) {
|
||||
_skyStage->setBackgroundMode(model::SunSkyStage::SKY_DOME);
|
||||
} else if (mode == QString("skybox")) {
|
||||
_skyStage->setBackgroundMode(model::SunSkyStage::SKY_BOX);
|
||||
}
|
||||
}
|
||||
|
||||
QString SceneScriptingInterface::getBackgroundMode() const {
|
||||
switch (_skyStage->getBackgroundMode()) {
|
||||
case model::SunSkyStage::NO_BACKGROUND:
|
||||
return QString("inherit");
|
||||
case model::SunSkyStage::SKY_DOME:
|
||||
return QString("atmosphere");
|
||||
case model::SunSkyStage::SKY_BOX:
|
||||
return QString("skybox");
|
||||
default:
|
||||
return QString("inherit");
|
||||
};
|
||||
}
|
||||
|
||||
model::SunSkyStagePointer SceneScriptingInterface::getSkyStage() const {
|
||||
return _skyStage;
|
||||
}
|
||||
|
|
|
@ -57,7 +57,8 @@ public:
|
|||
Q_INVOKABLE glm::vec3 getKeyLightDirection() const;
|
||||
|
||||
|
||||
|
||||
Q_INVOKABLE void setBackgroundMode(const QString& mode);
|
||||
Q_INVOKABLE QString getBackgroundMode() const;
|
||||
|
||||
model::SunSkyStagePointer getSkyStage() const;
|
||||
|
||||
|
|
|
@ -187,6 +187,9 @@ void pickRayFromScriptValue(const QScriptValue& object, PickRay& pickRay) {
|
|||
|
||||
QScriptValue collisionToScriptValue(QScriptEngine* engine, const Collision& collision) {
|
||||
QScriptValue obj = engine->newObject();
|
||||
obj.setProperty("type", collision.type);
|
||||
obj.setProperty("idA", quuidToScriptValue(engine, collision.idA));
|
||||
obj.setProperty("idB", quuidToScriptValue(engine, collision.idB));
|
||||
obj.setProperty("penetration", vec3toScriptValue(engine, collision.penetration));
|
||||
obj.setProperty("contactPoint", vec3toScriptValue(engine, collision.contactPoint));
|
||||
return obj;
|
||||
|
|
|
@ -13,6 +13,7 @@
|
|||
#define hifi_RegisteredMetaTypes_h
|
||||
|
||||
#include <QtScript/QScriptEngine>
|
||||
#include <QtCore/QUuid>
|
||||
|
||||
#include <glm/glm.hpp>
|
||||
#include <glm/gtc/quaternion.hpp>
|
||||
|
@ -65,11 +66,21 @@ Q_DECLARE_METATYPE(PickRay)
|
|||
QScriptValue pickRayToScriptValue(QScriptEngine* engine, const PickRay& pickRay);
|
||||
void pickRayFromScriptValue(const QScriptValue& object, PickRay& pickRay);
|
||||
|
||||
enum ContactEventType {
|
||||
CONTACT_EVENT_TYPE_START,
|
||||
CONTACT_EVENT_TYPE_CONTINUE,
|
||||
CONTACT_EVENT_TYPE_END
|
||||
};
|
||||
|
||||
class Collision {
|
||||
public:
|
||||
Collision() : contactPoint(0.0f), penetration(0.0f) { }
|
||||
Collision(const glm::vec3& contactPoint, const glm::vec3& penetration) :
|
||||
contactPoint(contactPoint), penetration(penetration) { }
|
||||
Collision() : type(CONTACT_EVENT_TYPE_START), idA(), idB(), contactPoint(0.0f), penetration(0.0f) { }
|
||||
Collision(ContactEventType cType, const QUuid& cIdA, const QUuid& cIdB, const glm::vec3& cPoint, const glm::vec3& cPenetration)
|
||||
: type(cType), idA(cIdA), idB(cIdB), contactPoint(cPoint), penetration(cPenetration) { }
|
||||
|
||||
ContactEventType type;
|
||||
QUuid idA;
|
||||
QUuid idB;
|
||||
glm::vec3 contactPoint;
|
||||
glm::vec3 penetration;
|
||||
};
|
||||
|
|
Loading…
Reference in a new issue