mirror of
https://github.com/lubosz/overte.git
synced 2025-04-09 08:22:30 +02:00
Merge branch 'master' of https://github.com/highfidelity/hifi into metavoxels
Conflicts: interface/src/renderer/FBXReader.cpp
This commit is contained in:
commit
0448596e58
84 changed files with 2905 additions and 281 deletions
|
@ -36,11 +36,8 @@ public:
|
|||
|
||||
public slots:
|
||||
void run();
|
||||
|
||||
void readPendingDatagrams();
|
||||
signals:
|
||||
void willSendAudioDataCallback();
|
||||
void willSendVisualDataCallback();
|
||||
|
||||
private:
|
||||
ScriptEngine _scriptEngine;
|
||||
VoxelEditPacketSender _voxelEditSender;
|
||||
|
|
|
@ -114,7 +114,7 @@ function sendNextCells() {
|
|||
|
||||
var sentFirstBoard = false;
|
||||
|
||||
function step() {
|
||||
function step(deltaTime) {
|
||||
if (sentFirstBoard) {
|
||||
// we've already sent the first full board, perform a step in time
|
||||
updateCells();
|
||||
|
@ -127,5 +127,5 @@ function step() {
|
|||
}
|
||||
|
||||
|
||||
Script.willSendVisualDataCallback.connect(step);
|
||||
Script.update.connect(step);
|
||||
Voxels.setPacketsPerSecond(200);
|
|
@ -18,7 +18,7 @@ var FACTOR = 0.75;
|
|||
var countParticles = 0; // the first time around we want to create the particle and thereafter to modify it.
|
||||
var particleID;
|
||||
|
||||
function updateParticle() {
|
||||
function updateParticle(deltaTime) {
|
||||
// the particle should be placed in front of the user's avatar
|
||||
var avatarFront = Quat.getFront(MyAvatar.orientation);
|
||||
|
||||
|
@ -62,7 +62,7 @@ function updateParticle() {
|
|||
}
|
||||
|
||||
// register the call back so it fires before each data send
|
||||
Script.willSendVisualDataCallback.connect(updateParticle);
|
||||
Script.update.connect(updateParticle);
|
||||
|
||||
// register our scriptEnding callback
|
||||
Script.scriptEnding.connect(function scriptEnding() {});
|
||||
|
|
|
@ -109,7 +109,7 @@ Agent.isAvatar = true;
|
|||
Avatar.position = firstPosition;
|
||||
printVector("New bot, position = ", Avatar.position);
|
||||
|
||||
function updateBehavior() {
|
||||
function updateBehavior(deltaTime) {
|
||||
if (Math.random() < CHANCE_OF_SOUND) {
|
||||
playRandomSound(Avatar.position);
|
||||
}
|
||||
|
@ -149,7 +149,7 @@ function updateBehavior() {
|
|||
}
|
||||
}
|
||||
}
|
||||
Script.willSendVisualDataCallback.connect(updateBehavior);
|
||||
Script.update.connect(updateBehavior);
|
||||
|
||||
function loadSounds() {
|
||||
sounds.push(new Sound("https://s3-us-west-1.amazonaws.com/highfidelity-public/sounds/Cocktail+Party+Snippets/Raws/AB1.raw"));
|
||||
|
|
|
@ -20,9 +20,8 @@ var joysticksCaptured = false;
|
|||
var THRUST_CONTROLLER = 0;
|
||||
var VIEW_CONTROLLER = 1;
|
||||
|
||||
function checkCamera() {
|
||||
function checkCamera(deltaTime) {
|
||||
if (Camera.getMode() == "independent") {
|
||||
var deltaTime = 1/60; // approximately our FPS - maybe better to be elapsed time since last call
|
||||
var THRUST_MAG_UP = 800.0;
|
||||
var THRUST_MAG_DOWN = 300.0;
|
||||
var THRUST_MAG_FWD = 500.0;
|
||||
|
@ -80,7 +79,7 @@ function checkCamera() {
|
|||
}
|
||||
}
|
||||
|
||||
Script.willSendVisualDataCallback.connect(checkCamera);
|
||||
Script.update.connect(checkCamera);
|
||||
|
||||
function mouseMoveEvent(event) {
|
||||
print("mouseMoveEvent event.x,y=" + event.x + ", " + event.y);
|
||||
|
|
|
@ -28,7 +28,7 @@ var clapping = new Array();
|
|||
clapping[0] = false;
|
||||
clapping[1] = false;
|
||||
|
||||
function maybePlaySound() {
|
||||
function maybePlaySound(deltaTime) {
|
||||
// Set the location and other info for the sound to play
|
||||
var palm1Position = Controller.getSpatialControlPosition(0);
|
||||
var palm2Position = Controller.getSpatialControlPosition(2);
|
||||
|
@ -62,4 +62,4 @@ function maybePlaySound() {
|
|||
}
|
||||
|
||||
// Connect a call back that happens every frame
|
||||
Script.willSendVisualDataCallback.connect(maybePlaySound);
|
||||
Script.update.connect(maybePlaySound);
|
|
@ -30,8 +30,9 @@ var gravity = {
|
|||
var damping = 0.1;
|
||||
|
||||
var scriptA = " " +
|
||||
" function collisionWithParticle(other) { " +
|
||||
" function collisionWithParticle(other, penetration) { " +
|
||||
" print('collisionWithParticle(other.getID()=' + other.getID() + ')...'); " +
|
||||
" Vec3.print('penetration=', penetration); " +
|
||||
" print('myID=' + Particle.getID() + '\\n'); " +
|
||||
" var colorBlack = { red: 0, green: 0, blue: 0 };" +
|
||||
" var otherColor = other.getColor();" +
|
||||
|
@ -45,8 +46,9 @@ var scriptA = " " +
|
|||
" ";
|
||||
|
||||
var scriptB = " " +
|
||||
" function collisionWithParticle(other) { " +
|
||||
" function collisionWithParticle(other, penetration) { " +
|
||||
" print('collisionWithParticle(other.getID()=' + other.getID() + ')...'); " +
|
||||
" Vec3.print('penetration=', penetration); " +
|
||||
" print('myID=' + Particle.getID() + '\\n'); " +
|
||||
" Particle.setScript('Particle.setShouldDie(true);'); " +
|
||||
" } " +
|
||||
|
@ -58,7 +60,7 @@ var color = {
|
|||
green: 255,
|
||||
blue: 0 };
|
||||
|
||||
function draw() {
|
||||
function draw(deltaTime) {
|
||||
print("hello... draw()... currentIteration=" + currentIteration + "\n");
|
||||
|
||||
// on the first iteration, setup a single particle that's slowly moving
|
||||
|
@ -150,5 +152,5 @@ function draw() {
|
|||
// register the call back so it fires before each data send
|
||||
print("here...\n");
|
||||
Particles.setPacketsPerSecond(40000);
|
||||
Script.willSendVisualDataCallback.connect(draw);
|
||||
Script.update.connect(draw);
|
||||
print("and here...\n");
|
||||
|
|
|
@ -16,7 +16,7 @@ for (t = 0; t < numberOfTriggers; t++) {
|
|||
triggerPulled[t] = false;
|
||||
}
|
||||
|
||||
function checkController() {
|
||||
function checkController(deltaTime) {
|
||||
var numberOfTriggers = Controller.getNumberOfTriggers();
|
||||
var numberOfSpatialControls = Controller.getNumberOfSpatialControls();
|
||||
var controllersPerTrigger = numberOfSpatialControls / numberOfTriggers;
|
||||
|
@ -48,7 +48,7 @@ function checkController() {
|
|||
}
|
||||
|
||||
// register the call back so it fires before each data send
|
||||
Script.willSendVisualDataCallback.connect(checkController);
|
||||
Script.update.connect(checkController);
|
||||
|
||||
function printKeyEvent(eventName, event) {
|
||||
print(eventName);
|
||||
|
|
|
@ -10,8 +10,8 @@
|
|||
|
||||
var count = 0;
|
||||
|
||||
function displayCount() {
|
||||
print("count =" + count);
|
||||
function displayCount(deltaTime) {
|
||||
print("count =" + count + " deltaTime=" + deltaTime);
|
||||
count++;
|
||||
}
|
||||
|
||||
|
@ -20,7 +20,7 @@ function scriptEnding() {
|
|||
}
|
||||
|
||||
// register the call back so it fires before each data send
|
||||
Script.willSendVisualDataCallback.connect(displayCount);
|
||||
Script.update.connect(displayCount);
|
||||
|
||||
// register our scriptEnding callback
|
||||
Script.scriptEnding.connect(scriptEnding);
|
||||
|
|
|
@ -31,7 +31,7 @@ var strokeSpeed = new Array();
|
|||
strokeSpeed[0] = 0.0;
|
||||
strokeSpeed[1] = 0.0;
|
||||
|
||||
function checkSticks() {
|
||||
function checkSticks(deltaTime) {
|
||||
for (var palm = 0; palm < 2; palm++) {
|
||||
var palmVelocity = Controller.getSpatialControlVelocity(palm * 2 + 1);
|
||||
var speed = length(palmVelocity);
|
||||
|
@ -69,4 +69,4 @@ function checkSticks() {
|
|||
}
|
||||
|
||||
// Connect a call back that happens every frame
|
||||
Script.willSendVisualDataCallback.connect(checkSticks);
|
||||
Script.update.connect(checkSticks);
|
|
@ -42,7 +42,7 @@ var positionDelta = { x: 0.05, y: 0, z: 0 };
|
|||
|
||||
var particleID = Particles.addParticle(originalProperties);
|
||||
|
||||
function moveParticle() {
|
||||
function moveParticle(deltaTime) {
|
||||
if (count >= moveUntil) {
|
||||
|
||||
// delete it...
|
||||
|
@ -85,5 +85,5 @@ function moveParticle() {
|
|||
|
||||
|
||||
// register the call back so it fires before each data send
|
||||
Script.willSendVisualDataCallback.connect(moveParticle);
|
||||
Script.update.connect(moveParticle);
|
||||
|
||||
|
|
|
@ -1329,7 +1329,7 @@ function checkControllers() {
|
|||
}
|
||||
}
|
||||
|
||||
function update() {
|
||||
function update(deltaTime) {
|
||||
var newWindowDimensions = Controller.getViewportDimensions();
|
||||
if (newWindowDimensions.x != windowDimensions.x || newWindowDimensions.y != windowDimensions.y) {
|
||||
windowDimensions = newWindowDimensions;
|
||||
|
@ -1399,6 +1399,6 @@ function scriptEnding() {
|
|||
}
|
||||
Script.scriptEnding.connect(scriptEnding);
|
||||
|
||||
Script.willSendVisualDataCallback.connect(update);
|
||||
Script.update.connect(update);
|
||||
|
||||
setupMenus();
|
||||
|
|
|
@ -60,7 +60,7 @@ function printProperties(properties) {
|
|||
}
|
||||
}
|
||||
|
||||
function findParticles() {
|
||||
function findParticles(deltaTime) {
|
||||
|
||||
// run for a while, then clean up
|
||||
// stop it...
|
||||
|
@ -122,7 +122,7 @@ function findParticles() {
|
|||
|
||||
|
||||
// register the call back so it fires before each data send
|
||||
Script.willSendVisualDataCallback.connect(findParticles);
|
||||
Script.update.connect(findParticles);
|
||||
|
||||
// register our scriptEnding callback
|
||||
Script.scriptEnding.connect(scriptEnding);
|
||||
|
|
464
examples/flockingBirds.js
Normal file
464
examples/flockingBirds.js
Normal file
|
@ -0,0 +1,464 @@
|
|||
//
|
||||
// flockingBirds.js
|
||||
// hifi
|
||||
//
|
||||
// Created by Brad Hefta-Gaub on 3/4/14.
|
||||
// Copyright (c) 2014 HighFidelity, Inc. All rights reserved.
|
||||
//
|
||||
// This is an example script that generates particles that act like flocking birds
|
||||
//
|
||||
// All birds, even flying solo...
|
||||
// birds don't like to fall too fast
|
||||
// if they fall to fast, they will go into a state of thrusting up, until they reach some max upward velocity, then
|
||||
// go back to gliding
|
||||
// birds don't like to be below a certain altitude
|
||||
// if they are below that altitude they will keep thrusting up, until they get ove
|
||||
|
||||
// flocking
|
||||
// try to align your velocity with velocity of other birds
|
||||
// try to fly toward center of flock
|
||||
// but dont get too close
|
||||
//
|
||||
|
||||
var birdsInFlock = 40;
|
||||
|
||||
var birdLifetime = 60; // 2 minutes
|
||||
var count=0; // iterations
|
||||
|
||||
var enableFlyTowardPoints = true; // some birds have a point they want to fly to
|
||||
var enabledClustedFlyTowardPoints = true; // the flyToward points will be generally near each other
|
||||
var flyToFrames = 10; // number of frames the bird would like to attempt to fly to it's flyTo point
|
||||
var PROBABILITY_OF_FLY_TOWARD_CHANGE = 0.01; // chance the bird will decide to change its flyTo point
|
||||
var PROBABILITY_EACH_BIRD_WILL_FLY_TOWARD = 0.2; // chance the bird will decide to flyTo, otherwise it follows
|
||||
var flyingToCount = 0; // count of birds currently flying to someplace
|
||||
var flyToCluster = { }; // the point that the cluster of flyTo points is based on when in enabledClustedFlyTowardPoints
|
||||
|
||||
// Bird behaviors
|
||||
var enableAvoidDropping = true; // birds will resist falling too fast, and will thrust up if falling
|
||||
var enableAvoidMinHeight = true; // birds will resist being below a certain height and thrust up to get above it
|
||||
var enableAvoidMaxHeight = true; // birds will resist being above a certain height and will glide to get below it
|
||||
var enableMatchFlockVelocity = true; // birds will thrust to match the flocks average velocity
|
||||
var enableThrustTowardCenter = true; // birds will thrust to try to move toward the center of the flock
|
||||
var enableAvoidOtherBirds = true; // birds will thrust away from all other birds
|
||||
var startWithVelocity = true;
|
||||
var flockGravity = { x: 0, y: -1, z: 0};
|
||||
|
||||
// NOTE: these two features don't seem to be very interesting, they cause odd behaviors
|
||||
var enableRandomXZThrust = false; // leading birds randomly decide to thrust in some random direction.
|
||||
var enableSomeBirdsLead = false; // birds randomly decide not fly toward flock, causing other birds to follow
|
||||
var leaders = 0; // number of birds leading
|
||||
var PROBABILITY_TO_LEAD = 0.1; // probabolity a bird will choose to lead
|
||||
|
||||
var birds = new Array(); // array of bird state data
|
||||
|
||||
|
||||
var flockStartPosition = { x: 100, y: 10, z: 100};
|
||||
var flockStartVelocity = { x: 0, y: 0, z: 0};
|
||||
var flockStartThrust = { x: 0, y: 0, z: 0}; // slightly upward against gravity
|
||||
var INITIAL_XY_VELOCITY_SCALE = 2;
|
||||
var birdRadius = 0.0625;
|
||||
var baseBirdColor = { red: 0, green: 255, blue: 255 };
|
||||
var glidingColor = { red: 255, green: 0, blue: 0 };
|
||||
var thrustUpwardColor = { red: 0, green: 255, blue: 0 };
|
||||
var thrustXYColor = { red: 128, green: 0, blue: 128 }; // will be added to any other color
|
||||
var leadingOrflyToColor = { red: 200, green: 200, blue: 255 };
|
||||
|
||||
var tooClose = birdRadius * 3; // how close birds are willing to be to each other
|
||||
var droppingTooFast = -1; // birds don't like to fall too fast
|
||||
var risingTooFast = 1; // birds don't like to climb too fast
|
||||
var upwardThrustAgainstGravity = -10; // how hard a bird will attempt to thrust up to avoid downward motion
|
||||
var droppingAdjustFrames = 5; // birds try to correct their min height in only a couple frames
|
||||
var minHeight = 10; // meters off the ground
|
||||
var maxHeight = 50; // meters off the ground
|
||||
var adjustFrames = 10; // typical number of frames a bird will assume for it's adjustments
|
||||
var PROBABILITY_OF_RANDOM_XZ_THRUST = 0.25;
|
||||
var RANDOM_XZ_THRUST_SCALE = 5; // random -SCALE...to...+SCALE in X or Z direction
|
||||
var MAX_XY_VELOCITY = 10;
|
||||
|
||||
// These are multiplied by every frame since a change. so after 50 frames, there's a 50% probability of change
|
||||
var PROBABILITY_OF_STARTING_XZ_THRUST = 0.0025;
|
||||
var PROBABILITY_OF_STOPPING_XZ_THRUST = 0.01;
|
||||
|
||||
var FLY_TOWARD_XZ_DISTANCE = 100;
|
||||
var FLY_TOWARD_Y_DISTANCE = 0;
|
||||
var FLY_TOWARD_XZ_CLUSTER_DELTA = 5;
|
||||
var FLY_TOWARD_Y_CLUSTER_DELTA = 0;
|
||||
|
||||
function createBirds() {
|
||||
if (enabledClustedFlyTowardPoints) {
|
||||
flyToCluster = { x: flockStartPosition.x + Math.random() * FLY_TOWARD_XZ_DISTANCE,
|
||||
y: flockStartPosition.y + Math.random() * FLY_TOWARD_Y_DISTANCE,
|
||||
z: flockStartPosition.z + Math.random() * FLY_TOWARD_XZ_DISTANCE};
|
||||
}
|
||||
|
||||
for(var i =0; i < birdsInFlock; i++) {
|
||||
var velocity;
|
||||
|
||||
position = Vec3.sum(flockStartPosition, { x: Math.random(), y: Math.random(), z: Math.random() }); // add random
|
||||
|
||||
var flyingToward = position;
|
||||
var isFlyingToward = false;
|
||||
if (enableFlyTowardPoints) {
|
||||
if (Math.random() < PROBABILITY_EACH_BIRD_WILL_FLY_TOWARD) {
|
||||
flyingToCount++;
|
||||
isFlyingToward = true;
|
||||
if (enabledClustedFlyTowardPoints) {
|
||||
flyingToward = { x: flyToCluster.x + Math.random() * FLY_TOWARD_XZ_CLUSTER_DELTA,
|
||||
y: flyToCluster.y + Math.random() * FLY_TOWARD_Y_CLUSTER_DELTA,
|
||||
z: flyToCluster.z + Math.random() * FLY_TOWARD_XZ_CLUSTER_DELTA};
|
||||
} else {
|
||||
flyingToward = { x: flockStartPosition.x + Math.random() * FLY_TOWARD_XZ_DISTANCE,
|
||||
y: flockStartPosition.y + Math.random() * FLY_TOWARD_Y_DISTANCE,
|
||||
z: flockStartPosition.z + Math.random() * FLY_TOWARD_XZ_DISTANCE};
|
||||
}
|
||||
}
|
||||
|
||||
Vec3.print("birds["+i+"].flyingToward=",flyingToward);
|
||||
}
|
||||
|
||||
birds[i] = {
|
||||
particle: {},
|
||||
properties: {},
|
||||
thrust: Vec3.sum(flockStartThrust, { x:0, y: 0, z: 0 }),
|
||||
gliding: true,
|
||||
xzThrust: { x:0, y: 0, z: 0},
|
||||
xzthrustCount: 0,
|
||||
isLeading: false,
|
||||
flyingToward: flyingToward,
|
||||
isFlyingToward: isFlyingToward,
|
||||
};
|
||||
|
||||
if (enableSomeBirdsLead) {
|
||||
if (Math.random() < PROBABILITY_TO_LEAD) {
|
||||
birds[i].isLeading = true;
|
||||
}
|
||||
if (leaders == 0 && i == (birdsInFlock-1)) {
|
||||
birds[i].isLeading = true;
|
||||
}
|
||||
if (birds[i].isLeading) {
|
||||
leaders++;
|
||||
velocity = { x: 2, y: 0, z: 2};
|
||||
print(">>>>>>THIS BIRD LEADS!!!! i="+i);
|
||||
}
|
||||
}
|
||||
|
||||
if (startWithVelocity) {
|
||||
velocity = Vec3.sum(flockStartVelocity, { x: (Math.random() * INITIAL_XY_VELOCITY_SCALE),
|
||||
y: 0,
|
||||
z: (Math.random() * INITIAL_XY_VELOCITY_SCALE) }); // add random
|
||||
} else {
|
||||
velocity = { x: 0, y: 0, z: 0};
|
||||
}
|
||||
birds[i].particle = Particles.addParticle({
|
||||
position: position,
|
||||
velocity: velocity,
|
||||
gravity: flockGravity,
|
||||
damping: 0,
|
||||
radius: birdRadius,
|
||||
color: baseBirdColor,
|
||||
lifetime: birdLifetime
|
||||
});
|
||||
|
||||
}
|
||||
print("flyingToCount=" + flyingToCount);
|
||||
}
|
||||
|
||||
var wantDebug = false;
|
||||
function updateBirds(deltaTime) {
|
||||
count++;
|
||||
|
||||
// get all our bird properties, and calculate the current flock velocity
|
||||
var averageVelocity = { x: 0, y: 0, z: 0};
|
||||
var averagePosition = { x: 0, y: 0, z: 0};
|
||||
var knownBirds = 0;
|
||||
for(var i =0; i < birdsInFlock; i++) {
|
||||
// identifyParticle() will check to see that the particle handle we have is in sync with the domain/server
|
||||
// context. If the handle is for a created particle that now has a known ID it will be updated to be a
|
||||
// handle with a known ID.
|
||||
birds[i].particle = Particles.identifyParticle(birds[i].particle);
|
||||
|
||||
if (birds[i].particle.isKnownID) {
|
||||
birds[i].properties = Particles.getParticleProperties(birds[i].particle);
|
||||
if (birds[i].properties.isKnownID) {
|
||||
knownBirds++;
|
||||
averageVelocity = Vec3.sum(averageVelocity, birds[i].properties.velocity);
|
||||
averagePosition = Vec3.sum(averagePosition, birds[i].properties.position);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (knownBirds == 0 && count > 100) {
|
||||
Script.stop();
|
||||
return;
|
||||
}
|
||||
averageVelocity = Vec3.multiply(averageVelocity, (1 / Math.max(1, knownBirds)));
|
||||
averagePosition = Vec3.multiply(averagePosition, (1 / Math.max(1, knownBirds)));
|
||||
|
||||
if (wantDebug) {
|
||||
Vec3.print("averagePosition=",averagePosition);
|
||||
Vec3.print("averageVelocity=",averageVelocity);
|
||||
print("knownBirds="+knownBirds);
|
||||
}
|
||||
|
||||
var flyToClusterChanged = false;
|
||||
if (enabledClustedFlyTowardPoints) {
|
||||
if (Math.random() < PROBABILITY_OF_FLY_TOWARD_CHANGE) {
|
||||
flyToClusterChanged = true;
|
||||
flyToCluster = { x: averagePosition.x + (Math.random() * FLY_TOWARD_XZ_DISTANCE) - FLY_TOWARD_XZ_DISTANCE/2,
|
||||
y: averagePosition.y + (Math.random() * FLY_TOWARD_Y_DISTANCE) - FLY_TOWARD_Y_DISTANCE/2,
|
||||
z: averagePosition.z + (Math.random() * FLY_TOWARD_XZ_DISTANCE) - FLY_TOWARD_XZ_DISTANCE/2};
|
||||
}
|
||||
}
|
||||
|
||||
// iterate all birds again, adjust their thrust for various goals
|
||||
for(var i =0; i < birdsInFlock; i++) {
|
||||
|
||||
birds[i].thrust = { x: 0, y: 0, z: 0 }; // assume no thrust...
|
||||
|
||||
if (birds[i].particle.isKnownID) {
|
||||
|
||||
if (enableFlyTowardPoints) {
|
||||
// if we're flying toward clusters, and the cluster changed, and this bird is flyingToward
|
||||
// then we need to update it's flyingToward
|
||||
if (enabledClustedFlyTowardPoints && flyToClusterChanged && birds[i].isFlyingToward) {
|
||||
flyingToward = { x: flyToCluster.x + (Math.random() * FLY_TOWARD_XZ_CLUSTER_DELTA) - FLY_TOWARD_XZ_CLUSTER_DELTA/2,
|
||||
y: flyToCluster.y + (Math.random() * FLY_TOWARD_Y_CLUSTER_DELTA) - FLY_TOWARD_Y_CLUSTER_DELTA/2,
|
||||
z: flyToCluster.z + (Math.random() * FLY_TOWARD_XZ_CLUSTER_DELTA) - FLY_TOWARD_XZ_CLUSTER_DELTA/2};
|
||||
birds[i].flyingToward = flyingToward;
|
||||
}
|
||||
|
||||
// there a random chance this bird will decide to change it's flying toward state
|
||||
if (Math.random() < PROBABILITY_OF_FLY_TOWARD_CHANGE) {
|
||||
var wasFlyingTo = birds[i].isFlyingToward;
|
||||
|
||||
// there's some chance it will decide it should be flying toward
|
||||
if (Math.random() < PROBABILITY_EACH_BIRD_WILL_FLY_TOWARD) {
|
||||
|
||||
// if we're flying toward clustered points, then we randomize from the cluster, otherwise we pick
|
||||
// completely random places based on flocks current averagePosition
|
||||
if (enabledClustedFlyTowardPoints) {
|
||||
flyingToward = { x: flyToCluster.x + (Math.random() * FLY_TOWARD_XZ_CLUSTER_DELTA) - FLY_TOWARD_XZ_CLUSTER_DELTA/2,
|
||||
y: flyToCluster.y + (Math.random() * FLY_TOWARD_Y_CLUSTER_DELTA) - FLY_TOWARD_Y_CLUSTER_DELTA/2,
|
||||
z: flyToCluster.z + (Math.random() * FLY_TOWARD_XZ_CLUSTER_DELTA) - FLY_TOWARD_XZ_CLUSTER_DELTA/2};
|
||||
} else {
|
||||
flyingToward = { x: averagePosition.x + (Math.random() * FLY_TOWARD_XZ_DISTANCE) - FLY_TOWARD_XZ_DISTANCE/2,
|
||||
y: averagePosition.y + (Math.random() * FLY_TOWARD_Y_DISTANCE) - FLY_TOWARD_Y_DISTANCE/2,
|
||||
z: averagePosition.z + (Math.random() * FLY_TOWARD_XZ_DISTANCE) - FLY_TOWARD_XZ_DISTANCE/2};
|
||||
}
|
||||
birds[i].flyingToward = flyingToward;
|
||||
birds[i].isFlyingToward = true;
|
||||
} else {
|
||||
birds[i].flyingToward = {};
|
||||
birds[i].isFlyingToward = false;
|
||||
}
|
||||
|
||||
// keep track of our bookkeeping
|
||||
if (!wasFlyingTo && birds[i].isFlyingToward) {
|
||||
flyingToCount++;
|
||||
}
|
||||
if (wasFlyingTo && !birds[i].isFlyingToward) {
|
||||
flyingToCount--;
|
||||
}
|
||||
print(">>>> CHANGING flyingToCount="+flyingToCount);
|
||||
if (birds[i].isFlyingToward) {
|
||||
Vec3.print("... now birds["+i+"].flyingToward=", birds[i].flyingToward);
|
||||
}
|
||||
}
|
||||
|
||||
// actually apply the thrust after all that
|
||||
if (birds[i].isFlyingToward) {
|
||||
var flyTowardDelta = Vec3.subtract(birds[i].flyingToward, birds[i].properties.position);
|
||||
var thrustTowardFlyTo = Vec3.multiply(flyTowardDelta, 1/flyToFrames);
|
||||
birds[i].thrust = Vec3.sum(birds[i].thrust, thrustTowardFlyTo);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// adjust thrust to avoid dropping to fast
|
||||
if (enableAvoidDropping) {
|
||||
if (birds[i].gliding) {
|
||||
if (birds[i].properties.velocity.y < droppingTooFast) {
|
||||
birds[i].gliding = false; // leave thrusting against gravity till it gets too high
|
||||
//print("birdGliding["+i+"]=false <<<< try to conteract gravity <<<<<<<<<<<<<<<<<<<<");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// if the bird is currently not gliding, check to see if it's rising too fast
|
||||
if (!birds[i].gliding && birds[i].properties.velocity.y > risingTooFast) {
|
||||
//Vec3.print("bird rising too fast will glide bird["+i+"]=",birds[i].properties.velocity.y);
|
||||
birds[i].gliding = true;
|
||||
}
|
||||
|
||||
// adjust thrust to avoid minHeight, we don't care about rising too fast in this case, so we do it
|
||||
// after the rising too fast check
|
||||
if (enableAvoidMinHeight) {
|
||||
if (birds[i].properties.position.y < minHeight) {
|
||||
//Vec3.print("**** enableAvoidMinHeight... enable thrust against gravity... bird["+i+"].position=",birds[i].properties.position);
|
||||
birds[i].gliding = false;
|
||||
}
|
||||
}
|
||||
|
||||
// adjust thrust to avoid maxHeight
|
||||
if (enableAvoidMaxHeight) {
|
||||
if (birds[i].properties.position.y > maxHeight) {
|
||||
//Vec3.print("********************* bird above max height will glide bird["+i+"].position=",birds[i].properties.position);
|
||||
birds[i].gliding = true;
|
||||
}
|
||||
}
|
||||
|
||||
// if the bird is currently not gliding, then it is applying a thrust upward against gravity
|
||||
if (!birds[i].gliding) {
|
||||
// as long as we're not rising too fast, keep thrusting...
|
||||
if (birds[i].properties.velocity.y < risingTooFast) {
|
||||
var thrustAdjust = {x: 0, y: (flockGravity.y * upwardThrustAgainstGravity), z: 0};
|
||||
//Vec3.print("bird fighting gravity thrustAdjust for bird["+i+"]=",thrustAdjust);
|
||||
birds[i].thrust = Vec3.sum(birds[i].thrust, thrustAdjust);
|
||||
} else {
|
||||
//print("%%% non-gliding bird, thrusting too much...");
|
||||
}
|
||||
}
|
||||
|
||||
if (enableRandomXZThrust && birds[i].isLeading) {
|
||||
birds[i].xzThrustCount++;
|
||||
|
||||
// we will randomly decide to enable XY thrust, in which case we will set the thrust and leave it
|
||||
// that way till we randomly shut it off.
|
||||
|
||||
// if we don't have a thrust, check against probability of starting it, and create a random thrust if
|
||||
// probability occurs
|
||||
if (Vec3.length(birds[i].xzThrust) == 0) {
|
||||
var probabilityToStart = (PROBABILITY_OF_STARTING_XZ_THRUST * birds[i].xzThrustCount);
|
||||
//print("probabilityToStart=" + probabilityToStart);
|
||||
if (Math.random() < probabilityToStart) {
|
||||
var xThrust = (Math.random() * (RANDOM_XZ_THRUST_SCALE * 2)) - RANDOM_XZ_THRUST_SCALE;
|
||||
var zThrust = (Math.random() * (RANDOM_XZ_THRUST_SCALE * 2)) - RANDOM_XZ_THRUST_SCALE;
|
||||
|
||||
birds[i].xzThrust = { x: zThrust, y: 0, z: zThrust };
|
||||
birds[i].xzThrustCount = 0;
|
||||
//Vec3.print(">>>>>>>>>> STARTING XY THRUST birdXYthrust["+i+"]=", birds[i].xzThrust);
|
||||
}
|
||||
}
|
||||
|
||||
// if we're thrusting... then check for probability of stopping
|
||||
if (Vec3.length(birds[i].xzThrust)) {
|
||||
var probabilityToStop = (PROBABILITY_OF_STOPPING_XZ_THRUST * birds[i].xzThrustCount);
|
||||
//print("probabilityToStop=" + probabilityToStop);
|
||||
if (Math.random() < probabilityToStop) {
|
||||
birds[i].xzThrust = { x: 0, y: 0, z: 0};
|
||||
//Vec3.print(">>>>>>>>>> STOPPING XY THRUST birdXYthrust["+i+"]=", birds[i].xzThrust);
|
||||
birds[i].xzThrustCount = 0;
|
||||
}
|
||||
|
||||
if (birds[i].properties.velocity.x > MAX_XY_VELOCITY) {
|
||||
birds[i].xzThrust.x = 0;
|
||||
//Vec3.print(">>>>>>>>>> CLEARING X THRUST birdXYthrust["+i+"]=", birds[i].xzThrust);
|
||||
}
|
||||
if (birds[i].properties.velocity.z > MAX_XY_VELOCITY) {
|
||||
birds[i].xzThrust.z = 0;
|
||||
//Vec3.print(">>>>>>>>>> CLEARING Y THRUST birdXYthrust["+i+"]=", birds[i].xzThrust);
|
||||
}
|
||||
|
||||
if (Vec3.length(birds[i].xzThrust)) {
|
||||
birds[i].thrust = Vec3.sum(birds[i].thrust, birds[i].xzThrust);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// adjust thrust to move their velocity toward average flock velocity
|
||||
if (enableMatchFlockVelocity) {
|
||||
if (birds[i].isLeading) {
|
||||
print("this bird is leading... i="+i);
|
||||
} else {
|
||||
var velocityDelta = Vec3.subtract(averageVelocity, birds[i].properties.velocity);
|
||||
var thrustAdjust = velocityDelta;
|
||||
birds[i].thrust = Vec3.sum(birds[i].thrust, thrustAdjust);
|
||||
}
|
||||
}
|
||||
|
||||
// adjust thrust to move their velocity toward average flock position
|
||||
if (enableThrustTowardCenter) {
|
||||
if (birds[i].isLeading) {
|
||||
print("this bird is leading... i="+i);
|
||||
} else {
|
||||
var positionDelta = Vec3.subtract(averagePosition, birds[i].properties.position);
|
||||
var thrustTowardCenter = Vec3.multiply(positionDelta, 1/adjustFrames);
|
||||
birds[i].thrust = Vec3.sum(birds[i].thrust, thrustTowardCenter);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// adjust thrust to avoid other birds
|
||||
if (enableAvoidOtherBirds) {
|
||||
var sumThrustThisBird = { x: 0, y: 0, z: 0 };
|
||||
|
||||
for(var j =0; j < birdsInFlock; j++) {
|
||||
|
||||
// if this is not me, and a known bird, then check our position
|
||||
if (birds[i].properties.isKnownID && j != i) {
|
||||
var positionMe = birds[i].properties.position;
|
||||
var positionYou = birds[j].properties.position;
|
||||
var awayFromYou = Vec3.subtract(positionMe, positionYou); // vector pointing away from "you"
|
||||
var distance = Vec3.length(awayFromYou);
|
||||
if (distance < tooClose) {
|
||||
// NOTE: this was Philip's recommendation for "avoiding" other birds...
|
||||
// Vme -= Vec3.multiply(Vme, normalize(PositionMe - PositionYou))
|
||||
//
|
||||
// But it doesn't seem to work... Here's my JS implementation...
|
||||
//
|
||||
// var velocityMe = birds[i].properties.velocity;
|
||||
// var thrustAdjust = Vec3.cross(velocityMe, Vec3.normalize(awayFromYou));
|
||||
// sumThrustThisBird = Vec3.sum(sumThrustThisBird, thrustAdjust);
|
||||
//
|
||||
// Instead I just apply a thrust equal to the vector away from all the birds
|
||||
sumThrustThisBird = Vec3.sum(sumThrustThisBird, awayFromYou);
|
||||
}
|
||||
}
|
||||
}
|
||||
birds[i].thrust = Vec3.sum(birds[i].thrust, sumThrustThisBird);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// iterate all birds again, apply their thrust
|
||||
for(var i =0; i < birdsInFlock; i++) {
|
||||
if (birds[i].particle.isKnownID) {
|
||||
|
||||
var color;
|
||||
if (birds[i].gliding) {
|
||||
color = glidingColor;
|
||||
} else {
|
||||
color = thrustUpwardColor;
|
||||
}
|
||||
if (Vec3.length(birds[i].xzThrust)) {
|
||||
color = Vec3.sum(color, thrustXYColor);
|
||||
}
|
||||
|
||||
var velocityMe = birds[i].properties.velocity;
|
||||
// add thrust to velocity
|
||||
var newVelocity = Vec3.sum(velocityMe, Vec3.multiply(birds[i].thrust, deltaTime));
|
||||
|
||||
if (birds[i].isLeading || birds[i].isFlyingToward) {
|
||||
Vec3.print("this bird is leading/flying toward... i="+i+" velocity=",newVelocity);
|
||||
color = leadingOrflyToColor;
|
||||
}
|
||||
|
||||
if (wantDebug) {
|
||||
Vec3.print("birds["+i+"].position=", birds[i].properties.position);
|
||||
Vec3.print("birds["+i+"].oldVelocity=", velocityMe);
|
||||
Vec3.print("birdThrusts["+i+"]=", birds[i].thrust);
|
||||
Vec3.print("birds["+i+"].newVelocity=", newVelocity);
|
||||
}
|
||||
|
||||
birds[i].particle = Particles.editParticle(birds[i].particle,{ velocity: newVelocity, color: color });
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
createBirds();
|
||||
// register the call back for simulation loop
|
||||
Script.update.connect(updateBirds);
|
||||
|
|
@ -41,7 +41,7 @@ var position = { x: 5.0, y: 0.6, z: 5.0 };
|
|||
Voxels.setVoxel(position.x, 0, position.z, 0.5, 0, 0, 255);
|
||||
|
||||
var totalParticles = 0;
|
||||
function makeFountain() {
|
||||
function makeFountain(deltaTime) {
|
||||
if (Math.random() < 0.10) {
|
||||
//print("Made particle!\n");
|
||||
var properties = {
|
||||
|
@ -64,4 +64,4 @@ function makeFountain() {
|
|||
}
|
||||
}
|
||||
// register the call back so it fires before each data send
|
||||
Script.willSendVisualDataCallback.connect(makeFountain);
|
||||
Script.update.connect(makeFountain);
|
|
@ -114,7 +114,7 @@ function sendNextCells() {
|
|||
|
||||
var sentFirstBoard = false;
|
||||
|
||||
function step() {
|
||||
function step(deltaTime) {
|
||||
if (sentFirstBoard) {
|
||||
// we've already sent the first full board, perform a step in time
|
||||
updateCells();
|
||||
|
@ -127,6 +127,6 @@ function step() {
|
|||
}
|
||||
|
||||
print("here");
|
||||
Script.willSendVisualDataCallback.connect(step);
|
||||
Script.update.connect(step);
|
||||
Voxels.setPacketsPerSecond(200);
|
||||
print("now here");
|
||||
|
|
|
@ -10,17 +10,23 @@
|
|||
//
|
||||
|
||||
|
||||
function particleCollisionWithVoxel(particle, voxel) {
|
||||
print("hello...");
|
||||
|
||||
function particleCollisionWithVoxel(particle, voxel, penetration) {
|
||||
print("particleCollisionWithVoxel()..");
|
||||
print(" particle.getID()=" + particle.id);
|
||||
print(" voxel color...=" + voxel.red + ", " + voxel.green + ", " + voxel.blue);
|
||||
Vec3.print('penetration=', penetration);
|
||||
}
|
||||
|
||||
function particleCollisionWithParticle(particleA, particleB) {
|
||||
function particleCollisionWithParticle(particleA, particleB, penetration) {
|
||||
print("particleCollisionWithParticle()..");
|
||||
print(" particleA.getID()=" + particleA.id);
|
||||
print(" particleB.getID()=" + particleB.id);
|
||||
Vec3.print('penetration=', penetration);
|
||||
}
|
||||
|
||||
Particles.particleCollisionWithVoxel.connect(particleCollisionWithVoxel);
|
||||
Particles.particleCollisionWithParticle.connect(particleCollisionWithParticle);
|
||||
|
||||
print("here... hello...");
|
||||
|
|
|
@ -61,7 +61,8 @@ function shootBullet(position, velocity) {
|
|||
Audio.playSound(fireSound, audioOptions);
|
||||
}
|
||||
|
||||
function particleCollisionWithVoxel(particle, voxel) {
|
||||
function particleCollisionWithVoxel(particle, voxel, penetration) {
|
||||
Vec3.print('particleCollisionWithVoxel() ... penetration=', penetration);
|
||||
var HOLE_SIZE = 0.125;
|
||||
var particleProperties = Particles.getParticleProperties(particle);
|
||||
var position = particleProperties.position;
|
||||
|
@ -73,7 +74,7 @@ function particleCollisionWithVoxel(particle, voxel) {
|
|||
Audio.playSound(impactSound, audioOptions);
|
||||
}
|
||||
|
||||
function update() {
|
||||
function update(deltaTime) {
|
||||
|
||||
// Check for mouseLook movement, update rotation
|
||||
// rotate body yaw for yaw received from mouse
|
||||
|
@ -178,7 +179,7 @@ function scriptEnding() {
|
|||
|
||||
Particles.particleCollisionWithVoxel.connect(particleCollisionWithVoxel);
|
||||
Script.scriptEnding.connect(scriptEnding);
|
||||
Script.willSendVisualDataCallback.connect(update);
|
||||
Script.update.connect(update);
|
||||
Controller.mousePressEvent.connect(mousePressEvent);
|
||||
Controller.mouseReleaseEvent.connect(mouseReleaseEvent);
|
||||
Controller.mouseMoveEvent.connect(mouseMoveEvent);
|
||||
|
|
|
@ -29,7 +29,6 @@ var grabbingWithLeftHand = false;
|
|||
var wasGrabbingWithLeftHand = false;
|
||||
var EPSILON = 0.000001;
|
||||
var velocity = { x: 0, y: 0, z: 0};
|
||||
var deltaTime = 1/60; // approximately our FPS - maybe better to be elapsed time since last call
|
||||
var THRUST_MAG_UP = 800.0;
|
||||
var THRUST_MAG_DOWN = 300.0;
|
||||
var THRUST_MAG_FWD = 500.0;
|
||||
|
@ -77,7 +76,7 @@ function getAndResetGrabRotation() {
|
|||
}
|
||||
|
||||
// handles all the grab related behavior: position (crawl), velocity (flick), and rotate (twist)
|
||||
function handleGrabBehavior() {
|
||||
function handleGrabBehavior(deltaTime) {
|
||||
// check for and handle grab behaviors
|
||||
grabbingWithRightHand = Controller.isButtonPressed(RIGHT_BUTTON_4);
|
||||
grabbingWithLeftHand = Controller.isButtonPressed(LEFT_BUTTON_4);
|
||||
|
@ -156,7 +155,7 @@ function handleGrabBehavior() {
|
|||
}
|
||||
|
||||
// Main update function that handles flying and grabbing behaviort
|
||||
function flyWithHydra() {
|
||||
function flyWithHydra(deltaTime) {
|
||||
var thrustJoystickPosition = Controller.getJoystickPosition(THRUST_CONTROLLER);
|
||||
|
||||
if (thrustJoystickPosition.x != 0 || thrustJoystickPosition.y != 0) {
|
||||
|
@ -193,10 +192,10 @@ function flyWithHydra() {
|
|||
var newPitch = MyAvatar.headPitch + (viewJoystickPosition.y * JOYSTICK_PITCH_MAG * deltaTime);
|
||||
MyAvatar.headPitch = newPitch;
|
||||
}
|
||||
handleGrabBehavior();
|
||||
handleGrabBehavior(deltaTime);
|
||||
}
|
||||
|
||||
Script.willSendVisualDataCallback.connect(flyWithHydra);
|
||||
Script.update.connect(flyWithHydra);
|
||||
Controller.captureJoystick(THRUST_CONTROLLER);
|
||||
Controller.captureJoystick(VIEW_CONTROLLER);
|
||||
|
||||
|
|
|
@ -45,13 +45,13 @@ function releaseMovementKeys() {
|
|||
}
|
||||
|
||||
var cameraPosition = Camera.getPosition();
|
||||
function moveCamera() {
|
||||
function moveCamera(update) {
|
||||
if (lookingAtSomething) {
|
||||
Camera.setPosition(cameraPosition);
|
||||
}
|
||||
}
|
||||
|
||||
Script.willSendVisualDataCallback.connect(moveCamera);
|
||||
Script.update.connect(moveCamera);
|
||||
|
||||
|
||||
function mousePressEvent(event) {
|
||||
|
|
|
@ -49,7 +49,7 @@ function mouseMoveEvent(event) {
|
|||
}
|
||||
}
|
||||
|
||||
function update() {
|
||||
function update(deltaTime) {
|
||||
if (wantDebugging) {
|
||||
print("update()...");
|
||||
}
|
||||
|
@ -91,5 +91,5 @@ MyAvatar.bodyPitch = 0;
|
|||
MyAvatar.bodyRoll = 0;
|
||||
|
||||
// would be nice to change to update
|
||||
Script.willSendVisualDataCallback.connect(update);
|
||||
Script.update.connect(update);
|
||||
Script.scriptEnding.connect(scriptEnding);
|
||||
|
|
|
@ -43,7 +43,7 @@ function touchUpdateEvent(event) {
|
|||
lastY = event.y;
|
||||
}
|
||||
|
||||
function update() {
|
||||
function update(deltaTime) {
|
||||
// rotate body yaw for yaw received from mouse
|
||||
var newOrientation = Quat.multiply(MyAvatar.orientation, Quat.fromVec3( { x: 0, y: yawFromMouse, z: 0 } ));
|
||||
if (wantDebugging) {
|
||||
|
@ -82,5 +82,5 @@ MyAvatar.bodyPitch = 0;
|
|||
MyAvatar.bodyRoll = 0;
|
||||
|
||||
// would be nice to change to update
|
||||
Script.willSendVisualDataCallback.connect(update);
|
||||
Script.update.connect(update);
|
||||
Script.scriptEnding.connect(scriptEnding);
|
||||
|
|
|
@ -12,7 +12,7 @@ var colorEdge = { r:255, g:250, b:175 };
|
|||
var frame = 0;
|
||||
var thisColor = color;
|
||||
|
||||
function moveVoxel() {
|
||||
function moveVoxel(deltaTime) {
|
||||
frame++;
|
||||
if (frame % 3 == 0) {
|
||||
// Get a new position
|
||||
|
@ -41,4 +41,4 @@ function moveVoxel() {
|
|||
|
||||
Voxels.setPacketsPerSecond(300);
|
||||
// Connect a call back that happens every frame
|
||||
Script.willSendVisualDataCallback.connect(moveVoxel);
|
||||
Script.update.connect(moveVoxel);
|
|
@ -90,7 +90,7 @@ Controller.touchEndEvent.connect(touchEndEvent);
|
|||
|
||||
|
||||
|
||||
function update() {
|
||||
function update(deltaTime) {
|
||||
// rotate body yaw for yaw received from multitouch rotate
|
||||
var newOrientation = Quat.multiply(MyAvatar.orientation, Quat.fromVec3( { x: 0, y: yawFromMultiTouch, z: 0 } ));
|
||||
if (wantDebugging) {
|
||||
|
@ -110,7 +110,7 @@ function update() {
|
|||
MyAvatar.headPitch = newPitch;
|
||||
pitchFromMultiTouch = 0;
|
||||
}
|
||||
Script.willSendVisualDataCallback.connect(update);
|
||||
Script.update.connect(update);
|
||||
|
||||
|
||||
function scriptEnding() {
|
||||
|
|
|
@ -186,7 +186,7 @@ var toolAVisible = false;
|
|||
var count = 0;
|
||||
|
||||
// Our update() function is called at approximately 60fps, and we will use it to animate our various overlays
|
||||
function update() {
|
||||
function update(deltaTime) {
|
||||
count++;
|
||||
|
||||
// every second or so, toggle the visibility our our blinking tool
|
||||
|
@ -226,7 +226,7 @@ function update() {
|
|||
// update our 3D line to go from origin to our avatar's position
|
||||
Overlays.editOverlay(line3d, { end: MyAvatar.position } );
|
||||
}
|
||||
Script.willSendVisualDataCallback.connect(update);
|
||||
Script.update.connect(update);
|
||||
|
||||
|
||||
// The slider is handled in the mouse event callbacks.
|
||||
|
|
|
@ -13,7 +13,7 @@ for (t = 0; t < numberOfTriggers; t++) {
|
|||
triggerPulled[t] = false;
|
||||
}
|
||||
|
||||
function checkController() {
|
||||
function checkController(deltaTime) {
|
||||
var numberOfTriggers = Controller.getNumberOfTriggers();
|
||||
var numberOfSpatialControls = Controller.getNumberOfSpatialControls();
|
||||
var controllersPerTrigger = numberOfSpatialControls / numberOfTriggers;
|
||||
|
@ -62,8 +62,9 @@ function checkController() {
|
|||
|
||||
// This is the script for the particles that this gun shoots.
|
||||
var script =
|
||||
" function collisionWithVoxel(voxel) { " +
|
||||
" function collisionWithVoxel(voxel, penetration) { " +
|
||||
" print('collisionWithVoxel(voxel)... '); " +
|
||||
" Vec3.print('penetration=', penetration); " +
|
||||
" print('myID=' + Particle.getID() + '\\n'); " +
|
||||
" var voxelColor = { red: voxel.red, green: voxel.green, blue: voxel.blue };" +
|
||||
" var voxelAt = { x: voxel.x, y: voxel.y, z: voxel.z };" +
|
||||
|
@ -93,4 +94,4 @@ function checkController() {
|
|||
|
||||
|
||||
// register the call back so it fires before each data send
|
||||
Script.willSendVisualDataCallback.connect(checkController);
|
||||
Script.update.connect(checkController);
|
||||
|
|
|
@ -99,7 +99,7 @@ var properties = {
|
|||
var range = 1.0; // Distance around avatar where I can move
|
||||
// Create the actual bird
|
||||
var particleID = Particles.addParticle(properties);
|
||||
function moveBird() {
|
||||
function moveBird(deltaTime) {
|
||||
|
||||
// check to see if we've been running long enough that our bird is dead
|
||||
var nowTimeInSeconds = new Date().getTime() / 1000;
|
||||
|
@ -194,4 +194,4 @@ function moveBird() {
|
|||
}
|
||||
|
||||
// register the call back so it fires before each data send
|
||||
Script.willSendVisualDataCallback.connect(moveBird);
|
||||
Script.update.connect(moveBird);
|
||||
|
|
|
@ -47,7 +47,7 @@ var modelAParticleID = Particles.addParticle(modelPropertiesA);
|
|||
var modelBParticleID = Particles.addParticle(modelPropertiesB);
|
||||
var ballParticleID = Particles.addParticle(ballProperties);
|
||||
|
||||
function endAfterAWhile() {
|
||||
function endAfterAWhile(deltaTime) {
|
||||
// stop it...
|
||||
if (count >= stopAfter) {
|
||||
print("calling Script.stop()");
|
||||
|
@ -60,5 +60,5 @@ function endAfterAWhile() {
|
|||
|
||||
|
||||
// register the call back so it fires before each data send
|
||||
Script.willSendVisualDataCallback.connect(endAfterAWhile);
|
||||
Script.update.connect(endAfterAWhile);
|
||||
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
// First, load the clap sound from a URL
|
||||
var clap = new Sound("https://s3-us-west-1.amazonaws.com/highfidelity-public/sounds/Animals/bushtit_1.raw");
|
||||
|
||||
function maybePlaySound() {
|
||||
function maybePlaySound(deltaTime) {
|
||||
if (Math.random() < 0.01) {
|
||||
// Set the location and other info for the sound to play
|
||||
var options = new AudioInjectionOptions();
|
||||
|
@ -17,4 +17,4 @@ function maybePlaySound() {
|
|||
}
|
||||
|
||||
// Connect a call back that happens every frame
|
||||
Script.willSendVisualDataCallback.connect(maybePlaySound);
|
||||
Script.update.connect(maybePlaySound);
|
|
@ -122,7 +122,7 @@ var velocity;
|
|||
var hueAngle = 0;
|
||||
var smoothedOffset;
|
||||
|
||||
function step() {
|
||||
function step(deltaTime) {
|
||||
if (stateHistory.length === 0) {
|
||||
// start at a random position within the bounds, with a random velocity
|
||||
position = randomVector(BOUNDS_MIN, BOUNDS_MAX);
|
||||
|
@ -170,4 +170,4 @@ function step() {
|
|||
hueAngle = (hueAngle + 1) % MAX_HUE_ANGLE;
|
||||
}
|
||||
|
||||
Script.willSendVisualDataCallback.connect(step);
|
||||
Script.update.connect(step);
|
||||
|
|
|
@ -22,7 +22,7 @@ var particleA = Particles.addParticle(
|
|||
lifetime: (lengthOfRide * 60) + 1
|
||||
});
|
||||
|
||||
function rideWithParticle() {
|
||||
function rideWithParticle(deltaTime) {
|
||||
|
||||
if (iteration <= lengthOfRide) {
|
||||
|
||||
|
@ -46,5 +46,5 @@ function rideWithParticle() {
|
|||
|
||||
|
||||
// register the call back so it fires before each data send
|
||||
Script.willSendVisualDataCallback.connect(rideWithParticle);
|
||||
Script.update.connect(rideWithParticle);
|
||||
|
||||
|
|
|
@ -31,7 +31,7 @@ function init() {
|
|||
}
|
||||
}
|
||||
|
||||
function keepLooking() {
|
||||
function keepLooking(deltaTime) {
|
||||
//print("count =" + count);
|
||||
|
||||
if (count == 0) {
|
||||
|
@ -63,7 +63,7 @@ function scriptEnding() {
|
|||
}
|
||||
|
||||
// register the call back so it fires before each data send
|
||||
Script.willSendVisualDataCallback.connect(keepLooking);
|
||||
Script.update.connect(keepLooking);
|
||||
|
||||
// register our scriptEnding callback
|
||||
Script.scriptEnding.connect(scriptEnding);
|
||||
|
|
|
@ -207,7 +207,7 @@ function displayGameOver() {
|
|||
print("Game over...");
|
||||
}
|
||||
|
||||
function update() {
|
||||
function update(deltaTime) {
|
||||
if (!gameOver) {
|
||||
//print("updating space invaders... iteration="+iteration);
|
||||
iteration++;
|
||||
|
@ -257,7 +257,7 @@ function update() {
|
|||
}
|
||||
|
||||
// register the call back so it fires before each data send
|
||||
Script.willSendVisualDataCallback.connect(update);
|
||||
Script.update.connect(update);
|
||||
|
||||
function cleanupGame() {
|
||||
print("cleaning up game...");
|
||||
|
@ -392,8 +392,9 @@ function deleteIfInvader(possibleInvaderParticle) {
|
|||
}
|
||||
}
|
||||
|
||||
function particleCollisionWithParticle(particleA, particleB) {
|
||||
function particleCollisionWithParticle(particleA, particleB, penetration) {
|
||||
print("particleCollisionWithParticle() a.id="+particleA.id + " b.id=" + particleB.id);
|
||||
Vec3.print('particleCollisionWithParticle() penetration=', penetration);
|
||||
if (missileFired) {
|
||||
myMissile = Particles.identifyParticle(myMissile);
|
||||
if (myMissile.id == particleA.id) {
|
||||
|
|
|
@ -208,7 +208,7 @@ function checkControllerSide(whichSide) {
|
|||
}
|
||||
|
||||
|
||||
function checkController() {
|
||||
function checkController(deltaTime) {
|
||||
var numberOfButtons = Controller.getNumberOfButtons();
|
||||
var numberOfTriggers = Controller.getNumberOfTriggers();
|
||||
var numberOfSpatialControls = Controller.getNumberOfSpatialControls();
|
||||
|
@ -226,4 +226,4 @@ function checkController() {
|
|||
|
||||
|
||||
// register the call back so it fires before each data send
|
||||
Script.willSendVisualDataCallback.connect(checkController);
|
||||
Script.update.connect(checkController);
|
||||
|
|
|
@ -73,7 +73,7 @@ var moved = true;
|
|||
var CHANCE_OF_MOVING = 0.05;
|
||||
var CHANCE_OF_TWEETING = 0.05;
|
||||
|
||||
function moveBird() {
|
||||
function moveBird(deltaTime) {
|
||||
frame++;
|
||||
if (frame % 3 == 0) {
|
||||
// Tweeting behavior
|
||||
|
@ -130,4 +130,4 @@ function moveBird() {
|
|||
|
||||
Voxels.setPacketsPerSecond(10000);
|
||||
// Connect a call back that happens every frame
|
||||
Script.willSendVisualDataCallback.connect(moveBird);
|
||||
Script.update.connect(moveBird);
|
|
@ -70,14 +70,13 @@ function clamp(valueToClamp, minValue, maxValue) {
|
|||
return Math.max(minValue, Math.min(maxValue, valueToClamp));
|
||||
}
|
||||
|
||||
function produceCollisionSound(palm, voxelDetail) {
|
||||
function produceCollisionSound(deltaTime, palm, voxelDetail) {
|
||||
// Collision between finger and a voxel plays sound
|
||||
|
||||
var palmVelocity = Controller.getSpatialControlVelocity(palm * 2);
|
||||
var speed = Vec3.length(palmVelocity);
|
||||
var fingerTipPosition = Controller.getSpatialControlPosition(palm * 2 + 1);
|
||||
|
||||
var deltaTime = 1/60; //close enough
|
||||
var LOWEST_FREQUENCY = 100.0;
|
||||
var HERTZ_PER_RGB = 3.0;
|
||||
var DECAY_PER_SAMPLE = 0.0005;
|
||||
|
@ -97,9 +96,7 @@ function produceCollisionSound(palm, voxelDetail) {
|
|||
Audio.startDrumSound(volume, frequency, DURATION_MAX, DECAY_PER_SAMPLE, audioOptions);
|
||||
}
|
||||
|
||||
function update() {
|
||||
var deltaTime = 1/60; //close enough
|
||||
|
||||
function update(deltaTime) {
|
||||
// Voxel Drumming with fingertips if enabled
|
||||
if (Menu.isOptionChecked("Voxel Drumming")) {
|
||||
|
||||
|
@ -111,7 +108,7 @@ function update() {
|
|||
if (!isColliding[palm]) {
|
||||
// Collision has just started
|
||||
isColliding[palm] = true;
|
||||
produceCollisionSound(palm, voxel);
|
||||
produceCollisionSound(deltaTime, palm, voxel);
|
||||
|
||||
// Set highlight voxel
|
||||
Overlays.editOverlay(highlightVoxel,
|
||||
|
@ -156,7 +153,7 @@ function update() {
|
|||
} // palm loop
|
||||
} // menu item check
|
||||
}
|
||||
Script.willSendVisualDataCallback.connect(update);
|
||||
Script.update.connect(update);
|
||||
|
||||
function scriptEnding() {
|
||||
Overlays.deleteOverlay(highlightVoxel);
|
||||
|
|
|
@ -1562,14 +1562,14 @@ void Application::init() {
|
|||
|
||||
// connect the _particleCollisionSystem to our script engine's ParticleScriptingInterface
|
||||
connect(&_particleCollisionSystem,
|
||||
SIGNAL(particleCollisionWithVoxel(const ParticleID&, const VoxelDetail&)),
|
||||
SIGNAL(particleCollisionWithVoxel(const ParticleID&, const VoxelDetail&, const glm::vec3&)),
|
||||
ScriptEngine::getParticlesScriptingInterface(),
|
||||
SLOT(forwardParticleCollisionWithVoxel(const ParticleID&, const VoxelDetail&)));
|
||||
SLOT(forwardParticleCollisionWithVoxel(const ParticleID&, const VoxelDetail&, const glm::vec3&)));
|
||||
|
||||
connect(&_particleCollisionSystem,
|
||||
SIGNAL(particleCollisionWithParticle(const ParticleID&, const ParticleID&)),
|
||||
SIGNAL(particleCollisionWithParticle(const ParticleID&, const ParticleID&, const glm::vec3&)),
|
||||
ScriptEngine::getParticlesScriptingInterface(),
|
||||
SLOT(forwardParticleCollisionWithParticle(const ParticleID&, const ParticleID&)));
|
||||
SLOT(forwardParticleCollisionWithParticle(const ParticleID&, const ParticleID&, const glm::vec3&)));
|
||||
|
||||
_pieMenu.init("./resources/images/hifi-interface-tools-v2-pie.svg",
|
||||
_glWidget->width(),
|
||||
|
@ -2383,8 +2383,8 @@ void Application::displaySide(Camera& whichCamera, bool selfAvatarOnly) {
|
|||
glMaterialfv(GL_FRONT, GL_SPECULAR, WHITE_SPECULAR_COLOR);
|
||||
}
|
||||
|
||||
bool forceRenderMyHead = (whichCamera.getInterpolatedMode() == CAMERA_MODE_MIRROR);
|
||||
_avatarManager.renderAvatars(forceRenderMyHead, selfAvatarOnly);
|
||||
bool mirrorMode = (whichCamera.getInterpolatedMode() == CAMERA_MODE_MIRROR);
|
||||
_avatarManager.renderAvatars(mirrorMode, selfAvatarOnly);
|
||||
|
||||
if (!selfAvatarOnly) {
|
||||
// Render the world box
|
||||
|
|
|
@ -201,7 +201,7 @@ static TextRenderer* textRenderer(TextRendererType type) {
|
|||
return displayNameRenderer;
|
||||
}
|
||||
|
||||
void Avatar::render() {
|
||||
void Avatar::render(bool forShadowMap) {
|
||||
glm::vec3 toTarget = _position - Application::getInstance()->getAvatar()->getPosition();
|
||||
float lengthToTarget = glm::length(toTarget);
|
||||
|
||||
|
@ -209,7 +209,7 @@ void Avatar::render() {
|
|||
// glow when moving in the distance
|
||||
|
||||
const float GLOW_DISTANCE = 5.0f;
|
||||
Glower glower(_moving && lengthToTarget > GLOW_DISTANCE ? 1.0f : 0.0f);
|
||||
Glower glower(_moving && lengthToTarget > GLOW_DISTANCE && !forShadowMap ? 1.0f : 0.0f);
|
||||
|
||||
// render body
|
||||
if (Menu::getInstance()->isOptionChecked(MenuOption::RenderSkeletonCollisionProxies)) {
|
||||
|
@ -233,7 +233,7 @@ void Avatar::render() {
|
|||
float angle = abs(angleBetween(toTarget + delta, toTarget - delta));
|
||||
float sphereRadius = getHead()->getAverageLoudness() * SPHERE_LOUDNESS_SCALING;
|
||||
|
||||
if ((sphereRadius > MIN_SPHERE_SIZE) && (angle < MAX_SPHERE_ANGLE) && (angle > MIN_SPHERE_ANGLE)) {
|
||||
if (!forShadowMap && (sphereRadius > MIN_SPHERE_SIZE) && (angle < MAX_SPHERE_ANGLE) && (angle > MIN_SPHERE_ANGLE)) {
|
||||
glColor4f(SPHERE_COLOR[0], SPHERE_COLOR[1], SPHERE_COLOR[2], 1.f - angle / MAX_SPHERE_ANGLE);
|
||||
glPushMatrix();
|
||||
glTranslatef(_position.x, _position.y, _position.z);
|
||||
|
@ -243,7 +243,10 @@ void Avatar::render() {
|
|||
}
|
||||
}
|
||||
const float DISPLAYNAME_DISTANCE = 10.0f;
|
||||
setShowDisplayName(lengthToTarget < DISPLAYNAME_DISTANCE);
|
||||
setShowDisplayName(!forShadowMap && lengthToTarget < DISPLAYNAME_DISTANCE);
|
||||
if (forShadowMap) {
|
||||
return;
|
||||
}
|
||||
renderDisplayName();
|
||||
|
||||
if (!_chatMessage.empty()) {
|
||||
|
@ -484,10 +487,19 @@ bool Avatar::findRayIntersection(const glm::vec3& origin, const glm::vec3& direc
|
|||
|
||||
bool Avatar::findSphereCollisions(const glm::vec3& penetratorCenter, float penetratorRadius,
|
||||
CollisionList& collisions, int skeletonSkipIndex) {
|
||||
// Temporarily disabling collisions against the skeleton because the collision proxies up
|
||||
// near the neck are bad and prevent the hand from hitting the face.
|
||||
//return _skeletonModel.findSphereCollisions(penetratorCenter, penetratorRadius, collisions, 1.0f, skeletonSkipIndex);
|
||||
return getHead()->getFaceModel().findSphereCollisions(penetratorCenter, penetratorRadius, collisions);
|
||||
return _skeletonModel.findSphereCollisions(penetratorCenter, penetratorRadius, collisions, skeletonSkipIndex);
|
||||
// Temporarily disabling collisions against the head because most of its collision proxies are bad.
|
||||
//return getHead()->getFaceModel().findSphereCollisions(penetratorCenter, penetratorRadius, collisions);
|
||||
}
|
||||
|
||||
bool Avatar::findCollisions(const QVector<const Shape*>& shapes, CollisionList& collisions) {
|
||||
_skeletonModel.updateShapePositions();
|
||||
bool collided = _skeletonModel.findCollisions(shapes, collisions);
|
||||
|
||||
Model& headModel = getHead()->getFaceModel();
|
||||
headModel.updateShapePositions();
|
||||
collided = headModel.findCollisions(shapes, collisions);
|
||||
return collided;
|
||||
}
|
||||
|
||||
bool Avatar::findParticleCollisions(const glm::vec3& particleCenter, float particleRadius, CollisionList& collisions) {
|
||||
|
|
|
@ -74,7 +74,7 @@ public:
|
|||
|
||||
void init();
|
||||
void simulate(float deltaTime);
|
||||
void render();
|
||||
void render(bool forShadowMap = false);
|
||||
|
||||
//setters
|
||||
void setDisplayingLookatVectors(bool displayingLookatVectors) { getHead()->setRenderLookatVectors(displayingLookatVectors); }
|
||||
|
@ -99,6 +99,11 @@ public:
|
|||
|
||||
bool findRayIntersection(const glm::vec3& origin, const glm::vec3& direction, float& distance) const;
|
||||
|
||||
/// \param shapes list of shapes to collide against avatar
|
||||
/// \param collisions list to store collision results
|
||||
/// \return true if at least one shape collided with avatar
|
||||
bool findCollisions(const QVector<const Shape*>& shapes, CollisionList& collisions);
|
||||
|
||||
/// Checks for penetration between the described sphere and the avatar.
|
||||
/// \param penetratorCenter the center of the penetration test sphere
|
||||
/// \param penetratorRadius the radius of the penetration test sphere
|
||||
|
|
|
@ -69,7 +69,7 @@ void AvatarManager::updateOtherAvatars(float deltaTime) {
|
|||
simulateAvatarFades(deltaTime);
|
||||
}
|
||||
|
||||
void AvatarManager::renderAvatars(bool forceRenderMyHead, bool selfAvatarOnly) {
|
||||
void AvatarManager::renderAvatars(bool forShadowMapOrMirror, bool selfAvatarOnly) {
|
||||
PerformanceWarning warn(Menu::getInstance()->isOptionChecked(MenuOption::PipelineWarnings),
|
||||
"Application::renderAvatars()");
|
||||
bool renderLookAtVectors = Menu::getInstance()->isOptionChecked(MenuOption::LookAtVectors);
|
||||
|
@ -83,16 +83,16 @@ void AvatarManager::renderAvatars(bool forceRenderMyHead, bool selfAvatarOnly) {
|
|||
avatar->init();
|
||||
}
|
||||
if (avatar == static_cast<Avatar*>(_myAvatar.data())) {
|
||||
_myAvatar->render(forceRenderMyHead);
|
||||
_myAvatar->render(forShadowMapOrMirror);
|
||||
} else {
|
||||
avatar->render();
|
||||
avatar->render(forShadowMapOrMirror);
|
||||
}
|
||||
avatar->setDisplayingLookatVectors(renderLookAtVectors);
|
||||
}
|
||||
renderAvatarFades();
|
||||
renderAvatarFades(forShadowMapOrMirror);
|
||||
} else {
|
||||
// just render myAvatar
|
||||
_myAvatar->render(forceRenderMyHead, true);
|
||||
_myAvatar->render(forShadowMapOrMirror);
|
||||
_myAvatar->setDisplayingLookatVectors(renderLookAtVectors);
|
||||
}
|
||||
}
|
||||
|
@ -115,13 +115,13 @@ void AvatarManager::simulateAvatarFades(float deltaTime) {
|
|||
}
|
||||
}
|
||||
|
||||
void AvatarManager::renderAvatarFades() {
|
||||
void AvatarManager::renderAvatarFades(bool forShadowMap) {
|
||||
// render avatar fades
|
||||
Glower glower;
|
||||
Glower glower(forShadowMap ? 0.0f : 1.0f);
|
||||
|
||||
foreach(const AvatarSharedPointer& fadingAvatar, _avatarFades) {
|
||||
Avatar* avatar = static_cast<Avatar*>(fadingAvatar.data());
|
||||
avatar->render();
|
||||
avatar->render(forShadowMap);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -29,7 +29,7 @@ public:
|
|||
MyAvatar* getMyAvatar() { return _myAvatar.data(); }
|
||||
|
||||
void updateOtherAvatars(float deltaTime);
|
||||
void renderAvatars(bool forceRenderMyHead, bool selfAvatarOnly = false);
|
||||
void renderAvatars(bool forShadowMapOrMirror = false, bool selfAvatarOnly = false);
|
||||
|
||||
void clearOtherAvatars();
|
||||
|
||||
|
@ -45,7 +45,7 @@ private:
|
|||
void processKillAvatar(const QByteArray& datagram);
|
||||
|
||||
void simulateAvatarFades(float deltaTime);
|
||||
void renderAvatarFades();
|
||||
void renderAvatarFades(bool forShadowMap);
|
||||
|
||||
// virtual override
|
||||
AvatarHash::iterator erase(const AvatarHash::iterator& iterator);
|
||||
|
|
|
@ -96,7 +96,7 @@ void Hand::playSlaps(PalmData& palm, Avatar* avatar)
|
|||
const float MAX_COLLISIONS_PER_AVATAR = 32;
|
||||
static CollisionList handCollisions(MAX_COLLISIONS_PER_AVATAR);
|
||||
|
||||
void Hand::collideAgainstAvatar(Avatar* avatar, bool isMyHand) {
|
||||
void Hand::collideAgainstAvatarOld(Avatar* avatar, bool isMyHand) {
|
||||
if (!avatar || avatar == _owningAvatar) {
|
||||
// don't collide with our own hands (that is done elsewhere)
|
||||
return;
|
||||
|
@ -117,17 +117,9 @@ void Hand::collideAgainstAvatar(Avatar* avatar, bool isMyHand) {
|
|||
for (int j = 0; j < handCollisions.size(); ++j) {
|
||||
CollisionInfo* collision = handCollisions.getCollision(j);
|
||||
if (isMyHand) {
|
||||
if (!avatar->collisionWouldMoveAvatar(*collision)) {
|
||||
// we resolve the hand from collision when it belongs to MyAvatar AND the other Avatar is
|
||||
// not expected to respond to the collision (hand hit unmovable part of their Avatar)
|
||||
totalPenetration = addPenetrations(totalPenetration, collision->_penetration);
|
||||
}
|
||||
} else {
|
||||
// when !isMyHand then avatar is MyAvatar and we apply the collision
|
||||
// which might not do anything (hand hit unmovable part of MyAvatar) however
|
||||
// we don't resolve the hand's penetration because we expect the remote
|
||||
// simulation to do the right thing.
|
||||
avatar->applyCollision(*collision);
|
||||
// we resolve the hand from collision when it belongs to MyAvatar AND the other Avatar is
|
||||
// not expected to respond to the collision (hand hit unmovable part of their Avatar)
|
||||
totalPenetration = addPenetrations(totalPenetration, collision->_penetration);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -138,6 +130,66 @@ void Hand::collideAgainstAvatar(Avatar* avatar, bool isMyHand) {
|
|||
}
|
||||
}
|
||||
|
||||
void Hand::collideAgainstAvatar(Avatar* avatar, bool isMyHand) {
|
||||
if (!avatar || avatar == _owningAvatar) {
|
||||
// don't collide hands against ourself (that is done elsewhere)
|
||||
return;
|
||||
}
|
||||
|
||||
// 2 = NUM_HANDS
|
||||
int palmIndices[2];
|
||||
getLeftRightPalmIndices(*palmIndices, *(palmIndices + 1));
|
||||
|
||||
const SkeletonModel& skeletonModel = _owningAvatar->getSkeletonModel();
|
||||
int jointIndices[2];
|
||||
jointIndices[0] = skeletonModel.getLeftHandJointIndex();
|
||||
jointIndices[1] = skeletonModel.getRightHandJointIndex();
|
||||
|
||||
palmIndices[1] = -1; // adebug temporarily disable right hand
|
||||
jointIndices[1] = -1; // adebug temporarily disable right hand
|
||||
|
||||
for (size_t i = 0; i < 1; i++) {
|
||||
int palmIndex = palmIndices[i];
|
||||
int jointIndex = jointIndices[i];
|
||||
if (palmIndex == -1 || jointIndex == -1) {
|
||||
continue;
|
||||
}
|
||||
PalmData& palm = _palms[palmIndex];
|
||||
if (!palm.isActive()) {
|
||||
continue;
|
||||
}
|
||||
if (isMyHand && Menu::getInstance()->isOptionChecked(MenuOption::PlaySlaps)) {
|
||||
playSlaps(palm, avatar);
|
||||
}
|
||||
|
||||
handCollisions.clear();
|
||||
QVector<const Shape*> shapes;
|
||||
skeletonModel.getHandShapes(jointIndex, shapes);
|
||||
bool collided = isMyHand ? avatar->findCollisions(shapes, handCollisions) : avatar->findCollisions(shapes, handCollisions);
|
||||
if (collided) {
|
||||
//if (avatar->findCollisions(shapes, handCollisions)) {
|
||||
glm::vec3 averagePenetration;
|
||||
glm::vec3 averageContactPoint;
|
||||
for (int j = 0; j < handCollisions.size(); ++j) {
|
||||
CollisionInfo* collision = handCollisions.getCollision(j);
|
||||
averagePenetration += collision->_penetration;
|
||||
averageContactPoint += collision->_contactPoint;
|
||||
}
|
||||
averagePenetration /= float(handCollisions.size());
|
||||
if (isMyHand) {
|
||||
// our hand against other avatar
|
||||
// for now we resolve it to test shapes/collisions
|
||||
// TODO: only partially resolve this penetration
|
||||
palm.addToPosition(-averagePenetration);
|
||||
} else {
|
||||
// someone else's hand against MyAvatar
|
||||
// TODO: submit collision info to MyAvatar which should lean accordingly
|
||||
averageContactPoint /= float(handCollisions.size());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Hand::collideAgainstOurself() {
|
||||
if (!Menu::getInstance()->isOptionChecked(MenuOption::HandsCollideWithSelf)) {
|
||||
return;
|
||||
|
@ -147,27 +199,27 @@ void Hand::collideAgainstOurself() {
|
|||
getLeftRightPalmIndices(leftPalmIndex, rightPalmIndex);
|
||||
float scaledPalmRadius = PALM_COLLISION_RADIUS * _owningAvatar->getScale();
|
||||
|
||||
const Model& skeletonModel = _owningAvatar->getSkeletonModel();
|
||||
for (size_t i = 0; i < getNumPalms(); i++) {
|
||||
PalmData& palm = getPalms()[i];
|
||||
if (!palm.isActive()) {
|
||||
continue;
|
||||
}
|
||||
const Model& skeletonModel = _owningAvatar->getSkeletonModel();
|
||||
}
|
||||
// ignoring everything below the parent of the parent of the last free joint
|
||||
int skipIndex = skeletonModel.getParentJointIndex(skeletonModel.getParentJointIndex(
|
||||
skeletonModel.getLastFreeJointIndex((i == leftPalmIndex) ? skeletonModel.getLeftHandJointIndex() :
|
||||
(i == rightPalmIndex) ? skeletonModel.getRightHandJointIndex() : -1)));
|
||||
|
||||
handCollisions.clear();
|
||||
glm::vec3 totalPenetration;
|
||||
if (_owningAvatar->findSphereCollisions(palm.getPosition(), scaledPalmRadius, handCollisions, skipIndex)) {
|
||||
glm::vec3 totalPenetration;
|
||||
for (int j = 0; j < handCollisions.size(); ++j) {
|
||||
CollisionInfo* collision = handCollisions.getCollision(j);
|
||||
totalPenetration = addPenetrations(totalPenetration, collision->_penetration);
|
||||
}
|
||||
// resolve penetration
|
||||
palm.addToPosition(-totalPenetration);
|
||||
}
|
||||
// resolve penetration
|
||||
palm.addToPosition(-totalPenetration);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -56,6 +56,7 @@ public:
|
|||
const glm::vec3& getLeapFingerTipBallPosition (int ball) const { return _leapFingerTipBalls [ball].position;}
|
||||
const glm::vec3& getLeapFingerRootBallPosition(int ball) const { return _leapFingerRootBalls[ball].position;}
|
||||
|
||||
void collideAgainstAvatarOld(Avatar* avatar, bool isMyHand);
|
||||
void collideAgainstAvatar(Avatar* avatar, bool isMyHand);
|
||||
void collideAgainstOurself();
|
||||
|
||||
|
|
|
@ -452,24 +452,24 @@ void MyAvatar::renderDebugBodyPoints() {
|
|||
|
||||
|
||||
}
|
||||
void MyAvatar::render(bool forceRenderHead, bool avatarOnly) {
|
||||
void MyAvatar::render(bool forShadowMapOrMirror) {
|
||||
// don't render if we've been asked to disable local rendering
|
||||
if (!_shouldRender) {
|
||||
return; // exit early
|
||||
}
|
||||
|
||||
if (Menu::getInstance()->isOptionChecked(MenuOption::Avatars)) {
|
||||
renderBody(forShadowMapOrMirror);
|
||||
}
|
||||
// render body
|
||||
if (Menu::getInstance()->isOptionChecked(MenuOption::RenderSkeletonCollisionProxies)) {
|
||||
_skeletonModel.renderCollisionProxies(1.f);
|
||||
_skeletonModel.renderCollisionProxies(0.8f);
|
||||
}
|
||||
if (Menu::getInstance()->isOptionChecked(MenuOption::RenderHeadCollisionProxies)) {
|
||||
_skeletonModel.renderCollisionProxies(1.f);
|
||||
getHead()->getFaceModel().renderCollisionProxies(0.8f);
|
||||
}
|
||||
if (Menu::getInstance()->isOptionChecked(MenuOption::Avatars)) {
|
||||
renderBody(forceRenderHead);
|
||||
}
|
||||
setShowDisplayName(!avatarOnly);
|
||||
if (avatarOnly) {
|
||||
setShowDisplayName(!forShadowMapOrMirror);
|
||||
if (forShadowMapOrMirror) {
|
||||
return;
|
||||
}
|
||||
renderDisplayName();
|
||||
|
@ -941,6 +941,11 @@ void MyAvatar::updateCollisionWithAvatars(float deltaTime) {
|
|||
}
|
||||
float theirBoundingRadius = avatar->getBoundingRadius();
|
||||
if (distance < myBoundingRadius + theirBoundingRadius) {
|
||||
_skeletonModel.updateShapePositions();
|
||||
Model& headModel = getHead()->getFaceModel();
|
||||
headModel.updateShapePositions();
|
||||
|
||||
/* TODO: Andrew to fix Avatar-Avatar body collisions
|
||||
Extents theirStaticExtents = _skeletonModel.getStaticExtents();
|
||||
glm::vec3 staticScale = theirStaticExtents.maximum - theirStaticExtents.minimum;
|
||||
float theirCapsuleRadius = 0.25f * (staticScale.x + staticScale.z);
|
||||
|
@ -952,6 +957,7 @@ void MyAvatar::updateCollisionWithAvatars(float deltaTime) {
|
|||
// move the avatar out by half the penetration
|
||||
setPosition(_position - 0.5f * penetration);
|
||||
}
|
||||
*/
|
||||
|
||||
// collide our hands against them
|
||||
getHand()->collideAgainstAvatar(avatar, true);
|
||||
|
|
|
@ -35,7 +35,7 @@ public:
|
|||
void simulate(float deltaTime);
|
||||
void updateFromGyros(float deltaTime);
|
||||
|
||||
void render(bool forceRenderHead, bool avatarOnly = false);
|
||||
void render(bool forShadowMapOrMirror = false);
|
||||
void renderDebugBodyPoints();
|
||||
void renderHeadMouse() const;
|
||||
|
||||
|
|
|
@ -73,6 +73,17 @@ bool SkeletonModel::render(float alpha) {
|
|||
return true;
|
||||
}
|
||||
|
||||
void SkeletonModel::getHandShapes(int jointIndex, QVector<const Shape*>& shapes) const {
|
||||
if (jointIndex == -1) {
|
||||
return;
|
||||
}
|
||||
if (jointIndex == getLeftHandJointIndex()
|
||||
|| jointIndex == getRightHandJointIndex()) {
|
||||
// TODO: also add fingers and other hand-parts
|
||||
shapes.push_back(_shapes[jointIndex]);
|
||||
}
|
||||
}
|
||||
|
||||
class IndexValue {
|
||||
public:
|
||||
int index;
|
||||
|
|
|
@ -24,6 +24,10 @@ public:
|
|||
|
||||
void simulate(float deltaTime, bool delayLoad = false);
|
||||
bool render(float alpha);
|
||||
|
||||
/// \param jointIndex index of hand joint
|
||||
/// \param shapes[out] list in which is stored pointers to hand shapes
|
||||
void getHandShapes(int jointIndex, QVector<const Shape*>& shapes) const;
|
||||
|
||||
protected:
|
||||
|
||||
|
|
|
@ -6,6 +6,7 @@
|
|||
// Copyright (c) 2013 High Fidelity, Inc. All rights reserved.
|
||||
//
|
||||
|
||||
#include <iostream>
|
||||
#include <QBuffer>
|
||||
#include <QDataStream>
|
||||
#include <QIODevice>
|
||||
|
@ -21,6 +22,7 @@
|
|||
#include <OctalCode.h>
|
||||
|
||||
#include <GeometryUtil.h>
|
||||
#include <Shape.h>
|
||||
#include <VoxelTree.h>
|
||||
|
||||
#include "FBXReader.h"
|
||||
|
@ -28,6 +30,22 @@
|
|||
|
||||
using namespace std;
|
||||
|
||||
void Extents::reset() {
|
||||
minimum = glm::vec3(FLT_MAX);
|
||||
maximum = glm::vec3(-FLT_MAX);
|
||||
}
|
||||
|
||||
bool Extents::containsPoint(const glm::vec3& point) const {
|
||||
return (point.x >= minimum.x && point.x <= maximum.x
|
||||
&& point.y >= minimum.y && point.y <= maximum.y
|
||||
&& point.z >= minimum.z && point.z <= maximum.z);
|
||||
}
|
||||
|
||||
void Extents::addPoint(const glm::vec3& point) {
|
||||
minimum = glm::min(minimum, point);
|
||||
maximum = glm::max(maximum, point);
|
||||
}
|
||||
|
||||
static int fbxGeometryMetaTypeId = qRegisterMetaType<FBXGeometry>();
|
||||
|
||||
template<class T> QVariant readBinaryArray(QDataStream& in) {
|
||||
|
@ -538,8 +556,9 @@ public:
|
|||
FBXBlendshape blendshape;
|
||||
};
|
||||
|
||||
void printNode(const FBXNode& node, int indent) {
|
||||
QByteArray spaces(indent, ' ');
|
||||
void printNode(const FBXNode& node, int indentLevel) {
|
||||
int indentLength = 2;
|
||||
QByteArray spaces(indentLevel * indentLength, ' ');
|
||||
QDebug nodeDebug = qDebug();
|
||||
|
||||
nodeDebug.nospace() << spaces.data() << node.name.data() << ": ";
|
||||
|
@ -548,7 +567,7 @@ void printNode(const FBXNode& node, int indent) {
|
|||
}
|
||||
|
||||
foreach (const FBXNode& child, node.children) {
|
||||
printNode(child, indent + 1);
|
||||
printNode(child, indentLevel + 1);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -842,6 +861,21 @@ QString getString(const QVariant& value) {
|
|||
return list.isEmpty() ? value.toString() : list.at(0).toString();
|
||||
}
|
||||
|
||||
class JointShapeInfo {
|
||||
public:
|
||||
JointShapeInfo() : numVertices(0), numProjectedVertices(0), averageVertex(0.f), boneBegin(0.f), averageRadius(0.f) {
|
||||
extents.reset();
|
||||
}
|
||||
|
||||
// NOTE: the points here are in the "joint frame" which has the "jointEnd" at the origin
|
||||
int numVertices; // num vertices from contributing meshes
|
||||
int numProjectedVertices; // num vertices that successfully project onto bone axis
|
||||
Extents extents; // max and min extents of mesh vertices (in joint frame)
|
||||
glm::vec3 averageVertex; // average of all mesh vertices (in joint frame)
|
||||
glm::vec3 boneBegin; // parent joint location (in joint frame)
|
||||
float averageRadius; // average distance from mesh points to averageVertex
|
||||
};
|
||||
|
||||
FBXGeometry extractFBXGeometry(const FBXNode& node, const QVariantHash& mapping) {
|
||||
QHash<QString, ExtractedMesh> meshes;
|
||||
QVector<ExtractedBlendshape> blendshapes;
|
||||
|
@ -1250,9 +1284,15 @@ FBXGeometry extractFBXGeometry(const FBXNode& node, const QVariantHash& mapping)
|
|||
}
|
||||
joint.boneRadius = 0.0f;
|
||||
joint.inverseBindRotation = joint.inverseDefaultRotation;
|
||||
joint.name = model.name;
|
||||
joint.shapePosition = glm::vec3(0.f);
|
||||
joint.shapeType = Shape::UNKNOWN_SHAPE;
|
||||
geometry.joints.append(joint);
|
||||
geometry.jointIndices.insert(model.name, geometry.joints.size());
|
||||
}
|
||||
// for each joint we allocate a JointShapeInfo in which we'll store collision shape info
|
||||
QVector<JointShapeInfo> jointShapeInfos;
|
||||
jointShapeInfos.resize(geometry.joints.size());
|
||||
|
||||
// find our special joints
|
||||
geometry.leftEyeJointIndex = modelIDs.indexOf(jointEyeLeftID);
|
||||
|
@ -1274,12 +1314,9 @@ FBXGeometry extractFBXGeometry(const FBXNode& node, const QVariantHash& mapping)
|
|||
geometry.neckPivot = glm::vec3(transform[3][0], transform[3][1], transform[3][2]);
|
||||
}
|
||||
|
||||
geometry.bindExtents.minimum = glm::vec3(FLT_MAX, FLT_MAX, FLT_MAX);
|
||||
geometry.bindExtents.maximum = glm::vec3(-FLT_MAX, -FLT_MAX, -FLT_MAX);
|
||||
geometry.staticExtents.minimum = glm::vec3(FLT_MAX, FLT_MAX, FLT_MAX);
|
||||
geometry.staticExtents.maximum = glm::vec3(-FLT_MAX, -FLT_MAX, -FLT_MAX);
|
||||
geometry.meshExtents.minimum = glm::vec3(FLT_MAX, FLT_MAX, FLT_MAX);
|
||||
geometry.meshExtents.maximum = glm::vec3(-FLT_MAX, -FLT_MAX, -FLT_MAX);
|
||||
geometry.bindExtents.reset();
|
||||
geometry.staticExtents.reset();
|
||||
geometry.meshExtents.reset();
|
||||
|
||||
QVariantHash springs = mapping.value("spring").toHash();
|
||||
QVariant defaultSpring = springs.value("default");
|
||||
|
@ -1402,8 +1439,7 @@ FBXGeometry extractFBXGeometry(const FBXNode& node, const QVariantHash& mapping)
|
|||
|
||||
// update the bind pose extents
|
||||
glm::vec3 bindTranslation = extractTranslation(geometry.offset * joint.bindTransform);
|
||||
geometry.bindExtents.minimum = glm::min(geometry.bindExtents.minimum, bindTranslation);
|
||||
geometry.bindExtents.maximum = glm::max(geometry.bindExtents.maximum, bindTranslation);
|
||||
geometry.bindExtents.addPoint(bindTranslation);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1416,11 +1452,6 @@ FBXGeometry extractFBXGeometry(const FBXNode& node, const QVariantHash& mapping)
|
|||
cluster.jointIndex = 0;
|
||||
}
|
||||
extracted.mesh.clusters.append(cluster);
|
||||
// BUG: joints that fall into this context do not get their bindTransform and
|
||||
// inverseBindRotation data members properly set. This causes bad boneRadius
|
||||
// and boneLength calculations for collision proxies. Affected joints are usually:
|
||||
// hair, teeth, tongue. I tried to figure out how to fix this but was going
|
||||
// crosseyed trying to understand FBX so I gave up for the time being -- Andrew.
|
||||
}
|
||||
|
||||
// whether we're skinned depends on how many clusters are attached
|
||||
|
@ -1437,20 +1468,26 @@ FBXGeometry extractFBXGeometry(const FBXNode& node, const QVariantHash& mapping)
|
|||
const FBXCluster& fbxCluster = extracted.mesh.clusters.at(i);
|
||||
int jointIndex = fbxCluster.jointIndex;
|
||||
FBXJoint& joint = geometry.joints[jointIndex];
|
||||
glm::vec3 boneEnd = extractTranslation(inverseModelTransform * joint.bindTransform);
|
||||
glm::mat4 transformJointToMesh = inverseModelTransform * joint.bindTransform;
|
||||
glm::quat rotateMeshToJoint = glm::inverse(extractRotation(transformJointToMesh));
|
||||
glm::vec3 boneEnd = extractTranslation(transformJointToMesh);
|
||||
glm::vec3 boneBegin = boneEnd;
|
||||
glm::vec3 boneDirection;
|
||||
float boneLength;
|
||||
if (joint.parentIndex != -1) {
|
||||
boneDirection = boneEnd - extractTranslation(inverseModelTransform *
|
||||
geometry.joints[joint.parentIndex].bindTransform);
|
||||
boneBegin = extractTranslation(inverseModelTransform * geometry.joints[joint.parentIndex].bindTransform);
|
||||
boneDirection = boneEnd - boneBegin;
|
||||
boneLength = glm::length(boneDirection);
|
||||
if (boneLength > EPSILON) {
|
||||
boneDirection /= boneLength;
|
||||
}
|
||||
}
|
||||
float radiusScale = extractUniformScale(joint.transform * fbxCluster.inverseBindMatrix);
|
||||
JointShapeInfo& jointShapeInfo = jointShapeInfos[jointIndex];
|
||||
jointShapeInfo.boneBegin = rotateMeshToJoint * (radiusScale * (boneBegin - boneEnd));
|
||||
|
||||
bool jointIsStatic = joint.freeLineage.isEmpty();
|
||||
glm::vec3 jointTranslation = extractTranslation(geometry.offset * joint.bindTransform);
|
||||
float radiusScale = extractUniformScale(joint.transform * fbxCluster.inverseBindMatrix);
|
||||
float totalWeight = 0.0f;
|
||||
for (int j = 0; j < cluster.indices.size(); j++) {
|
||||
int oldIndex = cluster.indices.at(j);
|
||||
|
@ -1464,13 +1501,17 @@ FBXGeometry extractFBXGeometry(const FBXNode& node, const QVariantHash& mapping)
|
|||
const glm::vec3& vertex = extracted.mesh.vertices.at(it.value());
|
||||
float proj = glm::dot(boneDirection, vertex - boneEnd);
|
||||
if (proj < 0.0f && proj > -boneLength) {
|
||||
joint.boneRadius = glm::max(joint.boneRadius, radiusScale * glm::distance(
|
||||
vertex, boneEnd + boneDirection * proj));
|
||||
joint.boneRadius = glm::max(joint.boneRadius,
|
||||
radiusScale * glm::distance(vertex, boneEnd + boneDirection * proj));
|
||||
++jointShapeInfo.numProjectedVertices;
|
||||
}
|
||||
glm::vec3 vertexInJointFrame = rotateMeshToJoint * (radiusScale * (vertex - boneEnd));
|
||||
jointShapeInfo.extents.addPoint(vertexInJointFrame);
|
||||
jointShapeInfo.averageVertex += vertexInJointFrame;
|
||||
++jointShapeInfo.numVertices;
|
||||
if (jointIsStatic) {
|
||||
// expand the extents of static (nonmovable) joints
|
||||
geometry.staticExtents.minimum = glm::min(geometry.staticExtents.minimum, vertex + jointTranslation);
|
||||
geometry.staticExtents.maximum = glm::max(geometry.staticExtents.maximum, vertex + jointTranslation);
|
||||
geometry.staticExtents.addPoint(vertex + jointTranslation);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1493,24 +1534,47 @@ FBXGeometry extractFBXGeometry(const FBXNode& node, const QVariantHash& mapping)
|
|||
} else {
|
||||
int jointIndex = maxJointIndex;
|
||||
FBXJoint& joint = geometry.joints[jointIndex];
|
||||
glm::vec3 boneEnd = extractTranslation(inverseModelTransform * joint.bindTransform);
|
||||
JointShapeInfo& jointShapeInfo = jointShapeInfos[jointIndex];
|
||||
|
||||
glm::mat4 transformJointToMesh = inverseModelTransform * joint.bindTransform;
|
||||
glm::quat rotateMeshToJoint = glm::inverse(extractRotation(transformJointToMesh));
|
||||
glm::vec3 boneEnd = extractTranslation(transformJointToMesh);
|
||||
glm::vec3 boneBegin = boneEnd;
|
||||
|
||||
glm::vec3 boneDirection;
|
||||
float boneLength;
|
||||
if (joint.parentIndex != -1) {
|
||||
boneDirection = boneEnd - extractTranslation(inverseModelTransform *
|
||||
geometry.joints[joint.parentIndex].bindTransform);
|
||||
boneBegin = extractTranslation(inverseModelTransform * geometry.joints[joint.parentIndex].bindTransform);
|
||||
boneDirection = boneEnd - boneBegin;
|
||||
boneLength = glm::length(boneDirection);
|
||||
if (boneLength > EPSILON) {
|
||||
boneDirection /= boneLength;
|
||||
}
|
||||
}
|
||||
float radiusScale = extractUniformScale(joint.transform * firstFBXCluster.inverseBindMatrix);
|
||||
jointShapeInfo.boneBegin = rotateMeshToJoint * (radiusScale * (boneBegin - boneEnd));
|
||||
|
||||
glm::vec3 averageVertex(0.f);
|
||||
foreach (const glm::vec3& vertex, extracted.mesh.vertices) {
|
||||
float proj = glm::dot(boneDirection, vertex - boneEnd);
|
||||
if (proj < 0.0f && proj > -boneLength) {
|
||||
joint.boneRadius = glm::max(joint.boneRadius, radiusScale * glm::distance(
|
||||
vertex, boneEnd + boneDirection * proj));
|
||||
joint.boneRadius = glm::max(joint.boneRadius, radiusScale * glm::distance(vertex, boneEnd + boneDirection * proj));
|
||||
++jointShapeInfo.numProjectedVertices;
|
||||
}
|
||||
glm::vec3 vertexInJointFrame = rotateMeshToJoint * (radiusScale * (vertex - boneEnd));
|
||||
jointShapeInfo.extents.addPoint(vertexInJointFrame);
|
||||
jointShapeInfo.averageVertex += vertexInJointFrame;
|
||||
averageVertex += vertex;
|
||||
}
|
||||
int numVertices = extracted.mesh.vertices.size();
|
||||
jointShapeInfo.numVertices = numVertices;
|
||||
if (numVertices > 0) {
|
||||
averageVertex /= float(jointShapeInfo.numVertices);
|
||||
float averageRadius = 0.f;
|
||||
foreach (const glm::vec3& vertex, extracted.mesh.vertices) {
|
||||
averageRadius += glm::distance(vertex, averageVertex);
|
||||
}
|
||||
jointShapeInfo.averageRadius = averageRadius * radiusScale;
|
||||
}
|
||||
}
|
||||
extracted.mesh.isEye = (maxJointIndex == geometry.leftEyeJointIndex || maxJointIndex == geometry.rightEyeJointIndex);
|
||||
|
@ -1560,6 +1624,39 @@ FBXGeometry extractFBXGeometry(const FBXNode& node, const QVariantHash& mapping)
|
|||
geometry.meshes.append(extracted.mesh);
|
||||
}
|
||||
|
||||
// now that all joints have been scanned, compute a collision shape for each joint
|
||||
glm::vec3 defaultCapsuleAxis(0.f, 1.f, 0.f);
|
||||
for (int i = 0; i < geometry.joints.size(); ++i) {
|
||||
FBXJoint& joint = geometry.joints[i];
|
||||
JointShapeInfo& jointShapeInfo = jointShapeInfos[i];
|
||||
|
||||
// we use a capsule if the joint ANY mesh vertices successfully projected onto the bone
|
||||
// AND its boneRadius is not too close to zero
|
||||
bool collideLikeCapsule = jointShapeInfo.numProjectedVertices > 0
|
||||
&& glm::length(jointShapeInfo.boneBegin) > EPSILON;
|
||||
|
||||
if (collideLikeCapsule) {
|
||||
joint.shapeRotation = rotationBetween(defaultCapsuleAxis, jointShapeInfo.boneBegin);
|
||||
joint.shapePosition = 0.5f * jointShapeInfo.boneBegin;
|
||||
joint.shapeType = Shape::CAPSULE_SHAPE;
|
||||
} else {
|
||||
// collide the joint like a sphere
|
||||
if (jointShapeInfo.numVertices > 0) {
|
||||
jointShapeInfo.averageVertex /= float(jointShapeInfo.numVertices);
|
||||
joint.shapePosition = jointShapeInfo.averageVertex;
|
||||
} else {
|
||||
joint.shapePosition = glm::vec3(0.f);
|
||||
joint.shapeType = Shape::SPHERE_SHAPE;
|
||||
}
|
||||
if (jointShapeInfo.numProjectedVertices == 0
|
||||
&& jointShapeInfo.numVertices > 0) {
|
||||
// the bone projection algorithm was not able to compute the joint radius
|
||||
// so we use an alternative measure
|
||||
jointShapeInfo.averageRadius /= float(jointShapeInfo.numVertices);
|
||||
joint.boneRadius = jointShapeInfo.averageRadius;
|
||||
}
|
||||
}
|
||||
}
|
||||
geometry.palmDirection = parseVec3(mapping.value("palmDirection", "0, -1, 0").toString());
|
||||
|
||||
// process attachments
|
||||
|
|
|
@ -25,6 +25,23 @@ typedef QList<FBXNode> FBXNodeList;
|
|||
/// The names of the blendshapes expected by Faceshift, terminated with an empty string.
|
||||
extern const char* FACESHIFT_BLENDSHAPES[];
|
||||
|
||||
class Extents {
|
||||
public:
|
||||
/// set minimum and maximum to FLT_MAX and -FLT_MAX respectively
|
||||
void reset();
|
||||
|
||||
/// \param point new point to compare against existing limits
|
||||
/// compare point to current limits and expand them if necessary to contain point
|
||||
void addPoint(const glm::vec3& point);
|
||||
|
||||
/// \param point
|
||||
/// \return true if point is within current limits
|
||||
bool containsPoint(const glm::vec3& point) const;
|
||||
|
||||
glm::vec3 minimum;
|
||||
glm::vec3 maximum;
|
||||
};
|
||||
|
||||
/// A node within an FBX document.
|
||||
class FBXNode {
|
||||
public:
|
||||
|
@ -64,8 +81,13 @@ public:
|
|||
glm::quat inverseDefaultRotation;
|
||||
glm::quat inverseBindRotation;
|
||||
glm::mat4 bindTransform;
|
||||
QString name; // temp field for debugging
|
||||
glm::vec3 shapePosition; // in joint frame
|
||||
glm::quat shapeRotation; // in joint frame
|
||||
int shapeType;
|
||||
};
|
||||
|
||||
|
||||
/// A single binding to a joint in an FBX document.
|
||||
class FBXCluster {
|
||||
public:
|
||||
|
@ -125,13 +147,6 @@ public:
|
|||
glm::vec3 scale;
|
||||
};
|
||||
|
||||
class Extents {
|
||||
public:
|
||||
|
||||
glm::vec3 minimum;
|
||||
glm::vec3 maximum;
|
||||
};
|
||||
|
||||
/// A set of meshes extracted from an FBX document.
|
||||
class FBXGeometry {
|
||||
public:
|
||||
|
|
|
@ -343,7 +343,14 @@ QSharedPointer<NetworkGeometry> NetworkGeometry::getLODOrFallback(float distance
|
|||
// if we previously selected a different distance, make sure we've moved far enough to justify switching
|
||||
const float HYSTERESIS_PROPORTION = 0.1f;
|
||||
if (glm::abs(distance - qMax(hysteresis, lodDistance)) / fabsf(hysteresis - lodDistance) < HYSTERESIS_PROPORTION) {
|
||||
return getLODOrFallback(hysteresis, hysteresis);
|
||||
lod = _lodParent;
|
||||
lodDistance = 0.0f;
|
||||
it = _lods.upperBound(hysteresis);
|
||||
if (it != _lods.constBegin()) {
|
||||
it = it - 1;
|
||||
lod = it.value();
|
||||
lodDistance = it.key();
|
||||
}
|
||||
}
|
||||
}
|
||||
if (lod->isLoaded()) {
|
||||
|
|
|
@ -13,10 +13,15 @@
|
|||
#include "Application.h"
|
||||
#include "Model.h"
|
||||
|
||||
#include <SphereShape.h>
|
||||
#include <CapsuleShape.h>
|
||||
#include <ShapeCollider.h>
|
||||
|
||||
using namespace std;
|
||||
|
||||
Model::Model(QObject* parent) :
|
||||
QObject(parent),
|
||||
_shapesAreDirty(true),
|
||||
_lodDistance(0.0f),
|
||||
_pupilDilation(0.0f) {
|
||||
// we may have been created in the network thread, but we live in the main thread
|
||||
|
@ -100,6 +105,51 @@ void Model::reset() {
|
|||
}
|
||||
}
|
||||
|
||||
void Model::clearShapes() {
|
||||
for (int i = 0; i < _shapes.size(); ++i) {
|
||||
delete _shapes[i];
|
||||
}
|
||||
_shapes.clear();
|
||||
}
|
||||
|
||||
void Model::createCollisionShapes() {
|
||||
clearShapes();
|
||||
const FBXGeometry& geometry = _geometry->getFBXGeometry();
|
||||
float uniformScale = extractUniformScale(_scale);
|
||||
for (int i = 0; i < _jointStates.size(); i++) {
|
||||
const FBXJoint& joint = geometry.joints[i];
|
||||
glm::vec3 meshCenter = _jointStates[i].combinedRotation * joint.shapePosition;
|
||||
glm::vec3 position = _rotation * (extractTranslation(_jointStates[i].transform) + uniformScale * meshCenter) + _translation;
|
||||
|
||||
float radius = uniformScale * joint.boneRadius;
|
||||
if (joint.shapeType == Shape::CAPSULE_SHAPE) {
|
||||
float halfHeight = 0.5f * uniformScale * joint.distanceToParent;
|
||||
CapsuleShape* shape = new CapsuleShape(radius, halfHeight);
|
||||
shape->setPosition(position);
|
||||
_shapes.push_back(shape);
|
||||
} else {
|
||||
SphereShape* shape = new SphereShape(radius, position);
|
||||
_shapes.push_back(shape);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Model::updateShapePositions() {
|
||||
if (_shapesAreDirty && _shapes.size() == _jointStates.size()) {
|
||||
float uniformScale = extractUniformScale(_scale);
|
||||
const FBXGeometry& geometry = _geometry->getFBXGeometry();
|
||||
for (int i = 0; i < _jointStates.size(); i++) {
|
||||
const FBXJoint& joint = geometry.joints[i];
|
||||
// shape position and rotation need to be in world-frame
|
||||
glm::vec3 jointToShapeOffset = uniformScale * (_jointStates[i].combinedRotation * joint.shapePosition);
|
||||
glm::vec3 worldPosition = extractTranslation(_jointStates[i].transform) + jointToShapeOffset + _translation;
|
||||
_shapes[i]->setPosition(worldPosition);
|
||||
_shapes[i]->setRotation(_jointStates[i].combinedRotation * joint.shapeRotation);
|
||||
}
|
||||
_shapesAreDirty = false;
|
||||
}
|
||||
}
|
||||
|
||||
void Model::simulate(float deltaTime, bool delayLoad) {
|
||||
// update our LOD
|
||||
QVector<JointState> newJointStates = updateGeometry(delayLoad);
|
||||
|
@ -128,6 +178,7 @@ void Model::simulate(float deltaTime, bool delayLoad) {
|
|||
_attachments.append(model);
|
||||
}
|
||||
_resetStates = true;
|
||||
createCollisionShapes();
|
||||
}
|
||||
|
||||
// update the world space transforms for all joints
|
||||
|
@ -416,6 +467,7 @@ void Model::setURL(const QUrl& url, const QUrl& fallback, bool retainCurrent, bo
|
|||
|
||||
// if so instructed, keep the current geometry until the new one is loaded
|
||||
_nextBaseGeometry = _nextGeometry = Application::getInstance()->getGeometryCache()->getGeometry(url, fallback, delayLoad);
|
||||
_nextLODHysteresis = NetworkGeometry::NO_HYSTERESIS;
|
||||
if (!retainCurrent || !isActive() || _nextGeometry->isLoaded()) {
|
||||
applyNextGeometry();
|
||||
}
|
||||
|
@ -454,20 +506,28 @@ bool Model::findRayIntersection(const glm::vec3& origin, const glm::vec3& direct
|
|||
return false;
|
||||
}
|
||||
|
||||
bool Model::findSphereCollisions(const glm::vec3& penetratorCenter, float penetratorRadius,
|
||||
CollisionList& collisions, float boneScale, int skipIndex) const {
|
||||
bool Model::findCollisions(const QVector<const Shape*> shapes, CollisionList& collisions) {
|
||||
bool collided = false;
|
||||
const glm::vec3 relativeCenter = penetratorCenter - _translation;
|
||||
for (int i = 0; i < shapes.size(); ++i) {
|
||||
const Shape* theirShape = shapes[i];
|
||||
for (int j = 0; j < _shapes.size(); ++j) {
|
||||
const Shape* ourShape = _shapes[j];
|
||||
if (ShapeCollider::shapeShape(theirShape, ourShape, collisions)) {
|
||||
collided = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return collided;
|
||||
}
|
||||
|
||||
bool Model::findSphereCollisions(const glm::vec3& sphereCenter, float sphereRadius,
|
||||
CollisionList& collisions, int skipIndex) {
|
||||
bool collided = false;
|
||||
updateShapePositions();
|
||||
SphereShape sphere(sphereRadius, sphereCenter);
|
||||
const FBXGeometry& geometry = _geometry->getFBXGeometry();
|
||||
glm::vec3 totalPenetration;
|
||||
float radiusScale = extractUniformScale(_scale) * boneScale;
|
||||
for (int i = 0; i < _jointStates.size(); i++) {
|
||||
for (int i = 0; i < _shapes.size(); i++) {
|
||||
const FBXJoint& joint = geometry.joints[i];
|
||||
glm::vec3 end = extractTranslation(_jointStates[i].transform);
|
||||
float endRadius = joint.boneRadius * radiusScale;
|
||||
glm::vec3 start = end;
|
||||
float startRadius = joint.boneRadius * radiusScale;
|
||||
glm::vec3 bonePenetration;
|
||||
if (joint.parentIndex != -1) {
|
||||
if (skipIndex != -1) {
|
||||
int ancestorIndex = joint.parentIndex;
|
||||
|
@ -479,24 +539,13 @@ bool Model::findSphereCollisions(const glm::vec3& penetratorCenter, float penetr
|
|||
|
||||
} while (ancestorIndex != -1);
|
||||
}
|
||||
start = extractTranslation(_jointStates[joint.parentIndex].transform);
|
||||
startRadius = geometry.joints[joint.parentIndex].boneRadius * radiusScale;
|
||||
}
|
||||
if (findSphereCapsuleConePenetration(relativeCenter, penetratorRadius, start, end,
|
||||
startRadius, endRadius, bonePenetration)) {
|
||||
totalPenetration = addPenetrations(totalPenetration, bonePenetration);
|
||||
CollisionInfo* collision = collisions.getNewCollision();
|
||||
if (collision) {
|
||||
collision->_type = MODEL_COLLISION;
|
||||
collision->_data = (void*)(this);
|
||||
collision->_flags = i;
|
||||
collision->_contactPoint = penetratorCenter + penetratorRadius * glm::normalize(totalPenetration);
|
||||
collision->_penetration = totalPenetration;
|
||||
collided = true;
|
||||
} else {
|
||||
// collisions are full, so we might as well break
|
||||
break;
|
||||
}
|
||||
if (ShapeCollider::shapeShape(&sphere, _shapes[i], collisions)) {
|
||||
CollisionInfo* collision = collisions.getLastCollision();
|
||||
collision->_type = MODEL_COLLISION;
|
||||
collision->_data = (void*)(this);
|
||||
collision->_flags = i;
|
||||
collided = true;
|
||||
}
|
||||
outerContinue: ;
|
||||
}
|
||||
|
@ -504,6 +553,7 @@ bool Model::findSphereCollisions(const glm::vec3& penetratorCenter, float penetr
|
|||
}
|
||||
|
||||
void Model::updateJointState(int index) {
|
||||
_shapesAreDirty = true;
|
||||
JointState& state = _jointStates[index];
|
||||
const FBXGeometry& geometry = _geometry->getFBXGeometry();
|
||||
const FBXJoint& joint = geometry.joints.at(index);
|
||||
|
@ -702,34 +752,51 @@ void Model::applyRotationDelta(int jointIndex, const glm::quat& delta, bool cons
|
|||
void Model::renderCollisionProxies(float alpha) {
|
||||
glPushMatrix();
|
||||
Application::getInstance()->loadTranslatedViewMatrix(_translation);
|
||||
|
||||
const FBXGeometry& geometry = _geometry->getFBXGeometry();
|
||||
float uniformScale = extractUniformScale(_scale);
|
||||
for (int i = 0; i < _jointStates.size(); i++) {
|
||||
updateShapePositions();
|
||||
const int BALL_SUBDIVISIONS = 10;
|
||||
for (int i = 0; i < _shapes.size(); i++) {
|
||||
glPushMatrix();
|
||||
|
||||
Shape* shape = _shapes[i];
|
||||
|
||||
glm::vec3 position = extractTranslation(_jointStates[i].transform);
|
||||
glTranslatef(position.x, position.y, position.z);
|
||||
|
||||
glm::quat rotation;
|
||||
getJointRotation(i, rotation);
|
||||
glm::vec3 axis = glm::axis(rotation);
|
||||
glRotatef(glm::angle(rotation), axis.x, axis.y, axis.z);
|
||||
|
||||
glColor4f(0.75f, 0.75f, 0.75f, alpha);
|
||||
float scaledRadius = geometry.joints[i].boneRadius * uniformScale;
|
||||
const int BALL_SUBDIVISIONS = 10;
|
||||
glutSolidSphere(scaledRadius, BALL_SUBDIVISIONS, BALL_SUBDIVISIONS);
|
||||
|
||||
glPopMatrix();
|
||||
|
||||
int parentIndex = geometry.joints[i].parentIndex;
|
||||
if (parentIndex != -1) {
|
||||
Avatar::renderJointConnectingCone(extractTranslation(_jointStates[parentIndex].transform), position,
|
||||
geometry.joints[parentIndex].boneRadius * uniformScale, scaledRadius);
|
||||
if (shape->getType() == Shape::SPHERE_SHAPE) {
|
||||
// shapes are stored in world-frame, so we have to transform into model frame
|
||||
glm::vec3 position = shape->getPosition() - _translation;
|
||||
glTranslatef(position.x, position.y, position.z);
|
||||
const glm::quat& rotation = shape->getRotation();
|
||||
glm::vec3 axis = glm::axis(rotation);
|
||||
glRotatef(glm::angle(rotation), axis.x, axis.y, axis.z);
|
||||
|
||||
// draw a grey sphere at shape position
|
||||
glColor4f(0.75f, 0.75f, 0.75f, alpha);
|
||||
glutSolidSphere(shape->getBoundingRadius(), BALL_SUBDIVISIONS, BALL_SUBDIVISIONS);
|
||||
} else if (shape->getType() == Shape::CAPSULE_SHAPE) {
|
||||
CapsuleShape* capsule = static_cast<CapsuleShape*>(shape);
|
||||
|
||||
// draw a blue sphere at the capsule endpoint
|
||||
glm::vec3 endPoint;
|
||||
capsule->getEndPoint(endPoint);
|
||||
endPoint = endPoint - _translation;
|
||||
glTranslatef(endPoint.x, endPoint.y, endPoint.z);
|
||||
glColor4f(0.6f, 0.6f, 0.8f, alpha);
|
||||
glutSolidSphere(capsule->getRadius(), BALL_SUBDIVISIONS, BALL_SUBDIVISIONS);
|
||||
|
||||
// draw a yellow sphere at the capsule startpoint
|
||||
glm::vec3 startPoint;
|
||||
capsule->getStartPoint(startPoint);
|
||||
startPoint = startPoint - _translation;
|
||||
glm::vec3 axis = endPoint - startPoint;
|
||||
glTranslatef(-axis.x, -axis.y, -axis.z);
|
||||
glColor4f(0.8f, 0.8f, 0.6f, alpha);
|
||||
glutSolidSphere(capsule->getRadius(), BALL_SUBDIVISIONS, BALL_SUBDIVISIONS);
|
||||
|
||||
// draw a green cylinder between the two points
|
||||
glm::vec3 origin(0.f);
|
||||
glColor4f(0.6f, 0.8f, 0.6f, alpha);
|
||||
Avatar::renderJointConnectingCone( origin, axis, capsule->getRadius(), capsule->getRadius());
|
||||
}
|
||||
glPopMatrix();
|
||||
}
|
||||
|
||||
glPopMatrix();
|
||||
}
|
||||
|
||||
|
@ -784,7 +851,7 @@ void Model::applyCollision(CollisionInfo& collision) {
|
|||
QVector<Model::JointState> Model::updateGeometry(bool delayLoad) {
|
||||
QVector<JointState> newJointStates;
|
||||
if (_nextGeometry) {
|
||||
_nextGeometry = _nextGeometry->getLODOrFallback(_lodDistance, _lodHysteresis, delayLoad);
|
||||
_nextGeometry = _nextGeometry->getLODOrFallback(_lodDistance, _nextLODHysteresis, delayLoad);
|
||||
if (!delayLoad) {
|
||||
_nextGeometry->setLoadPriority(this, -_lodDistance);
|
||||
_nextGeometry->ensureLoading();
|
||||
|
@ -827,7 +894,7 @@ void Model::applyNextGeometry() {
|
|||
// delete our local geometry and custom textures
|
||||
deleteGeometry();
|
||||
_dilatedTextures.clear();
|
||||
_lodHysteresis = NetworkGeometry::NO_HYSTERESIS;
|
||||
_lodHysteresis = _nextLODHysteresis;
|
||||
|
||||
// we retain a reference to the base geometry so that its reference count doesn't fall to zero
|
||||
_baseGeometry = _nextBaseGeometry;
|
||||
|
@ -847,6 +914,7 @@ void Model::deleteGeometry() {
|
|||
_blendedVertexBufferIDs.clear();
|
||||
_jointStates.clear();
|
||||
_meshStates.clear();
|
||||
clearShapes();
|
||||
|
||||
if (_geometry) {
|
||||
_geometry->clearLoadPriority(this);
|
||||
|
|
|
@ -17,6 +17,8 @@
|
|||
#include "ProgramObject.h"
|
||||
#include "TextureCache.h"
|
||||
|
||||
class Shape;
|
||||
|
||||
/// A generic 3D model displaying geometry loaded from a URL.
|
||||
class Model : public QObject {
|
||||
Q_OBJECT
|
||||
|
@ -52,6 +54,9 @@ public:
|
|||
|
||||
void init();
|
||||
void reset();
|
||||
void clearShapes();
|
||||
void createCollisionShapes();
|
||||
void updateShapePositions();
|
||||
void simulate(float deltaTime, bool delayLoad = false);
|
||||
bool render(float alpha);
|
||||
|
||||
|
@ -164,9 +169,14 @@ public:
|
|||
glm::vec4 computeAverageColor() const;
|
||||
|
||||
bool findRayIntersection(const glm::vec3& origin, const glm::vec3& direction, float& distance) const;
|
||||
|
||||
/// \param shapes list of pointers shapes to test against Model
|
||||
/// \param collisions list to store collision results
|
||||
/// \return true if at least one shape collided agains Model
|
||||
bool findCollisions(const QVector<const Shape*> shapes, CollisionList& collisions);
|
||||
|
||||
bool findSphereCollisions(const glm::vec3& penetratorCenter, float penetratorRadius,
|
||||
CollisionList& collisions, float boneScale = 1.0f, int skipIndex = -1) const;
|
||||
CollisionList& collisions, int skipIndex = -1);
|
||||
|
||||
void renderCollisionProxies(float alpha);
|
||||
|
||||
|
@ -189,13 +199,15 @@ protected:
|
|||
|
||||
class JointState {
|
||||
public:
|
||||
glm::vec3 translation;
|
||||
glm::quat rotation;
|
||||
glm::mat4 transform;
|
||||
glm::quat combinedRotation;
|
||||
glm::vec3 translation; // translation relative to parent
|
||||
glm::quat rotation; // rotation relative to parent
|
||||
glm::mat4 transform; // rotation to world frame + translation in model frame
|
||||
glm::quat combinedRotation; // rotation from joint local to world frame
|
||||
};
|
||||
|
||||
bool _shapesAreDirty;
|
||||
QVector<JointState> _jointStates;
|
||||
QVector<Shape*> _shapes;
|
||||
|
||||
class MeshState {
|
||||
public:
|
||||
|
@ -247,6 +259,7 @@ private:
|
|||
QSharedPointer<NetworkGeometry> _nextGeometry;
|
||||
float _lodDistance;
|
||||
float _lodHysteresis;
|
||||
float _nextLODHysteresis;
|
||||
|
||||
float _pupilDilation;
|
||||
std::vector<float> _blendshapeCoefficients;
|
||||
|
|
|
@ -113,7 +113,8 @@ GLuint TextureCache::getFileTextureID(const QString& filename) {
|
|||
glBindTexture(GL_TEXTURE_2D, id);
|
||||
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, image.width(), image.height(), 1,
|
||||
GL_BGRA, GL_UNSIGNED_BYTE, image.constBits());
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
|
||||
glGenerateMipmap(GL_TEXTURE_2D);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR);
|
||||
glBindTexture(GL_TEXTURE_2D, 0);
|
||||
|
||||
_fileTextureIDs.insert(filename, id);
|
||||
|
@ -335,7 +336,8 @@ void NetworkTexture::setImage(const QImage& image, const glm::vec4& averageColor
|
|||
glBindTexture(GL_TEXTURE_2D, getID());
|
||||
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, image.width(), image.height(), 1,
|
||||
GL_BGRA, GL_UNSIGNED_BYTE, image.constBits());
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
|
||||
glGenerateMipmap(GL_TEXTURE_2D);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR);
|
||||
glBindTexture(GL_TEXTURE_2D, 0);
|
||||
}
|
||||
|
||||
|
@ -388,7 +390,8 @@ QSharedPointer<Texture> DilatableNetworkTexture::getDilatedTexture(float dilatio
|
|||
glBindTexture(GL_TEXTURE_2D, texture->getID());
|
||||
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, dilatedImage.width(), dilatedImage.height(), 1,
|
||||
GL_BGRA, GL_UNSIGNED_BYTE, dilatedImage.constBits());
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
|
||||
glGenerateMipmap(GL_TEXTURE_2D);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR);
|
||||
glBindTexture(GL_TEXTURE_2D, 0);
|
||||
}
|
||||
|
||||
|
|
|
@ -89,7 +89,7 @@ public:
|
|||
friend class AvatarData;
|
||||
protected:
|
||||
AvatarData* _owningAvatarData;
|
||||
std::vector<PalmData> _palms;
|
||||
std::vector<PalmData> _palms;
|
||||
|
||||
glm::quat getBaseOrientation() const;
|
||||
glm::vec3 getBasePosition() const;
|
||||
|
|
|
@ -17,9 +17,7 @@
|
|||
|
||||
#include <glm/gtc/noise.hpp>
|
||||
|
||||
#include <QtCore/QDebug>
|
||||
#include <QImage>
|
||||
#include <QRgb>
|
||||
#include <QDebug>
|
||||
|
||||
#include "CoverageMap.h"
|
||||
#include <GeometryUtil.h>
|
||||
|
|
|
@ -884,25 +884,25 @@ void Particle::executeUpdateScripts() {
|
|||
}
|
||||
}
|
||||
|
||||
void Particle::collisionWithParticle(Particle* other) {
|
||||
void Particle::collisionWithParticle(Particle* other, const glm::vec3& penetration) {
|
||||
// Only run this particle script if there's a script attached directly to the particle.
|
||||
if (!_script.isEmpty()) {
|
||||
ScriptEngine engine(_script);
|
||||
ParticleScriptObject particleScriptable(this);
|
||||
startParticleScriptContext(engine, particleScriptable);
|
||||
ParticleScriptObject otherParticleScriptable(other);
|
||||
particleScriptable.emitCollisionWithParticle(&otherParticleScriptable);
|
||||
particleScriptable.emitCollisionWithParticle(&otherParticleScriptable, penetration);
|
||||
endParticleScriptContext(engine, particleScriptable);
|
||||
}
|
||||
}
|
||||
|
||||
void Particle::collisionWithVoxel(VoxelDetail* voxelDetails) {
|
||||
void Particle::collisionWithVoxel(VoxelDetail* voxelDetails, const glm::vec3& penetration) {
|
||||
// Only run this particle script if there's a script attached directly to the particle.
|
||||
if (!_script.isEmpty()) {
|
||||
ScriptEngine engine(_script);
|
||||
ParticleScriptObject particleScriptable(this);
|
||||
startParticleScriptContext(engine, particleScriptable);
|
||||
particleScriptable.emitCollisionWithVoxel(*voxelDetails);
|
||||
particleScriptable.emitCollisionWithVoxel(*voxelDetails, penetration);
|
||||
endParticleScriptContext(engine, particleScriptable);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -295,8 +295,8 @@ public:
|
|||
void applyHardCollision(const CollisionInfo& collisionInfo);
|
||||
|
||||
void update(const quint64& now);
|
||||
void collisionWithParticle(Particle* other);
|
||||
void collisionWithVoxel(VoxelDetail* voxel);
|
||||
void collisionWithParticle(Particle* other, const glm::vec3& penetration);
|
||||
void collisionWithVoxel(VoxelDetail* voxel, const glm::vec3& penetration);
|
||||
|
||||
void debugDump() const;
|
||||
|
||||
|
@ -371,8 +371,10 @@ public:
|
|||
//~ParticleScriptObject() { qDebug() << "~ParticleScriptObject() this=" << this; }
|
||||
|
||||
void emitUpdate() { emit update(); }
|
||||
void emitCollisionWithParticle(QObject* other) { emit collisionWithParticle(other); }
|
||||
void emitCollisionWithVoxel(const VoxelDetail& voxel) { emit collisionWithVoxel(voxel); }
|
||||
void emitCollisionWithParticle(QObject* other, const glm::vec3& penetration)
|
||||
{ emit collisionWithParticle(other, penetration); }
|
||||
void emitCollisionWithVoxel(const VoxelDetail& voxel, const glm::vec3& penetration)
|
||||
{ emit collisionWithVoxel(voxel, penetration); }
|
||||
|
||||
public slots:
|
||||
unsigned int getID() const { return _particle->getID(); }
|
||||
|
@ -417,8 +419,8 @@ public slots:
|
|||
|
||||
signals:
|
||||
void update();
|
||||
void collisionWithVoxel(const VoxelDetail& voxel);
|
||||
void collisionWithParticle(QObject* other);
|
||||
void collisionWithVoxel(const VoxelDetail& voxel, const glm::vec3& penetration);
|
||||
void collisionWithParticle(QObject* other, const glm::vec3& penetration);
|
||||
|
||||
private:
|
||||
Particle* _particle;
|
||||
|
|
|
@ -71,15 +71,18 @@ void ParticleCollisionSystem::checkParticle(Particle* particle) {
|
|||
updateCollisionWithAvatars(particle);
|
||||
}
|
||||
|
||||
void ParticleCollisionSystem::emitGlobalParticleCollisionWithVoxel(Particle* particle, VoxelDetail* voxelDetails) {
|
||||
void ParticleCollisionSystem::emitGlobalParticleCollisionWithVoxel(Particle* particle,
|
||||
VoxelDetail* voxelDetails, const glm::vec3& penetration) {
|
||||
ParticleID particleID = particle->getParticleID();
|
||||
emit particleCollisionWithVoxel(particleID, *voxelDetails);
|
||||
emit particleCollisionWithVoxel(particleID, *voxelDetails, penetration);
|
||||
}
|
||||
|
||||
void ParticleCollisionSystem::emitGlobalParticleCollisionWithParticle(Particle* particleA, Particle* particleB) {
|
||||
void ParticleCollisionSystem::emitGlobalParticleCollisionWithParticle(Particle* particleA,
|
||||
Particle* particleB, const glm::vec3& penetration) {
|
||||
|
||||
ParticleID idA = particleA->getParticleID();
|
||||
ParticleID idB = particleB->getParticleID();
|
||||
emit particleCollisionWithParticle(idA, idB);
|
||||
emit particleCollisionWithParticle(idA, idB, penetration);
|
||||
}
|
||||
|
||||
void ParticleCollisionSystem::updateCollisionWithVoxels(Particle* particle) {
|
||||
|
@ -95,10 +98,10 @@ void ParticleCollisionSystem::updateCollisionWithVoxels(Particle* particle) {
|
|||
if (_voxels->findSpherePenetration(center, radius, collisionInfo._penetration, (void**)&voxelDetails)) {
|
||||
|
||||
// let the particles run their collision scripts if they have them
|
||||
particle->collisionWithVoxel(voxelDetails);
|
||||
particle->collisionWithVoxel(voxelDetails, collisionInfo._penetration);
|
||||
|
||||
// let the global script run their collision scripts for particles if they have them
|
||||
emitGlobalParticleCollisionWithVoxel(particle, voxelDetails);
|
||||
emitGlobalParticleCollisionWithVoxel(particle, voxelDetails, collisionInfo._penetration);
|
||||
|
||||
updateCollisionSound(particle, collisionInfo._penetration, COLLISION_FREQUENCY);
|
||||
collisionInfo._penetration /= (float)(TREE_SCALE);
|
||||
|
@ -125,9 +128,9 @@ void ParticleCollisionSystem::updateCollisionWithParticles(Particle* particleA)
|
|||
// we don't want to count this as a collision.
|
||||
glm::vec3 relativeVelocity = particleA->getVelocity() - particleB->getVelocity();
|
||||
if (glm::dot(relativeVelocity, penetration) > 0.0f) {
|
||||
particleA->collisionWithParticle(particleB);
|
||||
particleB->collisionWithParticle(particleA);
|
||||
emitGlobalParticleCollisionWithParticle(particleA, particleB);
|
||||
particleA->collisionWithParticle(particleB, penetration);
|
||||
particleB->collisionWithParticle(particleA, penetration * -1.0f); // the penetration is reversed
|
||||
emitGlobalParticleCollisionWithParticle(particleA, particleB, penetration);
|
||||
|
||||
glm::vec3 axis = glm::normalize(penetration);
|
||||
glm::vec3 axialVelocity = glm::dot(relativeVelocity, axis) * axis;
|
||||
|
|
|
@ -53,13 +53,13 @@ public:
|
|||
void updateCollisionSound(Particle* particle, const glm::vec3 &penetration, float frequency);
|
||||
|
||||
signals:
|
||||
void particleCollisionWithVoxel(const ParticleID& particleID, const VoxelDetail& voxel);
|
||||
void particleCollisionWithParticle(const ParticleID& idA, const ParticleID& idB);
|
||||
void particleCollisionWithVoxel(const ParticleID& particleID, const VoxelDetail& voxel, const glm::vec3& penetration);
|
||||
void particleCollisionWithParticle(const ParticleID& idA, const ParticleID& idB, const glm::vec3& penetration);
|
||||
|
||||
private:
|
||||
static bool updateOperation(OctreeElement* element, void* extraData);
|
||||
void emitGlobalParticleCollisionWithVoxel(Particle* particle, VoxelDetail* voxelDetails);
|
||||
void emitGlobalParticleCollisionWithParticle(Particle* particleA, Particle* particleB);
|
||||
void emitGlobalParticleCollisionWithVoxel(Particle* particle, VoxelDetail* voxelDetails, const glm::vec3& penetration);
|
||||
void emitGlobalParticleCollisionWithParticle(Particle* particleA, Particle* particleB, const glm::vec3& penetration);
|
||||
|
||||
ParticleEditPacketSender* _packetSender;
|
||||
ParticleTree* _particles;
|
||||
|
|
|
@ -29,12 +29,13 @@ public:
|
|||
|
||||
private slots:
|
||||
/// inbound slots for external collision systems
|
||||
void forwardParticleCollisionWithVoxel(const ParticleID& particleID, const VoxelDetail& voxel) {
|
||||
emit particleCollisionWithVoxel(particleID, voxel);
|
||||
void forwardParticleCollisionWithVoxel(const ParticleID& particleID,
|
||||
const VoxelDetail& voxel, const glm::vec3& penetration) {
|
||||
emit particleCollisionWithVoxel(particleID, voxel, penetration);
|
||||
}
|
||||
|
||||
void forwardParticleCollisionWithParticle(const ParticleID& idA, const ParticleID& idB) {
|
||||
emit particleCollisionWithParticle(idA, idB);
|
||||
void forwardParticleCollisionWithParticle(const ParticleID& idA, const ParticleID& idB, const glm::vec3& penetration) {
|
||||
emit particleCollisionWithParticle(idA, idB, penetration);
|
||||
}
|
||||
|
||||
public slots:
|
||||
|
@ -65,8 +66,8 @@ public slots:
|
|||
QVector<ParticleID> findParticles(const glm::vec3& center, float radius) const;
|
||||
|
||||
signals:
|
||||
void particleCollisionWithVoxel(const ParticleID& particleID, const VoxelDetail& voxel);
|
||||
void particleCollisionWithParticle(const ParticleID& idA, const ParticleID& idB);
|
||||
void particleCollisionWithVoxel(const ParticleID& particleID, const VoxelDetail& voxel, const glm::vec3& penetration);
|
||||
void particleCollisionWithParticle(const ParticleID& idA, const ParticleID& idB, const glm::vec3& penetration);
|
||||
|
||||
private:
|
||||
void queueParticleMessage(PacketType packetType, ParticleID particleID, const ParticleProperties& properties);
|
||||
|
|
|
@ -11,6 +11,8 @@
|
|||
|
||||
#include <glm/gtx/vector_angle.hpp>
|
||||
|
||||
#include <QDebug>
|
||||
|
||||
#include <OctreeConstants.h>
|
||||
#include <SharedUtil.h>
|
||||
#include "Quat.h"
|
||||
|
@ -56,3 +58,7 @@ glm::quat Quat::mix(const glm::quat& q1, const glm::quat& q2, float alpha) {
|
|||
return safeMix(q1, q2, alpha);
|
||||
}
|
||||
|
||||
void Quat::print(const QString& lable, const glm::quat& q) {
|
||||
qDebug() << qPrintable(lable) << q.x << "," << q.y << "," << q.z << "," << q.w;
|
||||
}
|
||||
|
||||
|
|
|
@ -13,7 +13,9 @@
|
|||
#define __hifi__Quat__
|
||||
|
||||
#include <glm/gtc/quaternion.hpp>
|
||||
#include <QtCore/QObject>
|
||||
|
||||
#include <QObject>
|
||||
#include <QString>
|
||||
|
||||
/// Scriptable interface a Quaternion helper class object. Used exclusively in the JavaScript API
|
||||
class Quat : public QObject {
|
||||
|
@ -30,6 +32,7 @@ public slots:
|
|||
glm::vec3 safeEulerAngles(const glm::quat& orientation);
|
||||
glm::quat angleAxis(float angle, const glm::vec3& v);
|
||||
glm::quat mix(const glm::quat& q1, const glm::quat& q2, float alpha);
|
||||
void print(const QString& lable, const glm::quat& q);
|
||||
};
|
||||
|
||||
#endif /* defined(__hifi__Quat__) */
|
||||
|
|
|
@ -220,6 +220,8 @@ void ScriptEngine::run() {
|
|||
int thisFrame = 0;
|
||||
|
||||
NodeList* nodeList = NodeList::getInstance();
|
||||
|
||||
qint64 lastUpdate = usecTimestampNow();
|
||||
|
||||
while (!_isFinished) {
|
||||
int usecToSleep = usecTimestamp(&startTime) + (thisFrame++ * VISUAL_DATA_CALLBACK_USECS) - usecTimestampNow();
|
||||
|
@ -264,7 +266,10 @@ void ScriptEngine::run() {
|
|||
nodeList->broadcastToNodes(avatarPacket, NodeSet() << NodeType::AvatarMixer);
|
||||
}
|
||||
|
||||
emit willSendVisualDataCallback();
|
||||
qint64 now = usecTimestampNow();
|
||||
float deltaTime = (float)(now - lastUpdate)/(float)USECS_PER_SECOND;
|
||||
emit update(deltaTime);
|
||||
lastUpdate = now;
|
||||
|
||||
if (_engine.hasUncaughtException()) {
|
||||
int line = _engine.uncaughtExceptionLineNumber();
|
||||
|
|
|
@ -71,8 +71,7 @@ public slots:
|
|||
void clearTimeout(QObject* timer) { stopTimer(reinterpret_cast<QTimer*>(timer)); }
|
||||
|
||||
signals:
|
||||
void willSendAudioDataCallback();
|
||||
void willSendVisualDataCallback();
|
||||
void update(float deltaTime);
|
||||
void scriptEnding();
|
||||
void finished(const QString& fileNameString);
|
||||
void cleanupMenuItem(const QString& menuItemString);
|
||||
|
|
|
@ -9,10 +9,12 @@
|
|||
//
|
||||
//
|
||||
|
||||
#include <QDebug>
|
||||
|
||||
#include "Vec3.h"
|
||||
|
||||
glm::vec3 Vec3::multiply(const glm::vec3& v1, const glm::vec3& v2) {
|
||||
return v1 * v2;
|
||||
glm::vec3 Vec3::cross(const glm::vec3& v1, const glm::vec3& v2) {
|
||||
return glm::cross(v1,v2);
|
||||
}
|
||||
|
||||
glm::vec3 Vec3::multiply(const glm::vec3& v1, float f) {
|
||||
|
@ -29,6 +31,15 @@ glm::vec3 Vec3::sum(const glm::vec3& v1, const glm::vec3& v2) {
|
|||
glm::vec3 Vec3::subtract(const glm::vec3& v1, const glm::vec3& v2) {
|
||||
return v1 - v2;
|
||||
}
|
||||
|
||||
float Vec3::length(const glm::vec3& v) {
|
||||
return glm::length(v);
|
||||
}
|
||||
|
||||
glm::vec3 Vec3::normalize(const glm::vec3& v) {
|
||||
return glm::normalize(v);
|
||||
}
|
||||
|
||||
void Vec3::print(const QString& lable, const glm::vec3& v) {
|
||||
qDebug() << qPrintable(lable) << v.x << "," << v.y << "," << v.z;
|
||||
}
|
||||
|
|
|
@ -14,19 +14,23 @@
|
|||
|
||||
#include <glm/glm.hpp>
|
||||
#include <glm/gtc/quaternion.hpp>
|
||||
#include <QtCore/QObject>
|
||||
|
||||
#include <QObject>
|
||||
#include <QString>
|
||||
|
||||
/// Scriptable interface a Vec3ernion helper class object. Used exclusively in the JavaScript API
|
||||
class Vec3 : public QObject {
|
||||
Q_OBJECT
|
||||
|
||||
public slots:
|
||||
glm::vec3 multiply(const glm::vec3& v1, const glm::vec3& v2);
|
||||
glm::vec3 cross(const glm::vec3& v1, const glm::vec3& v2);
|
||||
glm::vec3 multiply(const glm::vec3& v1, float f);
|
||||
glm::vec3 multiplyQbyV(const glm::quat& q, const glm::vec3& v);
|
||||
glm::vec3 sum(const glm::vec3& v1, const glm::vec3& v2);
|
||||
glm::vec3 subtract(const glm::vec3& v1, const glm::vec3& v2);
|
||||
float length(const glm::vec3& v);
|
||||
glm::vec3 normalize(const glm::vec3& v);
|
||||
void print(const QString& lable, const glm::vec3& v);
|
||||
};
|
||||
|
||||
|
||||
|
|
78
libraries/shared/src/CapsuleShape.cpp
Normal file
78
libraries/shared/src/CapsuleShape.cpp
Normal file
|
@ -0,0 +1,78 @@
|
|||
//
|
||||
// CapsuleShape.cpp
|
||||
// hifi
|
||||
//
|
||||
// Created by Andrew Meadows on 2014.02.20
|
||||
// Copyright (c) 2014 High Fidelity, Inc. All rights reserved.
|
||||
//
|
||||
|
||||
#include <iostream>
|
||||
#include <glm/gtx/vector_angle.hpp>
|
||||
|
||||
#include "CapsuleShape.h"
|
||||
#include "SharedUtil.h"
|
||||
|
||||
|
||||
// default axis of CapsuleShape is Y-axis
|
||||
const glm::vec3 localAxis(0.f, 1.f, 0.f);
|
||||
|
||||
CapsuleShape::CapsuleShape() : Shape(Shape::CAPSULE_SHAPE) {}
|
||||
|
||||
CapsuleShape::CapsuleShape(float radius, float halfHeight) : Shape(Shape::CAPSULE_SHAPE),
|
||||
_radius(radius), _halfHeight(halfHeight) {
|
||||
updateBoundingRadius();
|
||||
}
|
||||
|
||||
CapsuleShape::CapsuleShape(float radius, float halfHeight, const glm::vec3& position, const glm::quat& rotation) :
|
||||
Shape(Shape::CAPSULE_SHAPE, position, rotation), _radius(radius), _halfHeight(halfHeight) {
|
||||
updateBoundingRadius();
|
||||
}
|
||||
|
||||
CapsuleShape::CapsuleShape(float radius, const glm::vec3& startPoint, const glm::vec3& endPoint) :
|
||||
Shape(Shape::CAPSULE_SHAPE), _radius(radius), _halfHeight(0.f) {
|
||||
glm::vec3 axis = endPoint - startPoint;
|
||||
float height = glm::length(axis);
|
||||
if (height > EPSILON) {
|
||||
_halfHeight = 0.5f * height;
|
||||
axis /= height;
|
||||
glm::vec3 yAxis(0.f, 1.f, 0.f);
|
||||
float angle = glm::angle(axis, yAxis);
|
||||
if (angle > EPSILON) {
|
||||
axis = glm::normalize(glm::cross(yAxis, axis));
|
||||
_rotation = glm::angleAxis(angle, axis);
|
||||
}
|
||||
}
|
||||
updateBoundingRadius();
|
||||
}
|
||||
|
||||
/// \param[out] startPoint is the center of start cap
|
||||
void CapsuleShape::getStartPoint(glm::vec3& startPoint) const {
|
||||
startPoint = getPosition() - _rotation * glm::vec3(0.f, _halfHeight, 0.f);
|
||||
}
|
||||
|
||||
/// \param[out] endPoint is the center of the end cap
|
||||
void CapsuleShape::getEndPoint(glm::vec3& endPoint) const {
|
||||
endPoint = getPosition() + _rotation * glm::vec3(0.f, _halfHeight, 0.f);
|
||||
}
|
||||
|
||||
void CapsuleShape::computeNormalizedAxis(glm::vec3& axis) const {
|
||||
// default axis of a capsule is along the yAxis
|
||||
axis = _rotation * glm::vec3(0.f, 1.f, 0.f);
|
||||
}
|
||||
|
||||
void CapsuleShape::setRadius(float radius) {
|
||||
_radius = radius;
|
||||
updateBoundingRadius();
|
||||
}
|
||||
|
||||
void CapsuleShape::setHalfHeight(float halfHeight) {
|
||||
_halfHeight = halfHeight;
|
||||
updateBoundingRadius();
|
||||
}
|
||||
|
||||
void CapsuleShape::setRadiusAndHalfHeight(float radius, float halfHeight) {
|
||||
_radius = radius;
|
||||
_halfHeight = halfHeight;
|
||||
updateBoundingRadius();
|
||||
}
|
||||
|
46
libraries/shared/src/CapsuleShape.h
Normal file
46
libraries/shared/src/CapsuleShape.h
Normal file
|
@ -0,0 +1,46 @@
|
|||
//
|
||||
// CapsuleShape.h
|
||||
// hifi
|
||||
//
|
||||
// Created by Andrew Meadows on 2014.02.20
|
||||
// Copyright (c) 2014 High Fidelity, Inc. All rights reserved.
|
||||
//
|
||||
|
||||
#ifndef __hifi__CapsuleShape__
|
||||
#define __hifi__CapsuleShape__
|
||||
|
||||
#include "Shape.h"
|
||||
|
||||
// adebug bookmark TODO: convert to new world-frame approach
|
||||
// default axis of CapsuleShape is Y-axis
|
||||
|
||||
class CapsuleShape : public Shape {
|
||||
public:
|
||||
CapsuleShape();
|
||||
CapsuleShape(float radius, float halfHeight);
|
||||
CapsuleShape(float radius, float halfHeight, const glm::vec3& position, const glm::quat& rotation);
|
||||
CapsuleShape(float radius, const glm::vec3& startPoint, const glm::vec3& endPoint);
|
||||
|
||||
float getRadius() const { return _radius; }
|
||||
float getHalfHeight() const { return _halfHeight; }
|
||||
|
||||
/// \param[out] startPoint is the center of start cap
|
||||
void getStartPoint(glm::vec3& startPoint) const;
|
||||
|
||||
/// \param[out] endPoint is the center of the end cap
|
||||
void getEndPoint(glm::vec3& endPoint) const;
|
||||
|
||||
void computeNormalizedAxis(glm::vec3& axis) const;
|
||||
|
||||
void setRadius(float radius);
|
||||
void setHalfHeight(float height);
|
||||
void setRadiusAndHalfHeight(float radius, float height);
|
||||
|
||||
protected:
|
||||
void updateBoundingRadius() { _boundingRadius = _radius + _halfHeight; }
|
||||
|
||||
float _radius;
|
||||
float _halfHeight;
|
||||
};
|
||||
|
||||
#endif /* defined(__hifi__CapsuleShape__) */
|
|
@ -8,6 +8,7 @@
|
|||
|
||||
#include "CollisionInfo.h"
|
||||
|
||||
|
||||
CollisionList::CollisionList(int maxSize) :
|
||||
_maxSize(maxSize),
|
||||
_size(0) {
|
||||
|
@ -16,13 +17,17 @@ CollisionList::CollisionList(int maxSize) :
|
|||
|
||||
CollisionInfo* CollisionList::getNewCollision() {
|
||||
// return pointer to existing CollisionInfo, or NULL of list is full
|
||||
return (_size < _maxSize) ? &(_collisions[++_size]) : NULL;
|
||||
return (_size < _maxSize) ? &(_collisions[_size++]) : NULL;
|
||||
}
|
||||
|
||||
CollisionInfo* CollisionList::getCollision(int index) {
|
||||
return (index > -1 && index < _size) ? &(_collisions[index]) : NULL;
|
||||
}
|
||||
|
||||
CollisionInfo* CollisionList::getLastCollision() {
|
||||
return (_size > 0) ? &(_collisions[_size - 1]) : NULL;
|
||||
}
|
||||
|
||||
void CollisionList::clear() {
|
||||
for (int i = 0; i < _size; ++i) {
|
||||
// we only clear the important stuff
|
||||
|
|
|
@ -10,6 +10,7 @@
|
|||
#define __hifi__CollisionInfo__
|
||||
|
||||
#include <glm/glm.hpp>
|
||||
#include <glm/gtc/quaternion.hpp>
|
||||
|
||||
#include <QVector>
|
||||
|
||||
|
@ -79,6 +80,12 @@ public:
|
|||
/// \return pointer to collision by index. NULL if index out of bounds.
|
||||
CollisionInfo* getCollision(int index);
|
||||
|
||||
/// \return pointer to last collision on the list. NULL if list is empty
|
||||
CollisionInfo* getLastCollision();
|
||||
|
||||
/// \return true if list is full
|
||||
bool isFull() const { return _size == _maxSize; }
|
||||
|
||||
/// \return number of valid collisions
|
||||
int size() const { return _size; }
|
||||
|
||||
|
@ -86,8 +93,8 @@ public:
|
|||
void clear();
|
||||
|
||||
private:
|
||||
int _maxSize;
|
||||
int _size;
|
||||
int _maxSize; // the container cannot get larger than this
|
||||
int _size; // the current number of valid collisions in the list
|
||||
QVector<CollisionInfo> _collisions;
|
||||
};
|
||||
|
||||
|
|
88
libraries/shared/src/ListShape.cpp
Normal file
88
libraries/shared/src/ListShape.cpp
Normal file
|
@ -0,0 +1,88 @@
|
|||
//
|
||||
// ListShape.cpp
|
||||
//
|
||||
// ListShape: A collection of shapes, each with a local transform.
|
||||
//
|
||||
// Created by Andrew Meadows on 2014.02.20
|
||||
// Copyright (c) 2014 High Fidelity, Inc. All rights reserved.
|
||||
//
|
||||
|
||||
#include "ListShape.h"
|
||||
|
||||
// ListShapeEntry
|
||||
|
||||
void ListShapeEntry::updateTransform(const glm::vec3& rootPosition, const glm::quat& rootRotation) {
|
||||
_shape->setPosition(rootPosition + rootRotation * _localPosition);
|
||||
_shape->setRotation(_localRotation * rootRotation);
|
||||
}
|
||||
|
||||
// ListShape
|
||||
|
||||
ListShape::~ListShape() {
|
||||
clear();
|
||||
}
|
||||
|
||||
void ListShape::setPosition(const glm::vec3& position) {
|
||||
_subShapeTransformsAreDirty = true;
|
||||
Shape::setPosition(position);
|
||||
}
|
||||
|
||||
void ListShape::setRotation(const glm::quat& rotation) {
|
||||
_subShapeTransformsAreDirty = true;
|
||||
Shape::setRotation(rotation);
|
||||
}
|
||||
|
||||
const Shape* ListShape::getSubShape(int index) const {
|
||||
if (index < 0 || index > _subShapeEntries.size()) {
|
||||
return NULL;
|
||||
}
|
||||
return _subShapeEntries[index]._shape;
|
||||
}
|
||||
|
||||
void ListShape::updateSubTransforms() {
|
||||
if (_subShapeTransformsAreDirty) {
|
||||
for (int i = 0; i < _subShapeEntries.size(); ++i) {
|
||||
_subShapeEntries[i].updateTransform(_position, _rotation);
|
||||
}
|
||||
_subShapeTransformsAreDirty = false;
|
||||
}
|
||||
}
|
||||
|
||||
void ListShape::addShape(Shape* shape, const glm::vec3& localPosition, const glm::quat& localRotation) {
|
||||
if (shape) {
|
||||
ListShapeEntry entry;
|
||||
entry._shape = shape;
|
||||
entry._localPosition = localPosition;
|
||||
entry._localRotation = localRotation;
|
||||
_subShapeEntries.push_back(entry);
|
||||
}
|
||||
}
|
||||
|
||||
void ListShape::setShapes(QVector<ListShapeEntry>& shapes) {
|
||||
clear();
|
||||
_subShapeEntries.swap(shapes);
|
||||
// TODO: audit our new list of entries and delete any that have null pointers
|
||||
computeBoundingRadius();
|
||||
}
|
||||
|
||||
void ListShape::clear() {
|
||||
// the ListShape owns its subShapes, so they need to be deleted
|
||||
for (int i = 0; i < _subShapeEntries.size(); ++i) {
|
||||
delete _subShapeEntries[i]._shape;
|
||||
}
|
||||
_subShapeEntries.clear();
|
||||
setBoundingRadius(0.f);
|
||||
}
|
||||
|
||||
void ListShape::computeBoundingRadius() {
|
||||
float maxRadius = 0.f;
|
||||
for (int i = 0; i < _subShapeEntries.size(); ++i) {
|
||||
ListShapeEntry& entry = _subShapeEntries[i];
|
||||
float radius = glm::length(entry._localPosition) + entry._shape->getBoundingRadius();
|
||||
if (radius > maxRadius) {
|
||||
maxRadius = radius;
|
||||
}
|
||||
}
|
||||
setBoundingRadius(maxRadius);
|
||||
}
|
||||
|
65
libraries/shared/src/ListShape.h
Normal file
65
libraries/shared/src/ListShape.h
Normal file
|
@ -0,0 +1,65 @@
|
|||
//
|
||||
// ListShape.h
|
||||
//
|
||||
// ListShape: A collection of shapes, each with a local transform.
|
||||
//
|
||||
// Created by Andrew Meadows on 2014.02.20
|
||||
// Copyright (c) 2014 High Fidelity, Inc. All rights reserved.
|
||||
//
|
||||
|
||||
#ifndef __hifi__ListShape__
|
||||
#define __hifi__ListShape__
|
||||
|
||||
#include <QVector>
|
||||
|
||||
#include <glm/glm.hpp>
|
||||
#include <glm/gtc/quaternion.hpp>
|
||||
#include <glm/gtx/norm.hpp>
|
||||
|
||||
#include "Shape.h"
|
||||
|
||||
|
||||
class ListShapeEntry {
|
||||
public:
|
||||
void updateTransform(const glm::vec3& position, const glm::quat& rotation);
|
||||
|
||||
Shape* _shape;
|
||||
glm::vec3 _localPosition;
|
||||
glm::quat _localRotation;
|
||||
};
|
||||
|
||||
class ListShape : public Shape {
|
||||
public:
|
||||
|
||||
ListShape() : Shape(LIST_SHAPE), _subShapeEntries(), _subShapeTransformsAreDirty(false) {}
|
||||
|
||||
ListShape(const glm::vec3& position, const glm::quat& rotation) :
|
||||
Shape(LIST_SHAPE, position, rotation), _subShapeEntries(), _subShapeTransformsAreDirty(false) {}
|
||||
|
||||
~ListShape();
|
||||
|
||||
void setPosition(const glm::vec3& position);
|
||||
void setRotation(const glm::quat& rotation);
|
||||
|
||||
const Shape* getSubShape(int index) const;
|
||||
|
||||
void updateSubTransforms();
|
||||
|
||||
int size() const { return _subShapeEntries.size(); }
|
||||
|
||||
void addShape(Shape* shape, const glm::vec3& localPosition, const glm::quat& localRotation);
|
||||
|
||||
void setShapes(QVector<ListShapeEntry>& shapes);
|
||||
|
||||
protected:
|
||||
void clear();
|
||||
void computeBoundingRadius();
|
||||
|
||||
QVector<ListShapeEntry> _subShapeEntries;
|
||||
bool _subShapeTransformsAreDirty;
|
||||
|
||||
private:
|
||||
ListShape(const ListShape& otherList); // don't implement this
|
||||
};
|
||||
|
||||
#endif // __hifi__ListShape__
|
54
libraries/shared/src/Shape.h
Normal file
54
libraries/shared/src/Shape.h
Normal file
|
@ -0,0 +1,54 @@
|
|||
//
|
||||
// Shape.h
|
||||
//
|
||||
// Created by Andrew Meadows on 2014.02.20
|
||||
// Copyright (c) 2014 High Fidelity, Inc. All rights reserved.
|
||||
//
|
||||
|
||||
#ifndef __hifi__Shape__
|
||||
#define __hifi__Shape__
|
||||
|
||||
#include <glm/glm.hpp>
|
||||
#include <glm/gtc/quaternion.hpp>
|
||||
|
||||
|
||||
class Shape {
|
||||
public:
|
||||
enum Type{
|
||||
UNKNOWN_SHAPE = 0,
|
||||
SPHERE_SHAPE,
|
||||
CAPSULE_SHAPE,
|
||||
BOX_SHAPE,
|
||||
LIST_SHAPE
|
||||
};
|
||||
|
||||
Shape() : _type(UNKNOWN_SHAPE), _boundingRadius(0.f), _position(0.f), _rotation() { }
|
||||
virtual ~Shape() {}
|
||||
|
||||
int getType() const { return _type; }
|
||||
float getBoundingRadius() const { return _boundingRadius; }
|
||||
const glm::vec3& getPosition() const { return _position; }
|
||||
const glm::quat& getRotation() const { return _rotation; }
|
||||
|
||||
virtual void setPosition(const glm::vec3& position) { _position = position; }
|
||||
virtual void setRotation(const glm::quat& rotation) { _rotation = rotation; }
|
||||
|
||||
protected:
|
||||
// these ctors are protected (used by derived classes only)
|
||||
Shape(Type type) : _type(type), _boundingRadius(0.f), _position(0.f), _rotation() {}
|
||||
|
||||
Shape(Type type, const glm::vec3& position)
|
||||
: _type(type), _boundingRadius(0.f), _position(position), _rotation() {}
|
||||
|
||||
Shape(Type type, const glm::vec3& position, const glm::quat& rotation)
|
||||
: _type(type), _boundingRadius(0.f), _position(position), _rotation(rotation) {}
|
||||
|
||||
void setBoundingRadius(float radius) { _boundingRadius = radius; }
|
||||
|
||||
int _type;
|
||||
float _boundingRadius;
|
||||
glm::vec3 _position;
|
||||
glm::quat _rotation;
|
||||
};
|
||||
|
||||
#endif /* defined(__hifi__Shape__) */
|
408
libraries/shared/src/ShapeCollider.cpp
Normal file
408
libraries/shared/src/ShapeCollider.cpp
Normal file
|
@ -0,0 +1,408 @@
|
|||
//
|
||||
// ShapeCollider.cpp
|
||||
// hifi
|
||||
//
|
||||
// Created by Andrew Meadows on 2014.02.20
|
||||
// Copyright (c) 2014 High Fidelity, Inc. All rights reserved.
|
||||
//
|
||||
|
||||
#include <iostream>
|
||||
|
||||
#include <glm/gtx/norm.hpp>
|
||||
|
||||
#include "ShapeCollider.h"
|
||||
|
||||
// NOTE:
|
||||
//
|
||||
// * Large ListShape's are inefficient keep the lists short.
|
||||
// * Collisions between lists of lists work in theory but are not recommended.
|
||||
|
||||
namespace ShapeCollider {
|
||||
|
||||
bool shapeShape(const Shape* shapeA, const Shape* shapeB, CollisionList& collisions) {
|
||||
// ATM we only have two shape types so we just check every case.
|
||||
// TODO: make a fast lookup for correct method
|
||||
int typeA = shapeA->getType();
|
||||
int typeB = shapeB->getType();
|
||||
if (typeA == Shape::SPHERE_SHAPE) {
|
||||
const SphereShape* sphereA = static_cast<const SphereShape*>(shapeA);
|
||||
if (typeB == Shape::SPHERE_SHAPE) {
|
||||
return sphereSphere(sphereA, static_cast<const SphereShape*>(shapeB), collisions);
|
||||
} else if (typeB == Shape::CAPSULE_SHAPE) {
|
||||
return sphereCapsule(sphereA, static_cast<const CapsuleShape*>(shapeB), collisions);
|
||||
}
|
||||
} else if (typeA == Shape::CAPSULE_SHAPE) {
|
||||
const CapsuleShape* capsuleA = static_cast<const CapsuleShape*>(shapeA);
|
||||
if (typeB == Shape::SPHERE_SHAPE) {
|
||||
return capsuleSphere(capsuleA, static_cast<const SphereShape*>(shapeB), collisions);
|
||||
} else if (typeB == Shape::CAPSULE_SHAPE) {
|
||||
return capsuleCapsule(capsuleA, static_cast<const CapsuleShape*>(shapeB), collisions);
|
||||
}
|
||||
} else if (typeA == Shape::LIST_SHAPE) {
|
||||
const ListShape* listA = static_cast<const ListShape*>(shapeA);
|
||||
if (typeB == Shape::SPHERE_SHAPE) {
|
||||
return listSphere(listA, static_cast<const SphereShape*>(shapeB), collisions);
|
||||
} else if (typeB == Shape::CAPSULE_SHAPE) {
|
||||
return listCapsule(listA, static_cast<const CapsuleShape*>(shapeB), collisions);
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool sphereSphere(const SphereShape* sphereA, const SphereShape* sphereB, CollisionList& collisions) {
|
||||
glm::vec3 BA = sphereB->getPosition() - sphereA->getPosition();
|
||||
float distanceSquared = glm::dot(BA, BA);
|
||||
float totalRadius = sphereA->getRadius() + sphereB->getRadius();
|
||||
if (distanceSquared < totalRadius * totalRadius) {
|
||||
// normalize BA
|
||||
float distance = sqrtf(distanceSquared);
|
||||
if (distance < EPSILON) {
|
||||
// the spheres are on top of each other, so we pick an arbitrary penetration direction
|
||||
BA = glm::vec3(0.f, 1.f, 0.f);
|
||||
distance = totalRadius;
|
||||
} else {
|
||||
BA /= distance;
|
||||
}
|
||||
// penetration points from A into B
|
||||
CollisionInfo* collision = collisions.getNewCollision();
|
||||
if (collision) {
|
||||
collision->_penetration = BA * (totalRadius - distance);
|
||||
// contactPoint is on surface of A
|
||||
collision->_contactPoint = sphereA->getPosition() + sphereA->getRadius() * BA;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool sphereCapsule(const SphereShape* sphereA, const CapsuleShape* capsuleB, CollisionList& collisions) {
|
||||
// find sphereA's closest approach to axis of capsuleB
|
||||
glm::vec3 BA = capsuleB->getPosition() - sphereA->getPosition();
|
||||
glm::vec3 capsuleAxis;
|
||||
capsuleB->computeNormalizedAxis(capsuleAxis);
|
||||
float axialDistance = - glm::dot(BA, capsuleAxis);
|
||||
float absAxialDistance = fabs(axialDistance);
|
||||
float totalRadius = sphereA->getRadius() + capsuleB->getRadius();
|
||||
if (absAxialDistance < totalRadius + capsuleB->getHalfHeight()) {
|
||||
glm::vec3 radialAxis = BA + axialDistance * capsuleAxis; // points from A to axis of B
|
||||
float radialDistance2 = glm::length2(radialAxis);
|
||||
if (radialDistance2 > totalRadius * totalRadius) {
|
||||
// sphere is too far from capsule axis
|
||||
return false;
|
||||
}
|
||||
if (absAxialDistance > capsuleB->getHalfHeight()) {
|
||||
// sphere hits capsule on a cap --> recompute radialAxis to point from spherA to cap center
|
||||
float sign = (axialDistance > 0.f) ? 1.f : -1.f;
|
||||
radialAxis = BA + (sign * capsuleB->getHalfHeight()) * capsuleAxis;
|
||||
radialDistance2 = glm::length2(radialAxis);
|
||||
}
|
||||
if (radialDistance2 > EPSILON * EPSILON) {
|
||||
CollisionInfo* collision = collisions.getNewCollision();
|
||||
if (!collision) {
|
||||
// collisions list is full
|
||||
return false;
|
||||
}
|
||||
// normalize the radialAxis
|
||||
float radialDistance = sqrtf(radialDistance2);
|
||||
radialAxis /= radialDistance;
|
||||
// penetration points from A into B
|
||||
collision->_penetration = (totalRadius - radialDistance) * radialAxis; // points from A into B
|
||||
// contactPoint is on surface of sphereA
|
||||
collision->_contactPoint = sphereA->getPosition() + sphereA->getRadius() * radialAxis;
|
||||
} else {
|
||||
// A is on B's axis, so the penetration is undefined...
|
||||
if (absAxialDistance > capsuleB->getHalfHeight()) {
|
||||
// ...for the cylinder case (for now we pretend the collision doesn't exist)
|
||||
return false;
|
||||
}
|
||||
CollisionInfo* collision = collisions.getNewCollision();
|
||||
if (!collision) {
|
||||
// collisions list is full
|
||||
return false;
|
||||
}
|
||||
// ... but still defined for the cap case
|
||||
if (axialDistance < 0.f) {
|
||||
// we're hitting the start cap, so we negate the capsuleAxis
|
||||
capsuleAxis *= -1;
|
||||
}
|
||||
// penetration points from A into B
|
||||
float sign = (axialDistance > 0.f) ? -1.f : 1.f;
|
||||
collision->_penetration = (sign * (totalRadius + capsuleB->getHalfHeight() - absAxialDistance)) * capsuleAxis;
|
||||
// contactPoint is on surface of sphereA
|
||||
collision->_contactPoint = sphereA->getPosition() + (sign * sphereA->getRadius()) * capsuleAxis;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool capsuleSphere(const CapsuleShape* capsuleA, const SphereShape* sphereB, CollisionList& collisions) {
|
||||
// find sphereB's closest approach to axis of capsuleA
|
||||
glm::vec3 AB = capsuleA->getPosition() - sphereB->getPosition();
|
||||
glm::vec3 capsuleAxis;
|
||||
capsuleA->computeNormalizedAxis(capsuleAxis);
|
||||
float axialDistance = - glm::dot(AB, capsuleAxis);
|
||||
float absAxialDistance = fabs(axialDistance);
|
||||
float totalRadius = sphereB->getRadius() + capsuleA->getRadius();
|
||||
if (absAxialDistance < totalRadius + capsuleA->getHalfHeight()) {
|
||||
glm::vec3 radialAxis = AB + axialDistance * capsuleAxis; // from sphereB to axis of capsuleA
|
||||
float radialDistance2 = glm::length2(radialAxis);
|
||||
if (radialDistance2 > totalRadius * totalRadius) {
|
||||
// sphere is too far from capsule axis
|
||||
return false;
|
||||
}
|
||||
|
||||
// closestApproach = point on capsuleA's axis that is closest to sphereB's center
|
||||
glm::vec3 closestApproach = capsuleA->getPosition() + axialDistance * capsuleAxis;
|
||||
|
||||
if (absAxialDistance > capsuleA->getHalfHeight()) {
|
||||
// sphere hits capsule on a cap
|
||||
// --> recompute radialAxis and closestApproach
|
||||
float sign = (axialDistance > 0.f) ? 1.f : -1.f;
|
||||
closestApproach = capsuleA->getPosition() + (sign * capsuleA->getHalfHeight()) * capsuleAxis;
|
||||
radialAxis = closestApproach - sphereB->getPosition();
|
||||
radialDistance2 = glm::length2(radialAxis);
|
||||
}
|
||||
if (radialDistance2 > EPSILON * EPSILON) {
|
||||
CollisionInfo* collision = collisions.getNewCollision();
|
||||
if (!collision) {
|
||||
// collisions list is full
|
||||
return false;
|
||||
}
|
||||
// normalize the radialAxis
|
||||
float radialDistance = sqrtf(radialDistance2);
|
||||
radialAxis /= radialDistance;
|
||||
// penetration points from A into B
|
||||
collision->_penetration = (radialDistance - totalRadius) * radialAxis; // points from A into B
|
||||
// contactPoint is on surface of capsuleA
|
||||
collision->_contactPoint = closestApproach - capsuleA->getRadius() * radialAxis;
|
||||
} else {
|
||||
// A is on B's axis, so the penetration is undefined...
|
||||
if (absAxialDistance > capsuleA->getHalfHeight()) {
|
||||
// ...for the cylinder case (for now we pretend the collision doesn't exist)
|
||||
return false;
|
||||
} else {
|
||||
CollisionInfo* collision = collisions.getNewCollision();
|
||||
if (!collision) {
|
||||
// collisions list is full
|
||||
return false;
|
||||
}
|
||||
// ... but still defined for the cap case
|
||||
if (axialDistance < 0.f) {
|
||||
// we're hitting the start cap, so we negate the capsuleAxis
|
||||
capsuleAxis *= -1;
|
||||
}
|
||||
float sign = (axialDistance > 0.f) ? 1.f : -1.f;
|
||||
collision->_penetration = (sign * (totalRadius + capsuleA->getHalfHeight() - absAxialDistance)) * capsuleAxis;
|
||||
// contactPoint is on surface of sphereA
|
||||
collision->_contactPoint = closestApproach + (sign * capsuleA->getRadius()) * capsuleAxis;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool capsuleCapsule(const CapsuleShape* capsuleA, const CapsuleShape* capsuleB, CollisionList& collisions) {
|
||||
glm::vec3 axisA;
|
||||
capsuleA->computeNormalizedAxis(axisA);
|
||||
glm::vec3 axisB;
|
||||
capsuleB->computeNormalizedAxis(axisB);
|
||||
glm::vec3 centerA = capsuleA->getPosition();
|
||||
glm::vec3 centerB = capsuleB->getPosition();
|
||||
|
||||
// NOTE: The formula for closest approach between two lines is:
|
||||
// d = [(B - A) . (a - (a.b)b)] / (1 - (a.b)^2)
|
||||
|
||||
float aDotB = glm::dot(axisA, axisB);
|
||||
float denominator = 1.f - aDotB * aDotB;
|
||||
float totalRadius = capsuleA->getRadius() + capsuleB->getRadius();
|
||||
if (denominator > EPSILON) {
|
||||
// distances to points of closest approach
|
||||
float distanceA = glm::dot((centerB - centerA), (axisA - (aDotB) * axisB)) / denominator;
|
||||
float distanceB = glm::dot((centerA - centerB), (axisB - (aDotB) * axisA)) / denominator;
|
||||
|
||||
// clamp the distances to the ends of the capsule line segments
|
||||
float absDistanceA = fabs(distanceA);
|
||||
if (absDistanceA > capsuleA->getHalfHeight() + capsuleA->getRadius()) {
|
||||
float signA = distanceA < 0.f ? -1.f : 1.f;
|
||||
distanceA = signA * capsuleA->getHalfHeight();
|
||||
}
|
||||
float absDistanceB = fabs(distanceB);
|
||||
if (absDistanceB > capsuleB->getHalfHeight() + capsuleB->getRadius()) {
|
||||
float signB = distanceB < 0.f ? -1.f : 1.f;
|
||||
distanceB = signB * capsuleB->getHalfHeight();
|
||||
}
|
||||
|
||||
// collide like spheres at closest approaches (do most of the math relative to B)
|
||||
glm::vec3 BA = (centerB + distanceB * axisB) - (centerA + distanceA * axisA);
|
||||
float distanceSquared = glm::dot(BA, BA);
|
||||
if (distanceSquared < totalRadius * totalRadius) {
|
||||
CollisionInfo* collision = collisions.getNewCollision();
|
||||
if (!collision) {
|
||||
// collisions list is full
|
||||
return false;
|
||||
}
|
||||
// normalize BA
|
||||
float distance = sqrtf(distanceSquared);
|
||||
if (distance < EPSILON) {
|
||||
// the contact spheres are on top of each other, so we need to pick a penetration direction...
|
||||
// try vector between the capsule centers...
|
||||
BA = centerB - centerA;
|
||||
distanceSquared = glm::length2(BA);
|
||||
if (distanceSquared > EPSILON * EPSILON) {
|
||||
distance = sqrtf(distanceSquared);
|
||||
BA /= distance;
|
||||
} else
|
||||
{
|
||||
// the capsule centers are on top of each other!
|
||||
// give up on a valid penetration direction and just use the yAxis
|
||||
BA = glm::vec3(0.f, 1.f, 0.f);
|
||||
distance = glm::max(capsuleB->getRadius(), capsuleA->getRadius());
|
||||
}
|
||||
} else {
|
||||
BA /= distance;
|
||||
}
|
||||
// penetration points from A into B
|
||||
collision->_penetration = BA * (totalRadius - distance);
|
||||
// contactPoint is on surface of A
|
||||
collision->_contactPoint = centerA + distanceA * axisA + capsuleA->getRadius() * BA;
|
||||
return true;
|
||||
}
|
||||
} else {
|
||||
// capsules are approximiately parallel but might still collide
|
||||
glm::vec3 BA = centerB - centerA;
|
||||
float axialDistance = glm::dot(BA, axisB);
|
||||
if (axialDistance > totalRadius + capsuleA->getHalfHeight() + capsuleB->getHalfHeight()) {
|
||||
return false;
|
||||
}
|
||||
BA = BA - axialDistance * axisB; // BA now points from centerA to axisB (perp to axis)
|
||||
float distanceSquared = glm::length2(BA);
|
||||
if (distanceSquared < totalRadius * totalRadius) {
|
||||
CollisionInfo* collision = collisions.getNewCollision();
|
||||
if (!collision) {
|
||||
// collisions list is full
|
||||
return false;
|
||||
}
|
||||
// We have all the info we need to compute the penetration vector...
|
||||
// normalize BA
|
||||
float distance = sqrtf(distanceSquared);
|
||||
if (distance < EPSILON) {
|
||||
// the spheres are on top of each other, so we pick an arbitrary penetration direction
|
||||
BA = glm::vec3(0.f, 1.f, 0.f);
|
||||
} else {
|
||||
BA /= distance;
|
||||
}
|
||||
// penetration points from A into B
|
||||
collision->_penetration = BA * (totalRadius - distance);
|
||||
|
||||
// However we need some more world-frame info to compute the contactPoint,
|
||||
// which is on the surface of capsuleA...
|
||||
//
|
||||
// Find the overlapping secion of the capsules --> they collide as if there were
|
||||
// two spheres at the midpoint of this overlapping section.
|
||||
// So we project all endpoints to axisB, find the interior pair,
|
||||
// and put A's proxy sphere on axisA at the midpoint of this section.
|
||||
|
||||
// sort the projections as much as possible during calculation
|
||||
float points[5];
|
||||
points[0] = -capsuleB->getHalfHeight();
|
||||
points[1] = axialDistance - capsuleA->getHalfHeight();
|
||||
points[2] = axialDistance + capsuleA->getHalfHeight();
|
||||
points[3] = capsuleB->getHalfHeight();
|
||||
|
||||
// Since there are only three comparisons to do we unroll the sort algorithm...
|
||||
// and use a fifth slot as temp during swap.
|
||||
if (points[4] > points[2]) {
|
||||
points[4] = points[1];
|
||||
points[1] = points[2];
|
||||
points[2] = points[4];
|
||||
}
|
||||
if (points[2] > points[3]) {
|
||||
points[4] = points[2];
|
||||
points[2] = points[3];
|
||||
points[3] = points[4];
|
||||
}
|
||||
if (points[0] > points[1]) {
|
||||
points[4] = points[0];
|
||||
points[0] = points[1];
|
||||
points[1] = points[4];
|
||||
}
|
||||
|
||||
// average the internal pair, and then do the math from centerB
|
||||
collision->_contactPoint = centerB + (0.5f * (points[1] + points[2])) * axisB
|
||||
+ (capsuleA->getRadius() - distance) * BA;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool sphereList(const SphereShape* sphereA, const ListShape* listB, CollisionList& collisions) {
|
||||
bool touching = false;
|
||||
for (int i = 0; i < listB->size() && !collisions.isFull(); ++i) {
|
||||
const Shape* subShape = listB->getSubShape(i);
|
||||
int subType = subShape->getType();
|
||||
if (subType == Shape::SPHERE_SHAPE) {
|
||||
touching = sphereSphere(sphereA, static_cast<const SphereShape*>(subShape), collisions) || touching;
|
||||
} else if (subType == Shape::CAPSULE_SHAPE) {
|
||||
touching = sphereCapsule(sphereA, static_cast<const CapsuleShape*>(subShape), collisions) || touching;
|
||||
}
|
||||
}
|
||||
return touching;
|
||||
}
|
||||
|
||||
bool capsuleList(const CapsuleShape* capsuleA, const ListShape* listB, CollisionList& collisions) {
|
||||
bool touching = false;
|
||||
for (int i = 0; i < listB->size() && !collisions.isFull(); ++i) {
|
||||
const Shape* subShape = listB->getSubShape(i);
|
||||
int subType = subShape->getType();
|
||||
if (subType == Shape::SPHERE_SHAPE) {
|
||||
touching = capsuleSphere(capsuleA, static_cast<const SphereShape*>(subShape), collisions) || touching;
|
||||
} else if (subType == Shape::CAPSULE_SHAPE) {
|
||||
touching = capsuleCapsule(capsuleA, static_cast<const CapsuleShape*>(subShape), collisions) || touching;
|
||||
}
|
||||
}
|
||||
return touching;
|
||||
}
|
||||
|
||||
bool listSphere(const ListShape* listA, const SphereShape* sphereB, CollisionList& collisions) {
|
||||
bool touching = false;
|
||||
for (int i = 0; i < listA->size() && !collisions.isFull(); ++i) {
|
||||
const Shape* subShape = listA->getSubShape(i);
|
||||
int subType = subShape->getType();
|
||||
if (subType == Shape::SPHERE_SHAPE) {
|
||||
touching = sphereSphere(static_cast<const SphereShape*>(subShape), sphereB, collisions) || touching;
|
||||
} else if (subType == Shape::CAPSULE_SHAPE) {
|
||||
touching = capsuleSphere(static_cast<const CapsuleShape*>(subShape), sphereB, collisions) || touching;
|
||||
}
|
||||
}
|
||||
return touching;
|
||||
}
|
||||
|
||||
bool listCapsule(const ListShape* listA, const CapsuleShape* capsuleB, CollisionList& collisions) {
|
||||
bool touching = false;
|
||||
for (int i = 0; i < listA->size() && !collisions.isFull(); ++i) {
|
||||
const Shape* subShape = listA->getSubShape(i);
|
||||
int subType = subShape->getType();
|
||||
if (subType == Shape::SPHERE_SHAPE) {
|
||||
touching = sphereCapsule(static_cast<const SphereShape*>(subShape), capsuleB, collisions) || touching;
|
||||
} else if (subType == Shape::CAPSULE_SHAPE) {
|
||||
touching = capsuleCapsule(static_cast<const CapsuleShape*>(subShape), capsuleB, collisions) || touching;
|
||||
}
|
||||
}
|
||||
return touching;
|
||||
}
|
||||
|
||||
bool listList(const ListShape* listA, const ListShape* listB, CollisionList& collisions) {
|
||||
bool touching = false;
|
||||
for (int i = 0; i < listA->size() && !collisions.isFull(); ++i) {
|
||||
const Shape* subShape = listA->getSubShape(i);
|
||||
for (int j = 0; j < listB->size() && !collisions.isFull(); ++j) {
|
||||
touching = shapeShape(subShape, listB->getSubShape(j), collisions) || touching;
|
||||
}
|
||||
}
|
||||
return touching;
|
||||
}
|
||||
|
||||
} // namespace ShapeCollider
|
82
libraries/shared/src/ShapeCollider.h
Normal file
82
libraries/shared/src/ShapeCollider.h
Normal file
|
@ -0,0 +1,82 @@
|
|||
//
|
||||
// ShapeCollider.h
|
||||
// hifi
|
||||
//
|
||||
// Created by Andrew Meadows on 2014.02.20
|
||||
// Copyright (c) 2014 High Fidelity, Inc. All rights reserved.
|
||||
//
|
||||
|
||||
#ifndef __hifi__ShapeCollider__
|
||||
#define __hifi__ShapeCollider__
|
||||
|
||||
#include "CapsuleShape.h"
|
||||
#include "CollisionInfo.h"
|
||||
#include "ListShape.h"
|
||||
#include "SharedUtil.h"
|
||||
#include "SphereShape.h"
|
||||
|
||||
namespace ShapeCollider {
|
||||
|
||||
/// \param shapeA pointer to first shape
|
||||
/// \param shapeB pointer to second shape
|
||||
/// \param[out] collisions where to append collision details
|
||||
/// \return true if shapes collide
|
||||
bool shapeShape(const Shape* shapeA, const Shape* shapeB, CollisionList& collisions);
|
||||
|
||||
/// \param sphereA pointer to first shape
|
||||
/// \param sphereB pointer to second shape
|
||||
/// \param[out] collisions where to append collision details
|
||||
/// \return true if shapes collide
|
||||
bool sphereSphere(const SphereShape* sphereA, const SphereShape* sphereB, CollisionList& collisions);
|
||||
|
||||
/// \param sphereA pointer to first shape
|
||||
/// \param capsuleB pointer to second shape
|
||||
/// \param[out] collisions where to append collision details
|
||||
/// \return true if shapes collide
|
||||
bool sphereCapsule(const SphereShape* sphereA, const CapsuleShape* capsuleB, CollisionList& collisions);
|
||||
|
||||
/// \param capsuleA pointer to first shape
|
||||
/// \param sphereB pointer to second shape
|
||||
/// \param[out] collisions where to append collision details
|
||||
/// \return true if shapes collide
|
||||
bool capsuleSphere(const CapsuleShape* capsuleA, const SphereShape* sphereB, CollisionList& collisions);
|
||||
|
||||
/// \param capsuleA pointer to first shape
|
||||
/// \param capsuleB pointer to second shape
|
||||
/// \param[out] collisions where to append collision details
|
||||
/// \return true if shapes collide
|
||||
bool capsuleCapsule(const CapsuleShape* capsuleA, const CapsuleShape* capsuleB, CollisionList& collisions);
|
||||
|
||||
/// \param sphereA pointer to first shape
|
||||
/// \param listB pointer to second shape
|
||||
/// \param[out] collisions where to append collision details
|
||||
/// \return true if shapes collide
|
||||
bool sphereList(const SphereShape* sphereA, const ListShape* listB, CollisionList& collisions);
|
||||
|
||||
/// \param capuleA pointer to first shape
|
||||
/// \param listB pointer to second shape
|
||||
/// \param[out] collisions where to append collision details
|
||||
/// \return true if shapes collide
|
||||
bool capsuleList(const CapsuleShape* capsuleA, const ListShape* listB, CollisionList& collisions);
|
||||
|
||||
/// \param listA pointer to first shape
|
||||
/// \param sphereB pointer to second shape
|
||||
/// \param[out] collisions where to append collision details
|
||||
/// \return true if shapes collide
|
||||
bool listSphere(const ListShape* listA, const SphereShape* sphereB, CollisionList& collisions);
|
||||
|
||||
/// \param listA pointer to first shape
|
||||
/// \param capsuleB pointer to second shape
|
||||
/// \param[out] collisions where to append collision details
|
||||
/// \return true if shapes collide
|
||||
bool listCapsule(const ListShape* listA, const CapsuleShape* capsuleB, CollisionList& collisions);
|
||||
|
||||
/// \param listA pointer to first shape
|
||||
/// \param capsuleB pointer to second shape
|
||||
/// \param[out] collisions where to append collision details
|
||||
/// \return true if shapes collide
|
||||
bool listList(const ListShape* listA, const ListShape* listB, CollisionList& collisions);
|
||||
|
||||
} // namespace ShapeCollider
|
||||
|
||||
#endif // __hifi__ShapeCollider__
|
31
libraries/shared/src/SphereShape.h
Normal file
31
libraries/shared/src/SphereShape.h
Normal file
|
@ -0,0 +1,31 @@
|
|||
//
|
||||
// SphereShape.h
|
||||
// hifi
|
||||
//
|
||||
// Created by Andrew Meadows on 2014.02.20
|
||||
// Copyright (c) 2014 High Fidelity, Inc. All rights reserved.
|
||||
//
|
||||
|
||||
#ifndef __hifi__SphereShape__
|
||||
#define __hifi__SphereShape__
|
||||
|
||||
#include "Shape.h"
|
||||
|
||||
class SphereShape : public Shape {
|
||||
public:
|
||||
SphereShape() : Shape(Shape::SPHERE_SHAPE) {}
|
||||
|
||||
SphereShape(float radius) : Shape(Shape::SPHERE_SHAPE) {
|
||||
_boundingRadius = radius;
|
||||
}
|
||||
|
||||
SphereShape(float radius, const glm::vec3& position) : Shape(Shape::SPHERE_SHAPE, position) {
|
||||
_boundingRadius = radius;
|
||||
}
|
||||
|
||||
float getRadius() const { return _boundingRadius; }
|
||||
|
||||
void setRadius(float radius) { _boundingRadius = radius; }
|
||||
};
|
||||
|
||||
#endif /* defined(__hifi__SphereShape__) */
|
34
tests/physics/CMakeLists.txt
Normal file
34
tests/physics/CMakeLists.txt
Normal file
|
@ -0,0 +1,34 @@
|
|||
cmake_minimum_required(VERSION 2.8)
|
||||
|
||||
set(TARGET_NAME physics-tests)
|
||||
|
||||
set(ROOT_DIR ../..)
|
||||
set(MACRO_DIR ${ROOT_DIR}/cmake/macros)
|
||||
|
||||
# setup for find modules
|
||||
set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} "${CMAKE_CURRENT_SOURCE_DIR}/../../cmake/modules/")
|
||||
|
||||
#find_package(Qt5Network REQUIRED)
|
||||
#find_package(Qt5Script REQUIRED)
|
||||
#find_package(Qt5Widgets REQUIRED)
|
||||
|
||||
include(${MACRO_DIR}/SetupHifiProject.cmake)
|
||||
setup_hifi_project(${TARGET_NAME} TRUE)
|
||||
|
||||
include(${MACRO_DIR}/AutoMTC.cmake)
|
||||
auto_mtc(${TARGET_NAME} ${ROOT_DIR})
|
||||
|
||||
#qt5_use_modules(${TARGET_NAME} Network Script Widgets)
|
||||
|
||||
#include glm
|
||||
include(${MACRO_DIR}/IncludeGLM.cmake)
|
||||
include_glm(${TARGET_NAME} ${ROOT_DIR})
|
||||
|
||||
# link in the shared libraries
|
||||
include(${MACRO_DIR}/LinkHifiLibrary.cmake)
|
||||
link_hifi_library(shared ${TARGET_NAME} ${ROOT_DIR})
|
||||
|
||||
IF (WIN32)
|
||||
#target_link_libraries(${TARGET_NAME} Winmm Ws2_32)
|
||||
ENDIF(WIN32)
|
||||
|
104
tests/physics/src/CollisionInfoTests.cpp
Normal file
104
tests/physics/src/CollisionInfoTests.cpp
Normal file
|
@ -0,0 +1,104 @@
|
|||
//
|
||||
// CollisionInfoTests.cpp
|
||||
// physics-tests
|
||||
//
|
||||
// Created by Andrew Meadows on 2014.02.21
|
||||
// Copyright (c) 2014 High Fidelity, Inc. All rights reserved.
|
||||
//
|
||||
|
||||
#include <iostream>
|
||||
|
||||
#include <glm/glm.hpp>
|
||||
#include <glm/gtx/quaternion.hpp>
|
||||
|
||||
#include <CollisionInfo.h>
|
||||
#include <SharedUtil.h>
|
||||
|
||||
#include "CollisionInfoTests.h"
|
||||
#include "PhysicsTestUtil.h"
|
||||
|
||||
|
||||
/*
|
||||
void CollisionInfoTests::rotateThenTranslate() {
|
||||
CollisionInfo collision;
|
||||
collision._penetration = xAxis;
|
||||
collision._contactPoint = yAxis;
|
||||
collision._addedVelocity = xAxis + yAxis + zAxis;
|
||||
|
||||
glm::quat rotation = glm::angleAxis(rightAngle, zAxis);
|
||||
float distance = 3.f;
|
||||
glm::vec3 translation = distance * yAxis;
|
||||
|
||||
collision.rotateThenTranslate(rotation, translation);
|
||||
|
||||
float error = glm::distance(collision._penetration, yAxis);
|
||||
if (error > EPSILON) {
|
||||
std::cout << __FILE__ << ":" << __LINE__
|
||||
<< " ERROR: collision._penetration = " << collision._penetration
|
||||
<< " but we expected " << yAxis
|
||||
<< std::endl;
|
||||
}
|
||||
|
||||
glm::vec3 expectedContactPoint = -xAxis + translation;
|
||||
error = glm::distance(collision._contactPoint, expectedContactPoint);
|
||||
if (error > EPSILON) {
|
||||
std::cout << __FILE__ << ":" << __LINE__
|
||||
<< " ERROR: collision._contactPoint = " << collision._contactPoint
|
||||
<< " but we expected " << expectedContactPoint
|
||||
<< std::endl;
|
||||
}
|
||||
|
||||
glm::vec3 expectedAddedVelocity = yAxis - xAxis + zAxis;
|
||||
error = glm::distance(collision._addedVelocity, expectedAddedVelocity);
|
||||
if (error > EPSILON) {
|
||||
std::cout << __FILE__ << ":" << __LINE__
|
||||
<< " ERROR: collision._addedVelocity = " << collision._contactPoint
|
||||
<< " but we expected " << expectedAddedVelocity
|
||||
<< std::endl;
|
||||
}
|
||||
}
|
||||
|
||||
void CollisionInfoTests::translateThenRotate() {
|
||||
CollisionInfo collision;
|
||||
collision._penetration = xAxis;
|
||||
collision._contactPoint = yAxis;
|
||||
collision._addedVelocity = xAxis + yAxis + zAxis;
|
||||
|
||||
glm::quat rotation = glm::angleAxis( -rightAngle, zAxis);
|
||||
float distance = 3.f;
|
||||
glm::vec3 translation = distance * yAxis;
|
||||
|
||||
collision.translateThenRotate(translation, rotation);
|
||||
|
||||
float error = glm::distance(collision._penetration, -yAxis);
|
||||
if (error > EPSILON) {
|
||||
std::cout << __FILE__ << ":" << __LINE__
|
||||
<< " ERROR: collision._penetration = " << collision._penetration
|
||||
<< " but we expected " << -yAxis
|
||||
<< std::endl;
|
||||
}
|
||||
|
||||
glm::vec3 expectedContactPoint = (1.f + distance) * xAxis;
|
||||
error = glm::distance(collision._contactPoint, expectedContactPoint);
|
||||
if (error > EPSILON) {
|
||||
std::cout << __FILE__ << ":" << __LINE__
|
||||
<< " ERROR: collision._contactPoint = " << collision._contactPoint
|
||||
<< " but we expected " << expectedContactPoint
|
||||
<< std::endl;
|
||||
}
|
||||
|
||||
glm::vec3 expectedAddedVelocity = - yAxis + xAxis + zAxis;
|
||||
error = glm::distance(collision._addedVelocity, expectedAddedVelocity);
|
||||
if (error > EPSILON) {
|
||||
std::cout << __FILE__ << ":" << __LINE__
|
||||
<< " ERROR: collision._addedVelocity = " << collision._contactPoint
|
||||
<< " but we expected " << expectedAddedVelocity
|
||||
<< std::endl;
|
||||
}
|
||||
}
|
||||
*/
|
||||
|
||||
void CollisionInfoTests::runAllTests() {
|
||||
// CollisionInfoTests::rotateThenTranslate();
|
||||
// CollisionInfoTests::translateThenRotate();
|
||||
}
|
21
tests/physics/src/CollisionInfoTests.h
Normal file
21
tests/physics/src/CollisionInfoTests.h
Normal file
|
@ -0,0 +1,21 @@
|
|||
//
|
||||
// CollisionInfoTests.h
|
||||
// physics-tests
|
||||
//
|
||||
// Created by Andrew Meadows on 2014.02.21
|
||||
// Copyright (c) 2014 High Fidelity, Inc. All rights reserved.
|
||||
//
|
||||
|
||||
#ifndef __tests__CollisionInfoTests__
|
||||
#define __tests__CollisionInfoTests__
|
||||
|
||||
namespace CollisionInfoTests {
|
||||
|
||||
// void rotateThenTranslate();
|
||||
// void translateThenRotate();
|
||||
|
||||
void runAllTests();
|
||||
}
|
||||
|
||||
#endif // __tests__CollisionInfoTests__
|
||||
|
37
tests/physics/src/PhysicsTestUtil.cpp
Normal file
37
tests/physics/src/PhysicsTestUtil.cpp
Normal file
|
@ -0,0 +1,37 @@
|
|||
//
|
||||
// PhysicsTestUtil.cpp
|
||||
// physics-tests
|
||||
//
|
||||
// Created by Andrew Meadows on 2014.02.21
|
||||
// Copyright (c) 2014 High Fidelity, Inc. All rights reserved.
|
||||
//
|
||||
|
||||
#include <glm/gtc/type_ptr.hpp>
|
||||
|
||||
#include "PhysicsTestUtil.h"
|
||||
|
||||
std::ostream& operator<<(std::ostream& s, const glm::vec3& v) {
|
||||
s << "<" << v.x << "," << v.y << "," << v.z << ">";
|
||||
return s;
|
||||
}
|
||||
|
||||
std::ostream& operator<<(std::ostream& s, const glm::quat& q) {
|
||||
s << "<" << q.x << "," << q.y << "," << q.z << "," << q.w << ">";
|
||||
return s;
|
||||
}
|
||||
|
||||
std::ostream& operator<<(std::ostream& s, const glm::mat4& m) {
|
||||
s << "[";
|
||||
for (int j = 0; j < 4; ++j) {
|
||||
s << " " << m[0][j] << " " << m[1][j] << " " << m[2][j] << " " << m[3][j] << ";";
|
||||
}
|
||||
s << " ]";
|
||||
return s;
|
||||
}
|
||||
|
||||
std::ostream& operator<<(std::ostream& s, const CollisionInfo& c) {
|
||||
s << "[penetration=" << c._penetration
|
||||
<< ", contactPoint=" << c._contactPoint
|
||||
<< ", addedVelocity=" << c._addedVelocity;
|
||||
return s;
|
||||
}
|
28
tests/physics/src/PhysicsTestUtil.h
Normal file
28
tests/physics/src/PhysicsTestUtil.h
Normal file
|
@ -0,0 +1,28 @@
|
|||
//
|
||||
// PhysicsTestUtil.h
|
||||
// physics-tests
|
||||
//
|
||||
// Created by Andrew Meadows on 2014.02.21
|
||||
// Copyright (c) 2014 High Fidelity, Inc. All rights reserved.
|
||||
//
|
||||
|
||||
#ifndef __tests__PhysicsTestUtil__
|
||||
#define __tests__PhysicsTestUtil__
|
||||
|
||||
#include <glm/glm.hpp>
|
||||
#include <glm/gtx/quaternion.hpp>
|
||||
|
||||
#include <CollisionInfo.h>
|
||||
|
||||
const glm::vec3 xAxis(1.f, 0.f, 0.f);
|
||||
const glm::vec3 yAxis(0.f, 1.f, 0.f);
|
||||
const glm::vec3 zAxis(0.f, 0.f, 1.f);
|
||||
|
||||
const float rightAngle = 90.f; // degrees
|
||||
|
||||
std::ostream& operator<<(std::ostream& s, const glm::vec3& v);
|
||||
std::ostream& operator<<(std::ostream& s, const glm::quat& q);
|
||||
std::ostream& operator<<(std::ostream& s, const glm::mat4& m);
|
||||
std::ostream& operator<<(std::ostream& s, const CollisionInfo& c);
|
||||
|
||||
#endif // __tests__PhysicsTestUtil__
|
706
tests/physics/src/ShapeColliderTests.cpp
Normal file
706
tests/physics/src/ShapeColliderTests.cpp
Normal file
|
@ -0,0 +1,706 @@
|
|||
//
|
||||
// ShapeColliderTests.cpp
|
||||
// physics-tests
|
||||
//
|
||||
// Created by Andrew Meadows on 2014.02.21
|
||||
// Copyright (c) 2014 High Fidelity, Inc. All rights reserved.
|
||||
//
|
||||
|
||||
//#include <stdio.h>
|
||||
#include <iostream>
|
||||
|
||||
#include <glm/glm.hpp>
|
||||
#include <glm/gtx/quaternion.hpp>
|
||||
|
||||
#include <CollisionInfo.h>
|
||||
#include <ShapeCollider.h>
|
||||
#include <SharedUtil.h>
|
||||
#include <SphereShape.h>
|
||||
|
||||
#include "PhysicsTestUtil.h"
|
||||
#include "ShapeColliderTests.h"
|
||||
|
||||
const glm::vec3 origin(0.f);
|
||||
|
||||
void ShapeColliderTests::sphereMissesSphere() {
|
||||
// non-overlapping spheres of unequal size
|
||||
float radiusA = 7.f;
|
||||
float radiusB = 3.f;
|
||||
float alpha = 1.2f;
|
||||
float beta = 1.3f;
|
||||
glm::vec3 offsetDirection = glm::normalize(glm::vec3(1.f, 2.f, 3.f));
|
||||
float offsetDistance = alpha * radiusA + beta * radiusB;
|
||||
|
||||
SphereShape sphereA(radiusA, origin);
|
||||
SphereShape sphereB(radiusB, offsetDistance * offsetDirection);
|
||||
CollisionList collisions(16);
|
||||
|
||||
// collide A to B...
|
||||
{
|
||||
bool touching = ShapeCollider::shapeShape(&sphereA, &sphereB, collisions);
|
||||
if (touching) {
|
||||
std::cout << __FILE__ << ":" << __LINE__
|
||||
<< " ERROR: sphereA and sphereB should NOT touch" << std::endl;
|
||||
}
|
||||
}
|
||||
|
||||
// collide B to A...
|
||||
{
|
||||
bool touching = ShapeCollider::shapeShape(&sphereB, &sphereA, collisions);
|
||||
if (touching) {
|
||||
std::cout << __FILE__ << ":" << __LINE__
|
||||
<< " ERROR: sphereA and sphereB should NOT touch" << std::endl;
|
||||
}
|
||||
}
|
||||
|
||||
// also test shapeShape
|
||||
{
|
||||
bool touching = ShapeCollider::shapeShape(&sphereB, &sphereA, collisions);
|
||||
if (touching) {
|
||||
std::cout << __FILE__ << ":" << __LINE__
|
||||
<< " ERROR: sphereA and sphereB should NOT touch" << std::endl;
|
||||
}
|
||||
}
|
||||
|
||||
if (collisions.size() > 0) {
|
||||
std::cout << __FILE__ << ":" << __LINE__
|
||||
<< " ERROR: expected empty collision list but size is " << collisions.size()
|
||||
<< std::endl;
|
||||
}
|
||||
}
|
||||
|
||||
void ShapeColliderTests::sphereTouchesSphere() {
|
||||
// overlapping spheres of unequal size
|
||||
float radiusA = 7.f;
|
||||
float radiusB = 3.f;
|
||||
float alpha = 0.2f;
|
||||
float beta = 0.3f;
|
||||
glm::vec3 offsetDirection = glm::normalize(glm::vec3(1.f, 2.f, 3.f));
|
||||
float offsetDistance = alpha * radiusA + beta * radiusB;
|
||||
float expectedPenetrationDistance = (1.f - alpha) * radiusA + (1.f - beta) * radiusB;
|
||||
glm::vec3 expectedPenetration = expectedPenetrationDistance * offsetDirection;
|
||||
|
||||
SphereShape sphereA(radiusA, origin);
|
||||
SphereShape sphereB(radiusB, offsetDistance * offsetDirection);
|
||||
CollisionList collisions(16);
|
||||
int numCollisions = 0;
|
||||
|
||||
// collide A to B...
|
||||
{
|
||||
bool touching = ShapeCollider::shapeShape(&sphereA, &sphereB, collisions);
|
||||
if (!touching) {
|
||||
std::cout << __FILE__ << ":" << __LINE__
|
||||
<< " ERROR: sphereA and sphereB should touch" << std::endl;
|
||||
} else {
|
||||
++numCollisions;
|
||||
}
|
||||
|
||||
// verify state of collisions
|
||||
if (numCollisions != collisions.size()) {
|
||||
std::cout << __FILE__ << ":" << __LINE__
|
||||
<< " ERROR: expected collisions size of " << numCollisions << " but actual size is " << collisions.size()
|
||||
<< std::endl;
|
||||
}
|
||||
CollisionInfo* collision = collisions.getCollision(numCollisions - 1);
|
||||
if (!collision) {
|
||||
std::cout << __FILE__ << ":" << __LINE__
|
||||
<< " ERROR: null collision" << std::endl;
|
||||
}
|
||||
|
||||
// penetration points from sphereA into sphereB
|
||||
float inaccuracy = glm::length(collision->_penetration - expectedPenetration);
|
||||
if (fabs(inaccuracy) > EPSILON) {
|
||||
std::cout << __FILE__ << ":" << __LINE__
|
||||
<< " ERROR: bad penetration: expected = " << expectedPenetration
|
||||
<< " actual = " << collision->_penetration
|
||||
<< std::endl;
|
||||
}
|
||||
|
||||
// contactPoint is on surface of sphereA
|
||||
glm::vec3 AtoB = sphereB.getPosition() - sphereA.getPosition();
|
||||
glm::vec3 expectedContactPoint = sphereA.getPosition() + radiusA * glm::normalize(AtoB);
|
||||
inaccuracy = glm::length(collision->_contactPoint - expectedContactPoint);
|
||||
if (fabs(inaccuracy) > EPSILON) {
|
||||
std::cout << __FILE__ << ":" << __LINE__
|
||||
<< " ERROR: bad contactPoint: expected = " << expectedContactPoint
|
||||
<< " actual = " << collision->_contactPoint
|
||||
<< std::endl;
|
||||
}
|
||||
}
|
||||
|
||||
// collide B to A...
|
||||
{
|
||||
bool touching = ShapeCollider::shapeShape(&sphereB, &sphereA, collisions);
|
||||
if (!touching) {
|
||||
std::cout << __FILE__ << ":" << __LINE__
|
||||
<< " ERROR: sphereA and sphereB should touch" << std::endl;
|
||||
} else {
|
||||
++numCollisions;
|
||||
}
|
||||
|
||||
// penetration points from sphereA into sphereB
|
||||
CollisionInfo* collision = collisions.getCollision(numCollisions - 1);
|
||||
float inaccuracy = glm::length(collision->_penetration + expectedPenetration);
|
||||
if (fabs(inaccuracy) > EPSILON) {
|
||||
std::cout << __FILE__ << ":" << __LINE__
|
||||
<< " ERROR: bad penetration: expected = " << expectedPenetration
|
||||
<< " actual = " << collision->_penetration
|
||||
<< std::endl;
|
||||
}
|
||||
|
||||
// contactPoint is on surface of sphereA
|
||||
glm::vec3 BtoA = sphereA.getPosition() - sphereB.getPosition();
|
||||
glm::vec3 expectedContactPoint = sphereB.getPosition() + radiusB * glm::normalize(BtoA);
|
||||
inaccuracy = glm::length(collision->_contactPoint - expectedContactPoint);
|
||||
if (fabs(inaccuracy) > EPSILON) {
|
||||
std::cout << __FILE__ << ":" << __LINE__
|
||||
<< " ERROR: bad contactPoint: expected = " << expectedContactPoint
|
||||
<< " actual = " << collision->_contactPoint
|
||||
<< std::endl;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void ShapeColliderTests::sphereMissesCapsule() {
|
||||
// non-overlapping sphere and capsule
|
||||
float radiusA = 1.5f;
|
||||
float radiusB = 2.3f;
|
||||
float totalRadius = radiusA + radiusB;
|
||||
float halfHeightB = 1.7f;
|
||||
float axialOffset = totalRadius + 1.1f * halfHeightB;
|
||||
float radialOffset = 1.2f * radiusA + 1.3f * radiusB;
|
||||
|
||||
SphereShape sphereA(radiusA);
|
||||
CapsuleShape capsuleB(radiusB, halfHeightB);
|
||||
|
||||
// give the capsule some arbirary transform
|
||||
float angle = 37.8;
|
||||
glm::vec3 axis = glm::normalize( glm::vec3(-7.f, 2.8f, 9.3f) );
|
||||
glm::quat rotation = glm::angleAxis(angle, axis);
|
||||
glm::vec3 translation(15.1f, -27.1f, -38.6f);
|
||||
capsuleB.setRotation(rotation);
|
||||
capsuleB.setPosition(translation);
|
||||
|
||||
CollisionList collisions(16);
|
||||
|
||||
// walk sphereA along the local yAxis next to, but not touching, capsuleB
|
||||
glm::vec3 localStartPosition(radialOffset, axialOffset, 0.f);
|
||||
int numberOfSteps = 10;
|
||||
float delta = 1.3f * (totalRadius + halfHeightB) / (numberOfSteps - 1);
|
||||
for (int i = 0; i < numberOfSteps; ++i) {
|
||||
// translate sphereA into world-frame
|
||||
glm::vec3 localPosition = localStartPosition + (float(i) * delta) * yAxis;
|
||||
sphereA.setPosition(rotation * localPosition + translation);
|
||||
|
||||
// sphereA agains capsuleB
|
||||
if (ShapeCollider::shapeShape(&sphereA, &capsuleB, collisions))
|
||||
{
|
||||
std::cout << __FILE__ << ":" << __LINE__
|
||||
<< " ERROR: sphere and capsule should NOT touch"
|
||||
<< std::endl;
|
||||
}
|
||||
|
||||
// capsuleB against sphereA
|
||||
if (ShapeCollider::shapeShape(&capsuleB, &sphereA, collisions))
|
||||
{
|
||||
std::cout << __FILE__ << ":" << __LINE__
|
||||
<< " ERROR: sphere and capsule should NOT touch"
|
||||
<< std::endl;
|
||||
}
|
||||
}
|
||||
|
||||
if (collisions.size() > 0) {
|
||||
std::cout << __FILE__ << ":" << __LINE__
|
||||
<< " ERROR: expected empty collision list but size is " << collisions.size()
|
||||
<< std::endl;
|
||||
}
|
||||
}
|
||||
|
||||
void ShapeColliderTests::sphereTouchesCapsule() {
|
||||
// overlapping sphere and capsule
|
||||
float radiusA = 2.f;
|
||||
float radiusB = 1.f;
|
||||
float totalRadius = radiusA + radiusB;
|
||||
float halfHeightB = 2.f;
|
||||
float alpha = 0.5f;
|
||||
float beta = 0.5f;
|
||||
float radialOffset = alpha * radiusA + beta * radiusB;
|
||||
|
||||
SphereShape sphereA(radiusA);
|
||||
CapsuleShape capsuleB(radiusB, halfHeightB);
|
||||
|
||||
CollisionList collisions(16);
|
||||
int numCollisions = 0;
|
||||
|
||||
{ // sphereA collides with capsuleB's cylindrical wall
|
||||
sphereA.setPosition(radialOffset * xAxis);
|
||||
|
||||
if (!ShapeCollider::shapeShape(&sphereA, &capsuleB, collisions))
|
||||
{
|
||||
std::cout << __FILE__ << ":" << __LINE__
|
||||
<< " ERROR: sphere and capsule should touch"
|
||||
<< std::endl;
|
||||
} else {
|
||||
++numCollisions;
|
||||
}
|
||||
|
||||
// penetration points from sphereA into capsuleB
|
||||
CollisionInfo* collision = collisions.getCollision(numCollisions - 1);
|
||||
glm::vec3 expectedPenetration = (radialOffset - totalRadius) * xAxis;
|
||||
float inaccuracy = glm::length(collision->_penetration - expectedPenetration);
|
||||
if (fabs(inaccuracy) > EPSILON) {
|
||||
std::cout << __FILE__ << ":" << __LINE__
|
||||
<< " ERROR: bad penetration: expected = " << expectedPenetration
|
||||
<< " actual = " << collision->_penetration
|
||||
<< std::endl;
|
||||
}
|
||||
|
||||
// contactPoint is on surface of sphereA
|
||||
glm::vec3 expectedContactPoint = sphereA.getPosition() - radiusA * xAxis;
|
||||
inaccuracy = glm::length(collision->_contactPoint - expectedContactPoint);
|
||||
if (fabs(inaccuracy) > EPSILON) {
|
||||
std::cout << __FILE__ << ":" << __LINE__
|
||||
<< " ERROR: bad contactPoint: expected = " << expectedContactPoint
|
||||
<< " actual = " << collision->_contactPoint
|
||||
<< std::endl;
|
||||
}
|
||||
|
||||
// capsuleB collides with sphereA
|
||||
if (!ShapeCollider::shapeShape(&capsuleB, &sphereA, collisions))
|
||||
{
|
||||
std::cout << __FILE__ << ":" << __LINE__
|
||||
<< " ERROR: capsule and sphere should touch"
|
||||
<< std::endl;
|
||||
} else {
|
||||
++numCollisions;
|
||||
}
|
||||
|
||||
// penetration points from sphereA into capsuleB
|
||||
collision = collisions.getCollision(numCollisions - 1);
|
||||
expectedPenetration = - (radialOffset - totalRadius) * xAxis;
|
||||
inaccuracy = glm::length(collision->_penetration - expectedPenetration);
|
||||
if (fabs(inaccuracy) > EPSILON) {
|
||||
std::cout << __FILE__ << ":" << __LINE__
|
||||
<< " ERROR: bad penetration: expected = " << expectedPenetration
|
||||
<< " actual = " << collision->_penetration
|
||||
<< std::endl;
|
||||
}
|
||||
|
||||
// contactPoint is on surface of capsuleB
|
||||
glm::vec3 BtoA = sphereA.getPosition() - capsuleB.getPosition();
|
||||
glm::vec3 closestApproach = capsuleB.getPosition() + glm::dot(BtoA, yAxis) * yAxis;
|
||||
expectedContactPoint = closestApproach + radiusB * glm::normalize(BtoA - closestApproach);
|
||||
inaccuracy = glm::length(collision->_contactPoint - expectedContactPoint);
|
||||
if (fabs(inaccuracy) > EPSILON) {
|
||||
std::cout << __FILE__ << ":" << __LINE__
|
||||
<< " ERROR: bad contactPoint: expected = " << expectedContactPoint
|
||||
<< " actual = " << collision->_contactPoint
|
||||
<< std::endl;
|
||||
}
|
||||
}
|
||||
{ // sphereA hits end cap at axis
|
||||
glm::vec3 axialOffset = (halfHeightB + alpha * radiusA + beta * radiusB) * yAxis;
|
||||
sphereA.setPosition(axialOffset * yAxis);
|
||||
|
||||
if (!ShapeCollider::shapeShape(&sphereA, &capsuleB, collisions))
|
||||
{
|
||||
std::cout << __FILE__ << ":" << __LINE__
|
||||
<< " ERROR: sphere and capsule should touch"
|
||||
<< std::endl;
|
||||
} else {
|
||||
++numCollisions;
|
||||
}
|
||||
|
||||
// penetration points from sphereA into capsuleB
|
||||
CollisionInfo* collision = collisions.getCollision(numCollisions - 1);
|
||||
glm::vec3 expectedPenetration = - ((1.f - alpha) * radiusA + (1.f - beta) * radiusB) * yAxis;
|
||||
float inaccuracy = glm::length(collision->_penetration - expectedPenetration);
|
||||
if (fabs(inaccuracy) > EPSILON) {
|
||||
std::cout << __FILE__ << ":" << __LINE__
|
||||
<< " ERROR: bad penetration: expected = " << expectedPenetration
|
||||
<< " actual = " << collision->_penetration
|
||||
<< std::endl;
|
||||
}
|
||||
|
||||
// contactPoint is on surface of sphereA
|
||||
glm::vec3 expectedContactPoint = sphereA.getPosition() - radiusA * yAxis;
|
||||
inaccuracy = glm::length(collision->_contactPoint - expectedContactPoint);
|
||||
if (fabs(inaccuracy) > EPSILON) {
|
||||
std::cout << __FILE__ << ":" << __LINE__
|
||||
<< " ERROR: bad contactPoint: expected = " << expectedContactPoint
|
||||
<< " actual = " << collision->_contactPoint
|
||||
<< std::endl;
|
||||
}
|
||||
|
||||
// capsuleB collides with sphereA
|
||||
if (!ShapeCollider::shapeShape(&capsuleB, &sphereA, collisions))
|
||||
{
|
||||
std::cout << __FILE__ << ":" << __LINE__
|
||||
<< " ERROR: capsule and sphere should touch"
|
||||
<< std::endl;
|
||||
} else {
|
||||
++numCollisions;
|
||||
}
|
||||
|
||||
// penetration points from sphereA into capsuleB
|
||||
collision = collisions.getCollision(numCollisions - 1);
|
||||
expectedPenetration = ((1.f - alpha) * radiusA + (1.f - beta) * radiusB) * yAxis;
|
||||
inaccuracy = glm::length(collision->_penetration - expectedPenetration);
|
||||
if (fabs(inaccuracy) > EPSILON) {
|
||||
std::cout << __FILE__ << ":" << __LINE__
|
||||
<< " ERROR: bad penetration: expected = " << expectedPenetration
|
||||
<< " actual = " << collision->_penetration
|
||||
<< std::endl;
|
||||
}
|
||||
|
||||
// contactPoint is on surface of capsuleB
|
||||
glm::vec3 endPoint;
|
||||
capsuleB.getEndPoint(endPoint);
|
||||
expectedContactPoint = endPoint + radiusB * yAxis;
|
||||
inaccuracy = glm::length(collision->_contactPoint - expectedContactPoint);
|
||||
if (fabs(inaccuracy) > EPSILON) {
|
||||
std::cout << __FILE__ << ":" << __LINE__
|
||||
<< " ERROR: bad contactPoint: expected = " << expectedContactPoint
|
||||
<< " actual = " << collision->_contactPoint
|
||||
<< std::endl;
|
||||
}
|
||||
}
|
||||
{ // sphereA hits start cap at axis
|
||||
glm::vec3 axialOffset = - (halfHeightB + alpha * radiusA + beta * radiusB) * yAxis;
|
||||
sphereA.setPosition(axialOffset * yAxis);
|
||||
|
||||
if (!ShapeCollider::shapeShape(&sphereA, &capsuleB, collisions))
|
||||
{
|
||||
std::cout << __FILE__ << ":" << __LINE__
|
||||
<< " ERROR: sphere and capsule should touch"
|
||||
<< std::endl;
|
||||
} else {
|
||||
++numCollisions;
|
||||
}
|
||||
|
||||
// penetration points from sphereA into capsuleB
|
||||
CollisionInfo* collision = collisions.getCollision(numCollisions - 1);
|
||||
glm::vec3 expectedPenetration = ((1.f - alpha) * radiusA + (1.f - beta) * radiusB) * yAxis;
|
||||
float inaccuracy = glm::length(collision->_penetration - expectedPenetration);
|
||||
if (fabs(inaccuracy) > EPSILON) {
|
||||
std::cout << __FILE__ << ":" << __LINE__
|
||||
<< " ERROR: bad penetration: expected = " << expectedPenetration
|
||||
<< " actual = " << collision->_penetration
|
||||
<< std::endl;
|
||||
}
|
||||
|
||||
// contactPoint is on surface of sphereA
|
||||
glm::vec3 expectedContactPoint = sphereA.getPosition() + radiusA * yAxis;
|
||||
inaccuracy = glm::length(collision->_contactPoint - expectedContactPoint);
|
||||
if (fabs(inaccuracy) > EPSILON) {
|
||||
std::cout << __FILE__ << ":" << __LINE__
|
||||
<< " ERROR: bad contactPoint: expected = " << expectedContactPoint
|
||||
<< " actual = " << collision->_contactPoint
|
||||
<< std::endl;
|
||||
}
|
||||
|
||||
// capsuleB collides with sphereA
|
||||
if (!ShapeCollider::shapeShape(&capsuleB, &sphereA, collisions))
|
||||
{
|
||||
std::cout << __FILE__ << ":" << __LINE__
|
||||
<< " ERROR: capsule and sphere should touch"
|
||||
<< std::endl;
|
||||
} else {
|
||||
++numCollisions;
|
||||
}
|
||||
|
||||
// penetration points from sphereA into capsuleB
|
||||
collision = collisions.getCollision(numCollisions - 1);
|
||||
expectedPenetration = - ((1.f - alpha) * radiusA + (1.f - beta) * radiusB) * yAxis;
|
||||
inaccuracy = glm::length(collision->_penetration - expectedPenetration);
|
||||
if (fabs(inaccuracy) > EPSILON) {
|
||||
std::cout << __FILE__ << ":" << __LINE__
|
||||
<< " ERROR: bad penetration: expected = " << expectedPenetration
|
||||
<< " actual = " << collision->_penetration
|
||||
<< std::endl;
|
||||
}
|
||||
|
||||
// contactPoint is on surface of capsuleB
|
||||
glm::vec3 startPoint;
|
||||
capsuleB.getStartPoint(startPoint);
|
||||
expectedContactPoint = startPoint - radiusB * yAxis;
|
||||
inaccuracy = glm::length(collision->_contactPoint - expectedContactPoint);
|
||||
if (fabs(inaccuracy) > EPSILON) {
|
||||
std::cout << __FILE__ << ":" << __LINE__
|
||||
<< " ERROR: bad contactPoint: expected = " << expectedContactPoint
|
||||
<< " actual = " << collision->_contactPoint
|
||||
<< std::endl;
|
||||
}
|
||||
}
|
||||
if (collisions.size() != numCollisions) {
|
||||
std::cout << __FILE__ << ":" << __LINE__
|
||||
<< " ERROR: expected " << numCollisions << " collisions but actual number is " << collisions.size()
|
||||
<< std::endl;
|
||||
}
|
||||
}
|
||||
|
||||
void ShapeColliderTests::capsuleMissesCapsule() {
|
||||
// non-overlapping capsules
|
||||
float radiusA = 2.f;
|
||||
float halfHeightA = 3.f;
|
||||
float radiusB = 3.f;
|
||||
float halfHeightB = 4.f;
|
||||
|
||||
float totalRadius = radiusA + radiusB;
|
||||
float totalHalfLength = totalRadius + halfHeightA + halfHeightB;
|
||||
|
||||
CapsuleShape capsuleA(radiusA, halfHeightA);
|
||||
CapsuleShape capsuleB(radiusA, halfHeightA);
|
||||
|
||||
CollisionList collisions(16);
|
||||
|
||||
// side by side
|
||||
capsuleB.setPosition((1.01f * totalRadius) * xAxis);
|
||||
if (ShapeCollider::shapeShape(&capsuleA, &capsuleB, collisions))
|
||||
{
|
||||
std::cout << __FILE__ << ":" << __LINE__
|
||||
<< " ERROR: capsule and capsule should NOT touch"
|
||||
<< std::endl;
|
||||
}
|
||||
if (ShapeCollider::shapeShape(&capsuleB, &capsuleA, collisions))
|
||||
{
|
||||
std::cout << __FILE__ << ":" << __LINE__
|
||||
<< " ERROR: capsule and capsule should NOT touch"
|
||||
<< std::endl;
|
||||
}
|
||||
|
||||
// end to end
|
||||
capsuleB.setPosition((1.01f * totalHalfLength) * xAxis);
|
||||
if (ShapeCollider::shapeShape(&capsuleA, &capsuleB, collisions))
|
||||
{
|
||||
std::cout << __FILE__ << ":" << __LINE__
|
||||
<< " ERROR: capsule and capsule should NOT touch"
|
||||
<< std::endl;
|
||||
}
|
||||
if (ShapeCollider::shapeShape(&capsuleB, &capsuleA, collisions))
|
||||
{
|
||||
std::cout << __FILE__ << ":" << __LINE__
|
||||
<< " ERROR: capsule and capsule should NOT touch"
|
||||
<< std::endl;
|
||||
}
|
||||
|
||||
// rotate B and move it to the side
|
||||
glm::quat rotation = glm::angleAxis(rightAngle, zAxis);
|
||||
capsuleB.setRotation(rotation);
|
||||
capsuleB.setPosition((1.01f * (totalRadius + capsuleB.getHalfHeight())) * xAxis);
|
||||
if (ShapeCollider::shapeShape(&capsuleA, &capsuleB, collisions))
|
||||
{
|
||||
std::cout << __FILE__ << ":" << __LINE__
|
||||
<< " ERROR: capsule and capsule should NOT touch"
|
||||
<< std::endl;
|
||||
}
|
||||
if (ShapeCollider::shapeShape(&capsuleB, &capsuleA, collisions))
|
||||
{
|
||||
std::cout << __FILE__ << ":" << __LINE__
|
||||
<< " ERROR: capsule and capsule should NOT touch"
|
||||
<< std::endl;
|
||||
}
|
||||
|
||||
if (collisions.size() > 0) {
|
||||
std::cout << __FILE__ << ":" << __LINE__
|
||||
<< " ERROR: expected empty collision list but size is " << collisions.size()
|
||||
<< std::endl;
|
||||
}
|
||||
}
|
||||
|
||||
void ShapeColliderTests::capsuleTouchesCapsule() {
|
||||
// overlapping capsules
|
||||
float radiusA = 2.f;
|
||||
float halfHeightA = 3.f;
|
||||
float radiusB = 3.f;
|
||||
float halfHeightB = 4.f;
|
||||
|
||||
float totalRadius = radiusA + radiusB;
|
||||
float totalHalfLength = totalRadius + halfHeightA + halfHeightB;
|
||||
|
||||
CapsuleShape capsuleA(radiusA, halfHeightA);
|
||||
CapsuleShape capsuleB(radiusB, halfHeightB);
|
||||
|
||||
CollisionList collisions(16);
|
||||
int numCollisions = 0;
|
||||
|
||||
{ // side by side
|
||||
capsuleB.setPosition((0.99f * totalRadius) * xAxis);
|
||||
if (!ShapeCollider::shapeShape(&capsuleA, &capsuleB, collisions))
|
||||
{
|
||||
std::cout << __FILE__ << ":" << __LINE__
|
||||
<< " ERROR: capsule and capsule should touch"
|
||||
<< std::endl;
|
||||
} else {
|
||||
++numCollisions;
|
||||
}
|
||||
if (!ShapeCollider::shapeShape(&capsuleB, &capsuleA, collisions))
|
||||
{
|
||||
std::cout << __FILE__ << ":" << __LINE__
|
||||
<< " ERROR: capsule and capsule should touch"
|
||||
<< std::endl;
|
||||
} else {
|
||||
++numCollisions;
|
||||
}
|
||||
}
|
||||
|
||||
{ // end to end
|
||||
capsuleB.setPosition((0.99f * totalHalfLength) * yAxis);
|
||||
|
||||
if (!ShapeCollider::shapeShape(&capsuleA, &capsuleB, collisions))
|
||||
{
|
||||
std::cout << __FILE__ << ":" << __LINE__
|
||||
<< " ERROR: capsule and capsule should touch"
|
||||
<< std::endl;
|
||||
} else {
|
||||
++numCollisions;
|
||||
}
|
||||
if (!ShapeCollider::shapeShape(&capsuleB, &capsuleA, collisions))
|
||||
{
|
||||
std::cout << __FILE__ << ":" << __LINE__
|
||||
<< " ERROR: capsule and capsule should touch"
|
||||
<< std::endl;
|
||||
} else {
|
||||
++numCollisions;
|
||||
}
|
||||
}
|
||||
|
||||
{ // rotate B and move it to the side
|
||||
glm::quat rotation = glm::angleAxis(rightAngle, zAxis);
|
||||
capsuleB.setRotation(rotation);
|
||||
capsuleB.setPosition((0.99f * (totalRadius + capsuleB.getHalfHeight())) * xAxis);
|
||||
|
||||
if (!ShapeCollider::shapeShape(&capsuleA, &capsuleB, collisions))
|
||||
{
|
||||
std::cout << __FILE__ << ":" << __LINE__
|
||||
<< " ERROR: capsule and capsule should touch"
|
||||
<< std::endl;
|
||||
} else {
|
||||
++numCollisions;
|
||||
}
|
||||
if (!ShapeCollider::shapeShape(&capsuleB, &capsuleA, collisions))
|
||||
{
|
||||
std::cout << __FILE__ << ":" << __LINE__
|
||||
<< " ERROR: capsule and capsule should touch"
|
||||
<< std::endl;
|
||||
} else {
|
||||
++numCollisions;
|
||||
}
|
||||
}
|
||||
|
||||
{ // again, but this time check collision details
|
||||
float overlap = 0.1f;
|
||||
glm::quat rotation = glm::angleAxis(rightAngle, zAxis);
|
||||
capsuleB.setRotation(rotation);
|
||||
glm::vec3 positionB = ((totalRadius + capsuleB.getHalfHeight()) - overlap) * xAxis;
|
||||
capsuleB.setPosition(positionB);
|
||||
|
||||
// capsuleA vs capsuleB
|
||||
if (!ShapeCollider::shapeShape(&capsuleA, &capsuleB, collisions))
|
||||
{
|
||||
std::cout << __FILE__ << ":" << __LINE__
|
||||
<< " ERROR: capsule and capsule should touch"
|
||||
<< std::endl;
|
||||
} else {
|
||||
++numCollisions;
|
||||
}
|
||||
|
||||
CollisionInfo* collision = collisions.getCollision(numCollisions - 1);
|
||||
glm::vec3 expectedPenetration = overlap * xAxis;
|
||||
float inaccuracy = glm::length(collision->_penetration - expectedPenetration);
|
||||
if (fabs(inaccuracy) > EPSILON) {
|
||||
std::cout << __FILE__ << ":" << __LINE__
|
||||
<< " ERROR: bad penetration: expected = " << expectedPenetration
|
||||
<< " actual = " << collision->_penetration
|
||||
<< std::endl;
|
||||
}
|
||||
|
||||
glm::vec3 expectedContactPoint = capsuleA.getPosition() + radiusA * xAxis;
|
||||
inaccuracy = glm::length(collision->_contactPoint - expectedContactPoint);
|
||||
if (fabs(inaccuracy) > EPSILON) {
|
||||
std::cout << __FILE__ << ":" << __LINE__
|
||||
<< " ERROR: bad contactPoint: expected = " << expectedContactPoint
|
||||
<< " actual = " << collision->_contactPoint
|
||||
<< std::endl;
|
||||
}
|
||||
|
||||
// capsuleB vs capsuleA
|
||||
if (!ShapeCollider::shapeShape(&capsuleB, &capsuleA, collisions))
|
||||
{
|
||||
std::cout << __FILE__ << ":" << __LINE__
|
||||
<< " ERROR: capsule and capsule should touch"
|
||||
<< std::endl;
|
||||
} else {
|
||||
++numCollisions;
|
||||
}
|
||||
|
||||
collision = collisions.getCollision(numCollisions - 1);
|
||||
expectedPenetration = - overlap * xAxis;
|
||||
inaccuracy = glm::length(collision->_penetration - expectedPenetration);
|
||||
if (fabs(inaccuracy) > EPSILON) {
|
||||
std::cout << __FILE__ << ":" << __LINE__
|
||||
<< " ERROR: bad penetration: expected = " << expectedPenetration
|
||||
<< " actual = " << collision->_penetration
|
||||
<< std::endl;
|
||||
}
|
||||
|
||||
expectedContactPoint = capsuleB.getPosition() - (radiusB + halfHeightB) * xAxis;
|
||||
inaccuracy = glm::length(collision->_contactPoint - expectedContactPoint);
|
||||
if (fabs(inaccuracy) > EPSILON) {
|
||||
std::cout << __FILE__ << ":" << __LINE__
|
||||
<< " ERROR: bad contactPoint: expected = " << expectedContactPoint
|
||||
<< " actual = " << collision->_contactPoint
|
||||
<< std::endl;
|
||||
}
|
||||
}
|
||||
|
||||
{ // collide cylinder wall against cylinder wall
|
||||
float overlap = 0.137f;
|
||||
float shift = 0.317f * halfHeightA;
|
||||
glm::quat rotation = glm::angleAxis(rightAngle, zAxis);
|
||||
capsuleB.setRotation(rotation);
|
||||
glm::vec3 positionB = (totalRadius - overlap) * zAxis + shift * yAxis;
|
||||
capsuleB.setPosition(positionB);
|
||||
|
||||
// capsuleA vs capsuleB
|
||||
if (!ShapeCollider::shapeShape(&capsuleA, &capsuleB, collisions))
|
||||
{
|
||||
std::cout << __FILE__ << ":" << __LINE__
|
||||
<< " ERROR: capsule and capsule should touch"
|
||||
<< std::endl;
|
||||
} else {
|
||||
++numCollisions;
|
||||
}
|
||||
|
||||
CollisionInfo* collision = collisions.getCollision(numCollisions - 1);
|
||||
glm::vec3 expectedPenetration = overlap * zAxis;
|
||||
float inaccuracy = glm::length(collision->_penetration - expectedPenetration);
|
||||
if (fabs(inaccuracy) > EPSILON) {
|
||||
std::cout << __FILE__ << ":" << __LINE__
|
||||
<< " ERROR: bad penetration: expected = " << expectedPenetration
|
||||
<< " actual = " << collision->_penetration
|
||||
<< std::endl;
|
||||
}
|
||||
|
||||
glm::vec3 expectedContactPoint = capsuleA.getPosition() + radiusA * zAxis + shift * yAxis;
|
||||
inaccuracy = glm::length(collision->_contactPoint - expectedContactPoint);
|
||||
if (fabs(inaccuracy) > EPSILON) {
|
||||
std::cout << __FILE__ << ":" << __LINE__
|
||||
<< " ERROR: bad contactPoint: expected = " << expectedContactPoint
|
||||
<< " actual = " << collision->_contactPoint
|
||||
<< std::endl;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void ShapeColliderTests::runAllTests() {
|
||||
sphereMissesSphere();
|
||||
sphereTouchesSphere();
|
||||
|
||||
sphereMissesCapsule();
|
||||
sphereTouchesCapsule();
|
||||
|
||||
capsuleMissesCapsule();
|
||||
capsuleTouchesCapsule();
|
||||
}
|
26
tests/physics/src/ShapeColliderTests.h
Normal file
26
tests/physics/src/ShapeColliderTests.h
Normal file
|
@ -0,0 +1,26 @@
|
|||
//
|
||||
// ShapeColliderTests.h
|
||||
// physics-tests
|
||||
//
|
||||
// Created by Andrew Meadows on 2014.02.21
|
||||
// Copyright (c) 2014 High Fidelity, Inc. All rights reserved.
|
||||
//
|
||||
|
||||
#ifndef __tests__ShapeColliderTests__
|
||||
#define __tests__ShapeColliderTests__
|
||||
|
||||
namespace ShapeColliderTests {
|
||||
|
||||
void sphereMissesSphere();
|
||||
void sphereTouchesSphere();
|
||||
|
||||
void sphereMissesCapsule();
|
||||
void sphereTouchesCapsule();
|
||||
|
||||
void capsuleMissesCapsule();
|
||||
void capsuleTouchesCapsule();
|
||||
|
||||
void runAllTests();
|
||||
}
|
||||
|
||||
#endif // __tests__ShapeColliderTests__
|
11
tests/physics/src/main.cpp
Normal file
11
tests/physics/src/main.cpp
Normal file
|
@ -0,0 +1,11 @@
|
|||
//
|
||||
// main.cpp
|
||||
// physics-tests
|
||||
//
|
||||
|
||||
#include "ShapeColliderTests.h"
|
||||
|
||||
int main(int argc, char** argv) {
|
||||
ShapeColliderTests::runAllTests();
|
||||
return 0;
|
||||
}
|
Loading…
Reference in a new issue