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

This commit is contained in:
Atlante45 2014-05-09 11:43:33 -07:00
commit 05283cd904
59 changed files with 1200 additions and 516 deletions

View file

@ -54,7 +54,7 @@
const short JITTER_BUFFER_MSECS = 12;
const short JITTER_BUFFER_SAMPLES = JITTER_BUFFER_MSECS * (SAMPLE_RATE / 1000.0);
const float LOUDNESS_TO_DISTANCE_RATIO = 0.00305f;
const float LOUDNESS_TO_DISTANCE_RATIO = 0.00001f;
const QString AUDIO_MIXER_LOGGING_TARGET_NAME = "audio-mixer";
@ -348,6 +348,16 @@ void AudioMixer::readPendingDatagrams() {
|| mixerPacketType == PacketTypeSilentAudioFrame) {
nodeList->findNodeAndUpdateWithDataFromPacket(receivedPacket);
} else if (mixerPacketType == PacketTypeMuteEnvironment) {
QByteArray packet = receivedPacket;
populatePacketHeader(packet, PacketTypeMuteEnvironment);
foreach (const SharedNodePointer& node, nodeList->getNodeHash()) {
if (node->getType() == NodeType::Agent && node->getActiveSocket() && node->getLinkedData() && node != nodeList->sendingNodeForPacket(receivedPacket)) {
nodeList->writeDatagram(packet, packet.size(), node);
}
}
} else {
// let processNodeData handle it.
nodeList->processNodeData(senderSockAddr, receivedPacket);

View file

@ -25,13 +25,24 @@ function vMinus(a, b) {
return rval;
}
// First, load two percussion sounds to be used on the sticks
// The model file to be used for the guitar
var guitarModel = "https://s3-us-west-1.amazonaws.com/highfidelity-public/models/attachments/guitar.fst";
var chord1 = new Sound("https://s3-us-west-1.amazonaws.com/highfidelity-public/sounds/Guitars/Guitar+-+Nylon+A.raw");
var chord2 = new Sound("https://s3-us-west-1.amazonaws.com/highfidelity-public/sounds/Guitars/Guitar+-+Nylon+B.raw");
var chord3 = new Sound("https://s3-us-west-1.amazonaws.com/highfidelity-public/sounds/Guitars/Guitar+-+Nylon+E.raw");
// Load sounds that will be played
var whichChord = chord1;
var chords = new Array();
// Nylon string guitar
chords[1] = new Sound("https://s3-us-west-1.amazonaws.com/highfidelity-public/sounds/Guitars/Guitar+-+Nylon+A.raw");
chords[2] = new Sound("https://s3-us-west-1.amazonaws.com/highfidelity-public/sounds/Guitars/Guitar+-+Nylon+B.raw");
chords[3] = new Sound("https://s3-us-west-1.amazonaws.com/highfidelity-public/sounds/Guitars/Guitar+-+Nylon+E.raw");
// Electric guitar
chords[4] = new Sound("https://s3-us-west-1.amazonaws.com/highfidelity-public/sounds/Guitars/Guitar+-+Metal+A+short.raw");
chords[5] = new Sound("https://s3-us-west-1.amazonaws.com/highfidelity-public/sounds/Guitars/Guitar+-+Metal+B+short.raw");
chords[6] = new Sound("https://s3-us-west-1.amazonaws.com/highfidelity-public/sounds/Guitars/Guitar+-+Metal+E+short.raw");
var guitarSelector = 3;
var whichChord = chords[guitarSelector + 1];
var leftHanded = false;
if (leftHanded) {
@ -46,42 +57,74 @@ var lastPosition = { x: 0.0,
y: 0.0,
z: 0.0 };
var soundPlaying = false;
var selectorPressed = false;
MyAvatar.attach(guitarModel, "Hips", {x: -0.0, y: -0.0, z: 0.0}, Quat.fromPitchYawRollDegrees(0, 0, 0), 1.0);
function checkHands(deltaTime) {
for (var palm = 0; palm < 2; palm++) {
var palmVelocity = Controller.getSpatialControlVelocity(palm * 2 + 1);
var speed = length(palmVelocity);
var speed = length(palmVelocity) / 4.0;
var position = Controller.getSpatialControlPosition(palm * 2 + 1);
var myPelvis = MyAvatar.position;
var trigger = Controller.getTriggerValue(strumHand);
var chord = Controller.getTriggerValue(chordHand);
if ((chord > 0.1) && Audio.isInjectorPlaying(soundPlaying)) {
// If chord finger trigger pulled, stop current chord
Audio.stopInjector(soundPlaying);
}
var BUTTON_COUNT = 6;
// Change guitars if button FWD (5) pressed
if (Controller.isButtonPressed(chordHand * BUTTON_COUNT + 5)) {
if (!selectorPressed) {
if (guitarSelector == 0) {
guitarSelector = 3;
} else {
guitarSelector = 0;
}
selectorPressed = true;
}
} else {
selectorPressed = false;
}
if (Controller.isButtonPressed(chordHand * BUTTON_COUNT + 1)) {
whichChord = chords[guitarSelector + 1];
} else if (Controller.isButtonPressed(chordHand * BUTTON_COUNT + 2)) {
whichChord = chords[guitarSelector + 2];
} else if (Controller.isButtonPressed(chordHand * BUTTON_COUNT + 3)) {
whichChord = chords[guitarSelector + 3];
}
if (palm == strumHand) {
var STRUM_HEIGHT_ABOVE_PELVIS = 0.15;
var STRUM_HEIGHT_ABOVE_PELVIS = 0.00;
var strumTriggerHeight = myPelvis.y + STRUM_HEIGHT_ABOVE_PELVIS;
//printVector(position);
if ((position.y < strumTriggerHeight) && (lastPosition.y >= strumTriggerHeight)) {
// If hand passes downward through guitar strings, play a chord!
if ( ( ((position.y < strumTriggerHeight) && (lastPosition.y >= strumTriggerHeight)) ||
((position.y > strumTriggerHeight) && (lastPosition.y <= strumTriggerHeight)) ) && (trigger > 0.1) ){
// If hand passes downward or upward through 'strings', and finger trigger pulled, play
var options = new AudioInjectionOptions();
options.position = position;
if (speed > 1.0) { speed = 1.0; }
options.volume = speed;
Audio.playSound(whichChord, options);
if (Audio.isInjectorPlaying(soundPlaying)) {
Audio.stopInjector(soundPlaying);
}
soundPlaying = Audio.playSound(whichChord, options);
}
lastPosition = Controller.getSpatialControlPosition(palm * 2 + 1);
} else {
// This is the chord controller
var distanceFromPelvis = Vec3.length(Vec3.subtract(position, myPelvis));
//print(distanceFromPelvis);
if (distanceFromPelvis > 0.50) {
whichChord = chord3;
} else if (distanceFromPelvis > 0.35) {
whichChord = chord2;
} else {
whichChord = chord1;
}
}
}
}
}
function scriptEnding() {
MyAvatar.detachOne(guitarModel);
}
// Connect a call back that happens every frame
Script.update.connect(checkHands);
Script.update.connect(checkHands);
Script.scriptEnding.connect(scriptEnding);

View file

@ -402,7 +402,7 @@ function moveOverlays() {
toolsX = windowDimensions.x - 8 - toolWidth;
toolsY = (windowDimensions.y - toolsHeight) / 2;
Overlays.addOverlay(firstModel, {
Overlays.editOverlay(firstModel, {
x: toolsX, y: toolsY + ((toolHeight + toolVerticalSpacing) * 0), width: toolWidth, height: toolHeight,
});
}

View file

@ -28,7 +28,7 @@ var BULLET_VELOCITY = 5.0;
var MIN_THROWER_DELAY = 1000;
var MAX_THROWER_DELAY = 1000;
var LEFT_BUTTON_3 = 3;
var RELOAD_INTERVAL = 9;
var RELOAD_INTERVAL = 5;
var showScore = false;
@ -39,6 +39,8 @@ var impactSound = new Sound("https://s3-us-west-1.amazonaws.com/highfidelity-pub
var targetHitSound = new Sound("http://highfidelity-public.s3-us-west-1.amazonaws.com/sounds/Space%20Invaders/hit.raw");
var targetLaunchSound = new Sound("http://highfidelity-public.s3-us-west-1.amazonaws.com/sounds/Space%20Invaders/shoot.raw");
var gunModel = "http://highfidelity-public.s3-us-west-1.amazonaws.com/models/attachments/Raygun2.fst";
var audioOptions = new AudioInjectionOptions();
audioOptions.volume = 0.9;
@ -90,12 +92,12 @@ function printVector(string, vector) {
}
function shootBullet(position, velocity) {
var BULLET_SIZE = 0.02;
var BULLET_SIZE = 0.01;
var BULLET_GRAVITY = -0.02;
Particles.addParticle(
{ position: position,
radius: BULLET_SIZE,
color: { red: 200, green: 0, blue: 0 },
color: { red: 10, green: 10, blue: 10 },
velocity: velocity,
gravity: { x: 0, y: BULLET_GRAVITY, z: 0 },
damping: 0 });
@ -185,11 +187,24 @@ function keyPressEvent(event) {
if (event.text == "t") {
var time = MIN_THROWER_DELAY + Math.random() * MAX_THROWER_DELAY;
Script.setTimeout(shootTarget, time);
} if (event.text == ".") {
} else if (event.text == ".") {
shootFromMouse();
} else if (event.text == "r") {
playLoadSound();
}
}
function playLoadSound() {
audioOptions.position = Vec3.sum(Camera.getPosition(), Quat.getFront(Camera.getOrientation()));
Audio.playSound(loadSound, audioOptions);
}
MyAvatar.attach(gunModel, "RightHand", {x: -0.02, y: -.14, z: 0.07}, Quat.fromPitchYawRollDegrees(-70, -151, 72), 0.20);
MyAvatar.attach(gunModel, "LeftHand", {x: -0.02, y: -.14, z: 0.07}, Quat.fromPitchYawRollDegrees(-70, -151, 72), 0.20);
// Give a bit of time to load before playing sound
Script.setTimeout(playLoadSound, 2000);
function update(deltaTime) {
// Check for mouseLook movement, update rotation
// rotate body yaw for yaw received from mouse
@ -303,7 +318,9 @@ function mouseMoveEvent(event) {
function scriptEnding() {
Overlays.deleteOverlay(reticle);
Overlays.deleteOverlay(text);
Overlays.deleteOverlay(text);
MyAvatar.detachOne(gunModel);
MyAvatar.detachOne(gunModel);
}
Particles.particleCollisionWithVoxel.connect(particleCollisionWithVoxel);

View file

@ -572,8 +572,8 @@ void Application::paintGL() {
glm::vec3 eyePosition = _myAvatar->getHead()->calculateAverageEyePosition();
float headHeight = eyePosition.y - _myAvatar->getPosition().y;
_myCamera.setDistance(MIRROR_FULLSCREEN_DISTANCE * _myAvatar->getScale() * _scaleMirror);
_myCamera.setTargetPosition(_myAvatar->getPosition() + glm::vec3(0, headHeight, 0));
_myCamera.setTargetRotation(_myAvatar->getWorldAlignedOrientation() * glm::quat(glm::vec3(0.0f, PI, 0.0f)));
_myCamera.setTargetPosition(_myAvatar->getPosition() + glm::vec3(0, headHeight + (_raiseMirror * _myAvatar->getScale()), 0));
_myCamera.setTargetRotation(_myAvatar->getWorldAlignedOrientation() * glm::quat(glm::vec3(0.0f, PI + _rotateMirror, 0.0f)));
// if the head would intersect the near clip plane, we must push the camera out
glm::vec3 relativePosition = glm::inverse(_myCamera.getTargetRotation()) *
@ -870,7 +870,11 @@ void Application::keyPressEvent(QKeyEvent* event) {
case Qt::Key_Up:
if (_myCamera.getMode() == CAMERA_MODE_MIRROR) {
_scaleMirror *= 0.95;
if (!isShifted) {
_scaleMirror *= 0.95f;
} else {
_raiseMirror += 0.05f;
}
} else {
_myAvatar->setDriveKeys(isShifted ? UP : FWD, 1.f);
}
@ -878,18 +882,30 @@ void Application::keyPressEvent(QKeyEvent* event) {
case Qt::Key_Down:
if (_myCamera.getMode() == CAMERA_MODE_MIRROR) {
_scaleMirror *= 1.05;
if (!isShifted) {
_scaleMirror *= 1.05f;
} else {
_raiseMirror -= 0.05f;
}
} else {
_myAvatar->setDriveKeys(isShifted ? DOWN : BACK, 1.f);
}
break;
case Qt::Key_Left:
_myAvatar->setDriveKeys(isShifted ? LEFT : ROT_LEFT, 1.f);
if (_myCamera.getMode() == CAMERA_MODE_MIRROR) {
_rotateMirror += PI / 20.f;
} else {
_myAvatar->setDriveKeys(isShifted ? LEFT : ROT_LEFT, 1.f);
}
break;
case Qt::Key_Right:
_myAvatar->setDriveKeys(isShifted ? RIGHT : ROT_RIGHT, 1.f);
if (_myCamera.getMode() == CAMERA_MODE_MIRROR) {
_rotateMirror -= PI / 20.f;
} else {
_myAvatar->setDriveKeys(isShifted ? RIGHT : ROT_RIGHT, 1.f);
}
break;
case Qt::Key_I:
@ -1201,10 +1217,6 @@ void Application::touchBeginEvent(QTouchEvent* event) {
return;
}
// put any application specific touch behavior below here..
_lastTouchAvgX = _touchAvgX;
_lastTouchAvgY = _touchAvgY;
}
void Application::touchEndEvent(QTouchEvent* event) {
@ -1859,34 +1871,6 @@ void Application::updateMyAvatarLookAtPosition() {
_myAvatar->getHead()->setLookAtPosition(lookAtSpot);
}
void Application::updateHandAndTouch(float deltaTime) {
bool showWarnings = Menu::getInstance()->isOptionChecked(MenuOption::PipelineWarnings);
PerformanceWarning warn(showWarnings, "Application::updateHandAndTouch()");
// Update from Touch
if (_isTouchPressed) {
_lastTouchAvgX = _touchAvgX;
_lastTouchAvgY = _touchAvgY;
}
}
void Application::updateLeap(float deltaTime) {
bool showWarnings = Menu::getInstance()->isOptionChecked(MenuOption::PipelineWarnings);
PerformanceWarning warn(showWarnings, "Application::updateLeap()");
}
void Application::updateSixense(float deltaTime) {
bool showWarnings = Menu::getInstance()->isOptionChecked(MenuOption::PipelineWarnings);
PerformanceWarning warn(showWarnings, "Application::updateSixense()");
_sixenseManager.update(deltaTime);
}
void Application::updateSerialDevices(float deltaTime) {
bool showWarnings = Menu::getInstance()->isOptionChecked(MenuOption::PipelineWarnings);
PerformanceWarning warn(showWarnings, "Application::updateSerialDevices()");
}
void Application::updateThreads(float deltaTime) {
bool showWarnings = Menu::getInstance()->isOptionChecked(MenuOption::PipelineWarnings);
PerformanceWarning warn(showWarnings, "Application::updateThreads()");
@ -2000,11 +1984,7 @@ void Application::update(float deltaTime) {
updateVisage();
_myAvatar->updateLookAtTargetAvatar();
updateMyAvatarLookAtPosition();
updateHandAndTouch(deltaTime); // Update state for touch sensors
updateLeap(deltaTime); // Leap finger-sensing device
updateSixense(deltaTime); // Razer Hydra controllers
updateSerialDevices(deltaTime); // Read serial port interface devices
_sixenseManager.update(deltaTime);
updateMyAvatar(deltaTime); // Sample hardware, update view frustum if needed, and send avatar data to mixer/nodes
updateThreads(deltaTime); // If running non-threaded, then give the threads some time to process...
_avatarManager.updateOtherAvatars(deltaTime); //loop through all the other avatars and simulate them...
@ -2357,8 +2337,8 @@ void Application::updateShadowMap() {
updateUntranslatedViewMatrix();
_avatarManager.renderAvatars(Avatar::SHADOW_RENDER_MODE);
_particles.render();
_models.render();
_particles.render(OctreeRenderer::SHADOW_RENDER_MODE);
_models.render(OctreeRenderer::SHADOW_RENDER_MODE);
glPopMatrix();

View file

@ -343,10 +343,6 @@ private:
void updateFaceshift();
void updateVisage();
void updateMyAvatarLookAtPosition();
void updateHandAndTouch(float deltaTime);
void updateLeap(float deltaTime);
void updateSixense(float deltaTime);
void updateSerialDevices(float deltaTime);
void updateThreads(float deltaTime);
void updateMetavoxels(float deltaTime);
void updateCamera(float deltaTime);
@ -457,6 +453,8 @@ private:
glm::mat4 _projectionMatrix;
float _scaleMirror;
float _rotateMirror;
float _raiseMirror;
glm::mat4 _shadowMatrix;
@ -475,8 +473,6 @@ private:
float _touchAvgX;
float _touchAvgY;
float _lastTouchAvgX;
float _lastTouchAvgY;
float _touchDragStartedAvgX;
float _touchDragStartedAvgY;
bool _isTouchPressed; // true if multitouch has been pressed (clear when finished)

View file

@ -1068,6 +1068,7 @@ void Audio::toggleScope() {
memset(_scopeInput.data(), 0, width * sizeof(int16_t));
memset(_scopeOutputLeft.data(), 0, width * sizeof(int16_t));
memset(_scopeOutputRight.data(), 0, width * sizeof(int16_t));
_scopeEnabledPause = false;
}
}

View file

@ -131,6 +131,20 @@ void DatagramProcessor::processDatagrams() {
break;
}
case PacketTypeMuteEnvironment: {
glm::vec3 position;
float radius;
int headerSize = numBytesForPacketHeaderGivenPacketType(PacketTypeMuteEnvironment);
memcpy(&position, incomingPacket.constData() + headerSize, sizeof(glm::vec3));
memcpy(&radius, incomingPacket.constData() + headerSize + sizeof(glm::vec3), sizeof(float));
if (glm::distance(Application::getInstance()->getAvatar()->getPosition(), position) < radius
&& !Application::getInstance()->getAudio()->getMuted()) {
Application::getInstance()->getAudio()->toggleMute();
}
break;
}
default:
nodeList->processNodeData(senderSockAddr, incomingPacket);
break;

View file

@ -68,6 +68,7 @@ const float DEFAULT_FACESHIFT_EYE_DEFLECTION = 0.25f;
const float DEFAULT_AVATAR_LOD_DISTANCE_MULTIPLIER = 1.0f;
const int ONE_SECOND_OF_FRAMES = 60;
const int FIVE_SECONDS_OF_FRAMES = 5 * ONE_SECOND_OF_FRAMES;
const float MUTE_RADIUS = 50;
Menu::Menu() :
_actionHash(),
@ -271,9 +272,6 @@ Menu::Menu() :
addCheckableActionToQMenuAndActionHash(viewMenu, MenuOption::Bandwidth, 0, true);
addActionToQMenuAndActionHash(viewMenu, MenuOption::BandwidthDetails, 0, this, SLOT(bandwidthDetails()));
addActionToQMenuAndActionHash(viewMenu, MenuOption::OctreeStats, 0, this, SLOT(octreeStatsDetails()));
addCheckableActionToQMenuAndActionHash(viewMenu, MenuOption::AudioScope, 0, false,
appInstance->getAudio(),
SLOT(toggleScope()));
QMenu* developerMenu = addMenu("Developer");
@ -291,7 +289,6 @@ Menu::Menu() :
addCheckableActionToQMenuAndActionHash(renderOptionsMenu, MenuOption::Metavoxels, 0, true);
addCheckableActionToQMenuAndActionHash(renderOptionsMenu, MenuOption::BuckyBalls, 0, false);
addCheckableActionToQMenuAndActionHash(renderOptionsMenu, MenuOption::Particles, 0, true);
addCheckableActionToQMenuAndActionHash(renderOptionsMenu, MenuOption::Models, 0, true);
addActionToQMenuAndActionHash(renderOptionsMenu, MenuOption::LodTools, Qt::SHIFT | Qt::Key_L, this, SLOT(lodTools()));
QMenu* voxelOptionsMenu = developerMenu->addMenu("Voxel Options");
@ -308,6 +305,12 @@ Menu::Menu() :
addCheckableActionToQMenuAndActionHash(voxelOptionsMenu, MenuOption::DontFadeOnVoxelServerChanges);
addCheckableActionToQMenuAndActionHash(voxelOptionsMenu, MenuOption::DisableAutoAdjustLOD);
QMenu* modelOptionsMenu = developerMenu->addMenu("Model Options");
addCheckableActionToQMenuAndActionHash(modelOptionsMenu, MenuOption::Models, 0, true);
addCheckableActionToQMenuAndActionHash(modelOptionsMenu, MenuOption::DisplayModelBounds, 0, false);
addCheckableActionToQMenuAndActionHash(modelOptionsMenu, MenuOption::DisplayModelElementProxy, 0, false);
addCheckableActionToQMenuAndActionHash(modelOptionsMenu, MenuOption::DisplayModelElementChildProxies, 0, false);
QMenu* avatarOptionsMenu = developerMenu->addMenu("Avatar Options");
addCheckableActionToQMenuAndActionHash(avatarOptionsMenu, MenuOption::Avatars, 0, true);
@ -370,20 +373,6 @@ Menu::Menu() :
addCheckableActionToQMenuAndActionHash(renderDebugMenu, MenuOption::PipelineWarnings);
addCheckableActionToQMenuAndActionHash(renderDebugMenu, MenuOption::SuppressShortTimings);
addCheckableActionToQMenuAndActionHash(renderDebugMenu,
MenuOption::CullSharedFaces,
Qt::CTRL | Qt::SHIFT | Qt::Key_C,
false,
appInstance->getVoxels(),
SLOT(cullSharedFaces()));
addCheckableActionToQMenuAndActionHash(renderDebugMenu,
MenuOption::ShowCulledSharedFaces,
0,
false,
appInstance->getVoxels(),
SLOT(showCulledSharedFaces()));
QMenu* audioDebugMenu = developerMenu->addMenu("Audio Debugging Tools");
addCheckableActionToQMenuAndActionHash(audioDebugMenu, MenuOption::AudioNoiseReduction,
0,
@ -397,13 +386,21 @@ Menu::Menu() :
false,
appInstance->getAudio(),
SLOT(toggleMute()));
addActionToQMenuAndActionHash(audioDebugMenu,
MenuOption::MuteEnvironment,
0,
this,
SLOT(muteEnvironment()));
addCheckableActionToQMenuAndActionHash(audioDebugMenu, MenuOption::AudioToneInjection,
0,
false,
appInstance->getAudio(),
SLOT(toggleToneInjection()));
addCheckableActionToQMenuAndActionHash(audioDebugMenu, MenuOption::AudioScope, Qt::CTRL | Qt::Key_P, false,
appInstance->getAudio(),
SLOT(toggleScope()));
addCheckableActionToQMenuAndActionHash(audioDebugMenu, MenuOption::AudioScopePause,
Qt::CTRL | Qt::Key_P,
Qt::CTRL | Qt::SHIFT | Qt::Key_P ,
false,
appInstance->getAudio(),
SLOT(toggleScopePause()));
@ -1000,6 +997,30 @@ void Menu::multipleDestinationsDecision(const QJsonObject& userData, const QJson
disconnect(manager, &LocationManager::multipleDestinationsFound, this, &Menu::multipleDestinationsDecision);
}
void Menu::muteEnvironment() {
int headerSize = numBytesForPacketHeaderGivenPacketType(PacketTypeMuteEnvironment);
int packetSize = headerSize + sizeof(glm::vec3) + sizeof(float);
glm::vec3 position = Application::getInstance()->getAvatar()->getPosition();
char* packet = (char*)malloc(packetSize);
populatePacketHeader(packet, PacketTypeMuteEnvironment);
memcpy(packet + headerSize, &position, sizeof(glm::vec3));
memcpy(packet + headerSize + sizeof(glm::vec3), &MUTE_RADIUS, sizeof(float));
QByteArray mutePacket(packet, packetSize);
// grab our audio mixer from the NodeList, if it exists
SharedNodePointer audioMixer = NodeList::getInstance()->soloNodeOfType(NodeType::AudioMixer);
if (audioMixer) {
// send off this mute packet
NodeList::getInstance()->writeDatagram(mutePacket, audioMixer);
}
free(packet);
}
void Menu::goToLocation() {
MyAvatar* myAvatar = Application::getInstance()->getAvatar();
glm::vec3 avatarPos = myAvatar->getPosition();

View file

@ -191,6 +191,7 @@ private slots:
void audioMuteToggled();
void namedLocationCreated(LocationManager::NamedLocationCreateResponse response);
void multipleDestinationsDecision(const QJsonObject& userData, const QJsonObject& placeData);
void muteEnvironment();
private:
static Menu* _instance;
@ -296,13 +297,15 @@ namespace MenuOption {
const QString CollideWithParticles = "Collide With Particles";
const QString CollideWithVoxels = "Collide With Voxels";
const QString Collisions = "Collisions";
const QString CullSharedFaces = "Cull Shared Voxel Faces";
const QString DecreaseAvatarSize = "Decrease Avatar Size";
const QString DecreaseVoxelSize = "Decrease Voxel Size";
const QString DisableAutoAdjustLOD = "Disable Automatically Adjusting LOD";
const QString DisplayFrustum = "Display Frustum";
const QString DisplayHands = "Display Hands";
const QString DisplayHandTargets = "Display Hand Targets";
const QString DisplayModelBounds = "Display Model Bounds";
const QString DisplayModelElementProxy = "Display Model Element Bounds";
const QString DisplayModelElementChildProxies = "Display Model Element Children";
const QString DontFadeOnVoxelServerChanges = "Don't Fade In/Out on Voxel Server Changes";
const QString EchoLocalAudio = "Echo Local Audio";
const QString EchoServerAudio = "Echo Server Audio";
@ -337,8 +340,10 @@ namespace MenuOption {
const QString Metavoxels = "Metavoxels";
const QString Mirror = "Mirror";
const QString Models = "Models";
const QString ModelOptions = "Model Options";
const QString MoveWithLean = "Move with Lean";
const QString MuteAudio = "Mute Microphone";
const QString MuteEnvironment = "Mute Environment";
const QString NameLocation = "Name this location";
const QString NewVoxelCullingMode = "New Voxel Culling Mode";
const QString OctreeStats = "Voxel and Particle Statistics";
@ -361,7 +366,6 @@ namespace MenuOption {
const QString SettingsExport = "Export Settings";
const QString SettingsImport = "Import Settings";
const QString Shadows = "Shadows";
const QString ShowCulledSharedFaces = "Show Culled Shared Voxel Faces";
const QString ShowIKConstraints = "Show IK Constraints";
const QString Stars = "Stars";
const QString Stats = "Stats";

View file

@ -9,6 +9,7 @@
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
#include <QCheckBox>
#include <QComboBox>
#include <QDebug>
#include <QDialogButtonBox>
@ -42,6 +43,9 @@ static const QString TEXDIR_FIELD = "texdir";
static const QString LOD_FIELD = "lod";
static const QString JOINT_INDEX_FIELD = "jointIndex";
static const QString SCALE_FIELD = "scale";
static const QString TRANSLATION_X_FIELD = "tx";
static const QString TRANSLATION_Y_FIELD = "ty";
static const QString TRANSLATION_Z_FIELD = "tz";
static const QString JOINT_FIELD = "joint";
static const QString FREE_JOINT_FIELD = "freeJoint";
@ -519,6 +523,14 @@ bool ModelUploader::addPart(const QFile& file, const QByteArray& contents, const
return true;
}
static QDoubleSpinBox* createTranslationBox() {
QDoubleSpinBox* box = new QDoubleSpinBox();
const double MAX_TRANSLATION = 1000000.0;
box->setMinimum(-MAX_TRANSLATION);
box->setMaximum(MAX_TRANSLATION);
return box;
}
ModelPropertiesDialog::ModelPropertiesDialog(ModelType modelType, const QVariantHash& originalMapping,
const QString& basePath, const FBXGeometry& geometry) :
_modelType(modelType),
@ -540,7 +552,18 @@ ModelPropertiesDialog::ModelPropertiesDialog(ModelType modelType, const QVariant
_scale->setMaximum(FLT_MAX);
_scale->setSingleStep(0.01);
if (_modelType != ATTACHMENT_MODEL) {
if (_modelType == ATTACHMENT_MODEL) {
QHBoxLayout* translation = new QHBoxLayout();
form->addRow("Translation:", translation);
translation->addWidget(_translationX = createTranslationBox());
translation->addWidget(_translationY = createTranslationBox());
translation->addWidget(_translationZ = createTranslationBox());
form->addRow("Pivot About Center:", _pivotAboutCenter = new QCheckBox());
form->addRow("Pivot Joint:", _pivotJoint = createJointBox());
connect(_pivotAboutCenter, SIGNAL(toggled(bool)), SLOT(updatePivotJoint()));
_pivotAboutCenter->setChecked(true);
} else {
form->addRow("Left Eye Joint:", _leftEyeJoint = createJointBox());
form->addRow("Right Eye Joint:", _rightEyeJoint = createJointBox());
form->addRow("Neck Joint:", _neckJoint = createJointBox());
@ -584,7 +607,19 @@ QVariantHash ModelPropertiesDialog::getMapping() const {
mapping.insert(JOINT_INDEX_FIELD, jointIndices);
QVariantHash joints = mapping.value(JOINT_FIELD).toHash();
if (_modelType != ATTACHMENT_MODEL) {
if (_modelType == ATTACHMENT_MODEL) {
glm::vec3 pivot;
if (_pivotAboutCenter->isChecked()) {
pivot = (_geometry.meshExtents.minimum + _geometry.meshExtents.maximum) * 0.5f;
} else if (_pivotJoint->currentIndex() != 0) {
pivot = extractTranslation(_geometry.joints.at(_pivotJoint->currentIndex() - 1).transform);
}
mapping.insert(TRANSLATION_X_FIELD, -pivot.x * _scale->value() + _translationX->value());
mapping.insert(TRANSLATION_Y_FIELD, -pivot.y * _scale->value() + _translationY->value());
mapping.insert(TRANSLATION_Z_FIELD, -pivot.z * _scale->value() + _translationZ->value());
} else {
insertJointMapping(joints, "jointEyeLeft", _leftEyeJoint->currentText());
insertJointMapping(joints, "jointEyeRight", _rightEyeJoint->currentText());
insertJointMapping(joints, "jointNeck", _neckJoint->currentText());
@ -617,7 +652,14 @@ void ModelPropertiesDialog::reset() {
_scale->setValue(_originalMapping.value(SCALE_FIELD).toDouble());
QVariantHash jointHash = _originalMapping.value(JOINT_FIELD).toHash();
if (_modelType != ATTACHMENT_MODEL) {
if (_modelType == ATTACHMENT_MODEL) {
_translationX->setValue(_originalMapping.value(TRANSLATION_X_FIELD).toDouble());
_translationY->setValue(_originalMapping.value(TRANSLATION_Y_FIELD).toDouble());
_translationZ->setValue(_originalMapping.value(TRANSLATION_Z_FIELD).toDouble());
_pivotAboutCenter->setChecked(true);
_pivotJoint->setCurrentIndex(0);
} else {
setJointText(_leftEyeJoint, jointHash.value("jointEyeLeft").toString());
setJointText(_rightEyeJoint, jointHash.value("jointEyeRight").toString());
setJointText(_neckJoint, jointHash.value("jointNeck").toString());
@ -654,6 +696,10 @@ void ModelPropertiesDialog::chooseTextureDirectory() {
_textureDirectory->setText(directory.length() == _basePath.length() ? "." : directory.mid(_basePath.length() + 1));
}
void ModelPropertiesDialog::updatePivotJoint() {
_pivotJoint->setEnabled(!_pivotAboutCenter->isChecked());
}
void ModelPropertiesDialog::createNewFreeJoint(const QString& joint) {
QWidget* freeJoint = new QWidget();
QHBoxLayout* freeJointLayout = new QHBoxLayout();

View file

@ -19,6 +19,7 @@
#include "ui/ModelsBrowser.h"
class QCheckBox;
class QComboBox;
class QDoubleSpinBox;
class QFileInfo;
@ -83,6 +84,7 @@ public:
private slots:
void reset();
void chooseTextureDirectory();
void updatePivotJoint();
void createNewFreeJoint(const QString& joint = QString());
private:
@ -96,6 +98,11 @@ private:
QLineEdit* _name;
QPushButton* _textureDirectory;
QDoubleSpinBox* _scale;
QDoubleSpinBox* _translationX;
QDoubleSpinBox* _translationY;
QDoubleSpinBox* _translationZ;
QCheckBox* _pivotAboutCenter;
QComboBox* _pivotJoint;
QComboBox* _leftEyeJoint;
QComboBox* _rightEyeJoint;
QComboBox* _neckJoint;

View file

@ -73,7 +73,6 @@ const float BILLBOARD_LOD_DISTANCE = 40.0f;
void Avatar::init() {
getHead()->init();
getHand()->init();
_skeletonModel.init();
_initialized = true;
_shouldRenderBillboard = (getLODDistance() >= BILLBOARD_LOD_DISTANCE);
@ -215,17 +214,20 @@ void Avatar::render(const glm::vec3& cameraPosition, RenderMode renderMode) {
if (Menu::getInstance()->isOptionChecked(MenuOption::Avatars)) {
renderBody(renderMode, glowLevel);
}
if (Menu::getInstance()->isOptionChecked(MenuOption::RenderSkeletonCollisionShapes)) {
if (renderMode != SHADOW_RENDER_MODE &&
Menu::getInstance()->isOptionChecked(MenuOption::RenderSkeletonCollisionShapes)) {
_skeletonModel.updateShapePositions();
_skeletonModel.renderJointCollisionShapes(0.7f);
}
if (Menu::getInstance()->isOptionChecked(MenuOption::RenderHeadCollisionShapes)) {
if (renderMode != SHADOW_RENDER_MODE &&
Menu::getInstance()->isOptionChecked(MenuOption::RenderHeadCollisionShapes)) {
if (shouldRenderHead(cameraPosition, renderMode)) {
getHead()->getFaceModel().updateShapePositions();
getHead()->getFaceModel().renderJointCollisionShapes(0.7f);
}
}
if (Menu::getInstance()->isOptionChecked(MenuOption::RenderBoundingCollisionShapes)) {
if (renderMode != SHADOW_RENDER_MODE &&
Menu::getInstance()->isOptionChecked(MenuOption::RenderBoundingCollisionShapes)) {
if (shouldRenderHead(cameraPosition, renderMode)) {
getHead()->getFaceModel().updateShapePositions();
getHead()->getFaceModel().renderBoundingCollisionShapes(0.7f);
@ -234,7 +236,7 @@ void Avatar::render(const glm::vec3& cameraPosition, RenderMode renderMode) {
}
}
// If this is the avatar being looked at, render a little ball above their head
if (_isLookAtTarget) {
if (renderMode != SHADOW_RENDER_MODE &&_isLookAtTarget) {
const float LOOK_AT_INDICATOR_RADIUS = 0.03f;
const float LOOK_AT_INDICATOR_HEIGHT = 0.60f;
const float LOOK_AT_INDICATOR_COLOR[] = { 0.8f, 0.0f, 0.0f, 0.5f };
@ -340,7 +342,8 @@ glm::quat Avatar::computeRotationFromBodyToWorldUp(float proportion) const {
void Avatar::renderBody(RenderMode renderMode, float glowLevel) {
Model::RenderMode modelRenderMode = (renderMode == SHADOW_RENDER_MODE) ?
Model::SHADOW_RENDER_MODE : Model::DEFAULT_RENDER_MODE;
Model::SHADOW_RENDER_MODE : Model::DEFAULT_RENDER_MODE;
{
Glower glower(glowLevel);
@ -351,7 +354,7 @@ void Avatar::renderBody(RenderMode renderMode, float glowLevel) {
}
_skeletonModel.render(1.0f, modelRenderMode);
renderAttachments(modelRenderMode);
getHand()->render(false);
getHand()->render(false, modelRenderMode);
}
getHead()->render(1.0f, modelRenderMode);
}
@ -634,8 +637,8 @@ bool Avatar::findParticleCollisions(const glm::vec3& particleCenter, float parti
penetration)) {
CollisionInfo* collision = collisions.getNewCollision();
if (collision) {
collision->_type = PADDLE_HAND_COLLISION;
collision->_flags = jointIndex;
collision->_type = COLLISION_TYPE_PADDLE_HAND;
collision->_intData = jointIndex;
collision->_penetration = penetration;
collision->_addedVelocity = palm->getVelocity();
collided = true;
@ -705,7 +708,9 @@ void Avatar::setSkeletonModelURL(const QUrl& skeletonModelURL) {
void Avatar::setAttachmentData(const QVector<AttachmentData>& attachmentData) {
AvatarData::setAttachmentData(attachmentData);
if (QThread::currentThread() != thread()) {
return;
}
// make sure we have as many models as attachments
while (_attachmentModels.size() < attachmentData.size()) {
Model* model = new Model(this);
@ -844,11 +849,11 @@ float Avatar::getHeadHeight() const {
}
bool Avatar::collisionWouldMoveAvatar(CollisionInfo& collision) const {
if (!collision._data || collision._type != MODEL_COLLISION) {
if (!collision._data || collision._type != COLLISION_TYPE_MODEL) {
return false;
}
Model* model = static_cast<Model*>(collision._data);
int jointIndex = collision._flags;
int jointIndex = collision._intData;
if (model == &(_skeletonModel) && jointIndex != -1) {
// collision response of skeleton is temporarily disabled
@ -856,7 +861,7 @@ bool Avatar::collisionWouldMoveAvatar(CollisionInfo& collision) const {
//return _skeletonModel.collisionHitsMoveableJoint(collision);
}
if (model == &(getHead()->getFaceModel())) {
// ATM we always handle MODEL_COLLISIONS against the face.
// ATM we always handle COLLISION_TYPE_MODEL against the face.
return true;
}
return false;

View file

@ -29,22 +29,11 @@ const float PALM_COLLISION_RADIUS = 0.03f;
Hand::Hand(Avatar* owningAvatar) :
HandData((AvatarData*)owningAvatar),
_owningAvatar(owningAvatar),
_renderAlpha(1.0)
_owningAvatar(owningAvatar)
{
}
void Hand::init() {
}
void Hand::reset() {
}
void Hand::simulate(float deltaTime, bool isMine) {
calculateGeometry();
if (isMine) {
// Iterate hand controllers, take actions as needed
for (size_t i = 0; i < getNumPalms(); ++i) {
@ -146,57 +135,9 @@ void Hand::resolvePenetrations() {
}
}
void Hand::calculateGeometry() {
// generate finger tip balls....
_leapFingerTipBalls.clear();
for (size_t i = 0; i < getNumPalms(); ++i) {
PalmData& palm = getPalms()[i];
if (palm.isActive()) {
for (size_t f = 0; f < palm.getNumFingers(); ++f) {
FingerData& finger = palm.getFingers()[f];
if (finger.isActive()) {
const float standardBallRadius = FINGERTIP_COLLISION_RADIUS;
HandBall ball;
ball.rotation = getBaseOrientation();
ball.position = finger.getTipPosition();
ball.radius = standardBallRadius;
ball.touchForce = 0.0;
ball.isCollidable = true;
ball.isColliding = false;
_leapFingerTipBalls.push_back(ball);
}
}
}
}
// generate finger root balls....
_leapFingerRootBalls.clear();
for (size_t i = 0; i < getNumPalms(); ++i) {
PalmData& palm = getPalms()[i];
if (palm.isActive()) {
for (size_t f = 0; f < palm.getNumFingers(); ++f) {
FingerData& finger = palm.getFingers()[f];
if (finger.isActive()) {
const float standardBallRadius = 0.005f;
HandBall ball;
ball.rotation = getBaseOrientation();
ball.position = finger.getRootPosition();
ball.radius = standardBallRadius;
ball.touchForce = 0.0;
ball.isCollidable = true;
ball.isColliding = false;
_leapFingerRootBalls.push_back(ball);
}
}
}
}
}
void Hand::render(bool isMine) {
_renderAlpha = 1.0;
if (Menu::getInstance()->isOptionChecked(MenuOption::RenderSkeletonCollisionShapes)) {
void Hand::render(bool isMine, Model::RenderMode renderMode) {
if (renderMode != Model::SHADOW_RENDER_MODE &&
Menu::getInstance()->isOptionChecked(MenuOption::RenderSkeletonCollisionShapes)) {
// draw a green sphere at hand joint location, which is actually near the wrist)
for (size_t i = 0; i < getNumPalms(); i++) {
PalmData& palm = getPalms()[i];
@ -212,20 +153,19 @@ void Hand::render(bool isMine) {
}
}
if (Menu::getInstance()->isOptionChecked(MenuOption::DisplayHands)) {
renderLeapHands(isMine);
if (renderMode != Model::SHADOW_RENDER_MODE && Menu::getInstance()->isOptionChecked(MenuOption::DisplayHands)) {
renderHandTargets(isMine);
}
glEnable(GL_DEPTH_TEST);
glEnable(GL_RESCALE_NORMAL);
}
void Hand::renderLeapHands(bool isMine) {
void Hand::renderHandTargets(bool isMine) {
const float alpha = 1.0f;
const glm::vec3 handColor(1.0, 0.84, 0.66); // use the skin color
const glm::vec3 handColor(1.0, 0.0, 0.0); // Color the hand targets red to be different than skin
glEnable(GL_DEPTH_TEST);
glDepthMask(GL_TRUE);
@ -239,6 +179,7 @@ void Hand::renderLeapHands(bool isMine) {
glm::vec3 targetPosition;
palm.getBallHoldPosition(targetPosition);
glPushMatrix();
glTranslatef(targetPosition.x, targetPosition.y, targetPosition.z);
const float collisionRadius = 0.05f;
glColor4f(0.5f,0.5f,0.5f, alpha);
@ -247,38 +188,36 @@ void Hand::renderLeapHands(bool isMine) {
}
}
glPushMatrix();
// Draw the leap balls
for (size_t i = 0; i < _leapFingerTipBalls.size(); i++) {
if (alpha > 0.0f) {
if (_leapFingerTipBalls[i].isColliding) {
glColor4f(handColor.r, 0, 0, alpha);
} else {
glColor4f(handColor.r, handColor.g, handColor.b, alpha);
}
glPushMatrix();
glTranslatef(_leapFingerTipBalls[i].position.x, _leapFingerTipBalls[i].position.y, _leapFingerTipBalls[i].position.z);
glutSolidSphere(_leapFingerTipBalls[i].radius, 20.0f, 20.0f);
glPopMatrix();
}
}
// Draw the finger root cones
const float PALM_BALL_RADIUS = 0.03f;
const float PALM_DISK_RADIUS = 0.06f;
const float PALM_DISK_THICKNESS = 0.01f;
const float PALM_FINGER_ROD_RADIUS = 0.003f;
// Draw the palm ball and disk
for (size_t i = 0; i < getNumPalms(); ++i) {
PalmData& palm = getPalms()[i];
if (palm.isActive()) {
for (size_t f = 0; f < palm.getNumFingers(); ++f) {
FingerData& finger = palm.getFingers()[f];
if (finger.isActive()) {
glColor4f(handColor.r, handColor.g, handColor.b, 0.5);
glColor4f(handColor.r, handColor.g, handColor.b, alpha);
glm::vec3 tip = finger.getTipPosition();
glm::vec3 root = finger.getRootPosition();
Avatar::renderJointConnectingCone(root, tip, 0.001f, 0.003f);
Avatar::renderJointConnectingCone(root, tip, PALM_FINGER_ROD_RADIUS, PALM_FINGER_ROD_RADIUS);
// Render sphere at palm/finger root
glm::vec3 palmNormal = root + palm.getNormal() * PALM_DISK_THICKNESS;
Avatar::renderJointConnectingCone(root, palmNormal, PALM_DISK_RADIUS, 0.0f);
glPushMatrix();
glTranslatef(root.x, root.y, root.z);
glutSolidSphere(PALM_BALL_RADIUS, 20.0f, 20.0f);
glPopMatrix();
}
}
}
}
/*
// Draw the hand paddles
int MAX_NUM_PADDLES = 2; // one for left and one for right
glColor4f(handColor.r, handColor.g, handColor.b, 0.3f);
@ -309,6 +248,7 @@ void Hand::renderLeapHands(bool isMine) {
Avatar::renderJointConnectingCone(root, tip, HAND_PADDLE_RADIUS, 0.f);
}
}
*/
glDepthMask(GL_TRUE);
glEnable(GL_DEPTH_TEST);

View file

@ -24,6 +24,7 @@
#include <HandData.h>
#include "InterfaceConfig.h"
#include "renderer/Model.h"
#include "world.h"
@ -49,15 +50,9 @@ public:
float touchForce; // a scalar determining the amount that the cursor (or hand) is penetrating the ball
};
void init();
void reset();
void simulate(float deltaTime, bool isMine);
void render(bool isMine);
void render(bool isMine, Model::RenderMode renderMode = Model::DEFAULT_RENDER_MODE);
// getters
const glm::vec3& getLeapFingerTipBallPosition (int ball) const { return _leapFingerTipBalls [ball].position;}
const glm::vec3& getLeapFingerRootBallPosition(int ball) const { return _leapFingerRootBalls[ball].position;}
void collideAgainstAvatar(Avatar* avatar, bool isMyHand);
void collideAgainstOurself();
@ -71,14 +66,8 @@ private:
int _controllerButtons; /// Button states read from hand-held controllers
Avatar* _owningAvatar;
float _renderAlpha;
std::vector<HandBall> _leapFingerTipBalls;
std::vector<HandBall> _leapFingerRootBalls;
void renderLeapHands(bool isMine);
void renderLeapFingerTrails();
void calculateGeometry();
void renderHandTargets(bool isMine);
};
#endif // hifi_Hand_h

View file

@ -106,16 +106,10 @@ void Head::simulate(float deltaTime, bool isMine, bool billboard) {
const float BROW_LIFT_THRESHOLD = 100.0f;
if (_audioAttack > BROW_LIFT_THRESHOLD) {
_browAudioLift += sqrtf(_audioAttack) * 0.00005f;
_browAudioLift += sqrtf(_audioAttack) * 0.01f;
}
_browAudioLift = glm::clamp(_browAudioLift *= 0.7f, 0.0f, 1.0f);
const float CLAMP = 0.01f;
if (_browAudioLift > CLAMP) {
_browAudioLift = CLAMP;
}
_browAudioLift *= 0.7f;
const float BLINK_SPEED = 10.0f;
const float FULLY_OPEN = 0.0f;
const float FULLY_CLOSED = 1.0f;
@ -147,12 +141,12 @@ void Head::simulate(float deltaTime, bool isMine, bool billboard) {
}
// use data to update fake Faceshift blendshape coefficients
const float BROW_LIFT_SCALE = 500.0f;
const float JAW_OPEN_SCALE = 0.01f;
const float JAW_OPEN_DEAD_ZONE = 0.75f;
Application::getInstance()->getFaceshift()->updateFakeCoefficients(_leftEyeBlink, _rightEyeBlink,
min(1.0f, _browAudioLift * BROW_LIFT_SCALE), glm::clamp(sqrt(_averageLoudness * JAW_OPEN_SCALE) -
JAW_OPEN_DEAD_ZONE, 0.0f, 1.0f), _blendshapeCoefficients);
const float JAW_OPEN_SCALE = 10.f;
Application::getInstance()->getFaceshift()->updateFakeCoefficients(_leftEyeBlink,
_rightEyeBlink,
_browAudioLift,
glm::clamp(log(_averageLoudness) / JAW_OPEN_SCALE, 0.0f, 1.0f),
_blendshapeCoefficients);
}
if (!isMine) {
@ -182,7 +176,7 @@ void Head::relaxLean(float deltaTime) {
}
void Head::render(float alpha, Model::RenderMode mode) {
if (_faceModel.render(alpha, mode) && _renderLookatVectors) {
if (_faceModel.render(alpha, mode) && _renderLookatVectors && mode != Model::SHADOW_RENDER_MODE) {
renderLookatVectors(_leftEyePosition, _rightEyePosition, _lookAtPosition);
}
}

View file

@ -21,6 +21,7 @@
#include <QtCore/QTimer>
#include <AccountManager.h>
#include <GeometryUtil.h>
#include <NodeList.h>
#include <PacketHeaders.h>
#include <SharedUtil.h>
@ -45,6 +46,9 @@ const float PITCH_SPEED = 100.0f; // degrees/sec
const float COLLISION_RADIUS_SCALAR = 1.2f; // pertains to avatar-to-avatar collisions
const float COLLISION_RADIUS_SCALE = 0.125f;
const float MIN_KEYBOARD_CONTROL_SPEED = 2.0f;
const float MAX_WALKING_SPEED = 3.0f * MIN_KEYBOARD_CONTROL_SPEED;
const float DATA_SERVER_LOCATION_CHANGE_UPDATE_MSECS = 5.0f * 1000.0f;
// TODO: normalize avatar speed for standard avatar size, then scale all motion logic
@ -63,6 +67,7 @@ MyAvatar::MyAvatar() :
_distanceToNearestAvatar(std::numeric_limits<float>::max()),
_wasPushing(false),
_isPushing(false),
_trapDuration(0.0f),
_thrust(0.0f),
_motorVelocity(0.0f),
_motorTimescale(DEFAULT_MOTOR_TIMESCALE),
@ -92,18 +97,21 @@ MyAvatar::~MyAvatar() {
void MyAvatar::reset() {
_skeletonModel.reset();
getHead()->reset();
getHand()->reset();
_oculusYawOffset = 0.0f;
setVelocity(glm::vec3(0.0f));
setThrust(glm::vec3(0.0f));
setOrientation(glm::quat(glm::vec3(0.0f)));
// Reset the pitch and roll components of the avatar's orientation, preserve yaw direction
glm::vec3 eulers = safeEulerAngles(getOrientation());
eulers.x = 0.f;
eulers.z = 0.f;
setOrientation(glm::quat(eulers));
}
void MyAvatar::update(float deltaTime) {
Head* head = getHead();
head->relaxLean(deltaTime);
updateFromGyros(deltaTime);
updateFromFaceTracker(deltaTime);
if (Menu::getInstance()->isOptionChecked(MenuOption::MoveWithLean)) {
// Faceshift drive is enabled, set the avatar drive based on the head position
moveWithLean();
@ -130,7 +138,14 @@ void MyAvatar::simulate(float deltaTime) {
}
// update the movement of the hand and process handshaking with other avatars...
updateHandMovementAndTouching(deltaTime);
bool pointing = false;
if (_mousePressed) {
_handState = HAND_STATE_GRASPING;
} else if (pointing) {
_handState = HAND_STATE_POINTING;
} else {
_handState = HAND_STATE_NULL;
}
updateOrientation(deltaTime);
@ -212,6 +227,8 @@ void MyAvatar::simulate(float deltaTime) {
}
if (_collisionGroups & COLLISION_GROUP_VOXELS) {
updateCollisionWithVoxels(deltaTime, radius);
} else {
_trapDuration = 0.0f;
}
if (_collisionGroups & COLLISION_GROUP_AVATARS) {
updateCollisionWithAvatars(deltaTime);
@ -224,7 +241,7 @@ void MyAvatar::simulate(float deltaTime) {
}
// Update avatar head rotation with sensor data
void MyAvatar::updateFromGyros(float deltaTime) {
void MyAvatar::updateFromFaceTracker(float deltaTime) {
glm::vec3 estimatedPosition, estimatedRotation;
FaceTracker* tracker = Application::getInstance()->getActiveFaceTracker();
@ -333,7 +350,9 @@ void MyAvatar::render(const glm::vec3& cameraPosition, RenderMode renderMode) {
return; // exit early
}
Avatar::render(cameraPosition, renderMode);
if (Menu::getInstance()->isOptionChecked(MenuOption::ShowIKConstraints)) {
// don't display IK constraints in shadow mode
if (Menu::getInstance()->isOptionChecked(MenuOption::ShowIKConstraints) && renderMode != SHADOW_RENDER_MODE) {
_skeletonModel.renderIKConstraints();
}
}
@ -496,6 +515,55 @@ void MyAvatar::loadData(QSettings* settings) {
settings->endGroup();
}
void MyAvatar::saveAttachmentData(const AttachmentData& attachment) const {
QSettings* settings = Application::getInstance()->lockSettings();
settings->beginGroup("savedAttachmentData");
settings->beginGroup(_skeletonModel.getURL().toString());
settings->beginGroup(attachment.modelURL.toString());
settings->setValue("jointName", attachment.jointName);
settings->setValue("translation_x", attachment.translation.x);
settings->setValue("translation_y", attachment.translation.y);
settings->setValue("translation_z", attachment.translation.z);
glm::vec3 eulers = safeEulerAngles(attachment.rotation);
settings->setValue("rotation_x", eulers.x);
settings->setValue("rotation_y", eulers.y);
settings->setValue("rotation_z", eulers.z);
settings->setValue("scale", attachment.scale);
settings->endGroup();
settings->endGroup();
settings->endGroup();
Application::getInstance()->unlockSettings();
}
AttachmentData MyAvatar::loadAttachmentData(const QUrl& modelURL) const {
QSettings* settings = Application::getInstance()->lockSettings();
settings->beginGroup("savedAttachmentData");
settings->beginGroup(_skeletonModel.getURL().toString());
settings->beginGroup(modelURL.toString());
AttachmentData attachment;
attachment.modelURL = modelURL;
attachment.jointName = settings->value("jointName").toString();
attachment.translation.x = loadSetting(settings, "translation_x", 0.0f);
attachment.translation.y = loadSetting(settings, "translation_y", 0.0f);
attachment.translation.z = loadSetting(settings, "translation_z", 0.0f);
glm::vec3 eulers;
eulers.x = loadSetting(settings, "rotation_x", 0.0f);
eulers.y = loadSetting(settings, "rotation_y", 0.0f);
eulers.z = loadSetting(settings, "rotation_z", 0.0f);
attachment.rotation = glm::quat(eulers);
attachment.scale = loadSetting(settings, "scale", 1.0f);
settings->endGroup();
settings->endGroup();
settings->endGroup();
Application::getInstance()->unlockSettings();
return attachment;
}
int MyAvatar::parseDataAtOffset(const QByteArray& packet, int offset) {
qDebug() << "Error: ignoring update packet for MyAvatar"
<< " packetLength = " << packet.size()
@ -567,6 +635,31 @@ void MyAvatar::setSkeletonModelURL(const QUrl& skeletonModelURL) {
_billboardValid = false;
}
void MyAvatar::setAttachmentData(const QVector<AttachmentData>& attachmentData) {
Avatar::setAttachmentData(attachmentData);
if (QThread::currentThread() != thread()) {
return;
}
_billboardValid = false;
}
void MyAvatar::attach(const QString& modelURL, const QString& jointName, const glm::vec3& translation,
const glm::quat& rotation, float scale, bool allowDuplicates, bool useSaved) {
if (QThread::currentThread() != thread()) {
Avatar::attach(modelURL, jointName, translation, rotation, scale, allowDuplicates, useSaved);
return;
}
if (useSaved) {
AttachmentData attachment = loadAttachmentData(modelURL);
if (!attachment.jointName.isEmpty()) {
Avatar::attach(modelURL, attachment.jointName, attachment.translation,
attachment.rotation, attachment.scale, allowDuplicates, useSaved);
return;
}
}
Avatar::attach(modelURL, jointName, translation, rotation, scale, allowDuplicates, useSaved);
}
void MyAvatar::renderBody(RenderMode renderMode, float glowLevel) {
if (!(_skeletonModel.isRenderable() && getHead()->getFaceModel().isRenderable())) {
return; // wait until both models are loaded
@ -582,7 +675,7 @@ void MyAvatar::renderBody(RenderMode renderMode, float glowLevel) {
if (shouldRenderHead(Application::getInstance()->getCamera()->getPosition(), renderMode)) {
getHead()->render(1.0f, modelRenderMode);
}
getHand()->render(true);
getHand()->render(true, modelRenderMode);
}
const float RENDER_HEAD_CUTOFF_DISTANCE = 0.50f;
@ -693,8 +786,6 @@ void MyAvatar::updateMotorFromKeyboard(float deltaTime, bool walking) {
if (directionLength > EPSILON) {
direction /= directionLength;
// the finalMotorSpeed depends on whether we are walking or not
const float MIN_KEYBOARD_CONTROL_SPEED = 2.0f;
const float MAX_WALKING_SPEED = 3.0f * MIN_KEYBOARD_CONTROL_SPEED;
float finalMaxMotorSpeed = walking ? MAX_WALKING_SPEED : _maxMotorSpeed;
float motorLength = glm::length(_motorVelocity);
@ -905,40 +996,6 @@ void MyAvatar::updateThrust(float deltaTime) {
}
*/
void MyAvatar::updateHandMovementAndTouching(float deltaTime) {
glm::quat orientation = getOrientation();
// reset hand and arm positions according to hand movement
glm::vec3 up = orientation * IDENTITY_UP;
bool pointing = false;
if (glm::length(_mouseRayDirection) > EPSILON && !Application::getInstance()->isMouseHidden()) {
// confine to the approximate shoulder plane
glm::vec3 pointDirection = _mouseRayDirection;
if (glm::dot(_mouseRayDirection, up) > 0.0f) {
glm::vec3 projectedVector = glm::cross(up, glm::cross(_mouseRayDirection, up));
if (glm::length(projectedVector) > EPSILON) {
pointDirection = glm::normalize(projectedVector);
}
}
glm::vec3 shoulderPosition;
if (_skeletonModel.getRightShoulderPosition(shoulderPosition)) {
glm::vec3 farVector = _mouseRayOrigin + pointDirection * (float)TREE_SCALE - shoulderPosition;
const float ARM_RETRACTION = 0.75f;
float retractedLength = _skeletonModel.getRightArmLength() * ARM_RETRACTION;
setHandPosition(shoulderPosition + glm::normalize(farVector) * retractedLength);
pointing = true;
}
}
if (_mousePressed) {
_handState = HAND_STATE_GRASPING;
} else if (pointing) {
_handState = HAND_STATE_POINTING;
} else {
_handState = HAND_STATE_NULL;
}
}
void MyAvatar::updateCollisionWithEnvironment(float deltaTime, float radius) {
glm::vec3 up = getBodyUpDirection();
@ -958,65 +1015,102 @@ void MyAvatar::updateCollisionWithEnvironment(float deltaTime, float radius) {
static CollisionList myCollisions(64);
void MyAvatar::updateCollisionWithVoxels(float deltaTime, float radius) {
const float MAX_VOXEL_COLLISION_SPEED = 100.0f;
float speed = glm::length(_velocity);
if (speed > MAX_VOXEL_COLLISION_SPEED) {
// don't even bother to try to collide against voxles when moving very fast
_trapDuration = 0.0f;
return;
}
bool isTrapped = false;
myCollisions.clear();
const CapsuleShape& boundingShape = _skeletonModel.getBoundingShape();
if (Application::getInstance()->getVoxelTree()->findShapeCollisions(&boundingShape, myCollisions)) {
if (Application::getInstance()->getVoxelTree()->findShapeCollisions(&boundingShape, myCollisions, Octree::TryLock)) {
const float VOXEL_ELASTICITY = 0.0f;
const float VOXEL_DAMPING = 0.0f;
float capsuleRadius = boundingShape.getRadius();
float capsuleHalfHeight = boundingShape.getHalfHeight();
const float MAX_STEP_HEIGHT = capsuleRadius + capsuleHalfHeight;
const float MIN_STEP_HEIGHT = 0.0f;
glm::vec3 footBase = boundingShape.getPosition() - (capsuleRadius + capsuleHalfHeight) * _worldUpDirection;
float highestStep = 0.0f;
glm::vec3 stepPenetration(0.0f);
glm::vec3 totalPenetration(0.0f);
if (glm::length2(_gravity) > EPSILON) {
if (myCollisions.size() == 1) {
// trivial case
CollisionInfo* collision = myCollisions[0];
applyHardCollision(collision->_penetration, VOXEL_ELASTICITY, VOXEL_DAMPING);
_lastFloorContactPoint = collision->_contactPoint - collision->_penetration;
for (int i = 0; i < myCollisions.size(); ++i) {
CollisionInfo* collision = myCollisions[i];
glm::vec3 cubeCenter = collision->_vecData;
float cubeSide = collision->_floatData;
float verticalDepth = glm::dot(collision->_penetration, _worldUpDirection);
float horizontalDepth = glm::length(collision->_penetration - verticalDepth * _worldUpDirection);
const float MAX_TRAP_PERIOD = 0.125f;
if (horizontalDepth > capsuleRadius || fabsf(verticalDepth) > MAX_STEP_HEIGHT) {
isTrapped = true;
if (_trapDuration > MAX_TRAP_PERIOD) {
float distance = glm::dot(boundingShape.getPosition() - cubeCenter, _worldUpDirection);
if (distance < 0.0f) {
distance = fabsf(distance) + 0.5f * cubeSide;
}
distance += capsuleRadius + capsuleHalfHeight;
totalPenetration = addPenetrations(totalPenetration, - distance * _worldUpDirection);
continue;
}
} else if (_trapDuration > MAX_TRAP_PERIOD) {
// we're trapped, ignore this collision
continue;
}
totalPenetration = addPenetrations(totalPenetration, collision->_penetration);
if (glm::dot(collision->_penetration, _velocity) >= 0.0f) {
glm::vec3 cubeTop = cubeCenter + (0.5f * cubeSide) * _worldUpDirection;
float stepHeight = glm::dot(_worldUpDirection, cubeTop - footBase);
if (stepHeight > highestStep) {
highestStep = stepHeight;
stepPenetration = collision->_penetration;
}
}
}
float penetrationLength = glm::length(totalPenetration);
if (penetrationLength < EPSILON) {
_trapDuration = 0.0f;
return;
}
float verticalPenetration = glm::dot(totalPenetration, _worldUpDirection);
if (highestStep > MIN_STEP_HEIGHT && highestStep < MAX_STEP_HEIGHT && verticalPenetration <= 0.0f) {
// we're colliding against an edge
glm::vec3 targetVelocity = _motorVelocity;
if (_motionBehaviors & AVATAR_MOTION_MOTOR_USE_LOCAL_FRAME) {
// rotate _motorVelocity into world frame
glm::quat rotation = getHead()->getCameraOrientation();
targetVelocity = rotation * _motorVelocity;
}
if (_wasPushing && glm::dot(targetVelocity, totalPenetration) > EPSILON) {
// we're puhing into the edge, so we want to lift
// remove unhelpful horizontal component of the step's penetration
totalPenetration -= stepPenetration - (glm::dot(stepPenetration, _worldUpDirection) * _worldUpDirection);
// further adjust penetration to help lift
float liftSpeed = glm::max(MAX_WALKING_SPEED, speed);
float thisStep = glm::min(liftSpeed * deltaTime, highestStep);
float extraStep = glm::dot(totalPenetration, _worldUpDirection) + thisStep;
if (extraStep > 0.0f) {
totalPenetration -= extraStep * _worldUpDirection;
}
_position -= totalPenetration;
} else {
// This is special collision handling for when walking on a voxel field which
// prevents snagging at corners and seams.
// sift through the collisions looking for one against the "floor"
int floorIndex = 0;
float distanceToFloor = 0.0f;
float penetrationWithFloor = 0.0f;
for (int i = 0; i < myCollisions.size(); ++i) {
CollisionInfo* collision = myCollisions[i];
float distance = glm::dot(_gravity, collision->_contactPoint - _position);
if (distance > distanceToFloor) {
distanceToFloor = distance;
penetrationWithFloor = glm::dot(_gravity, collision->_penetration);
floorIndex = i;
}
}
// step through the collisions again and apply each that is not redundant
glm::vec3 oldPosition = _position;
for (int i = 0; i < myCollisions.size(); ++i) {
CollisionInfo* collision = myCollisions[i];
if (i == floorIndex) {
applyHardCollision(collision->_penetration, VOXEL_ELASTICITY, VOXEL_DAMPING);
_lastFloorContactPoint = collision->_contactPoint - collision->_penetration;
} else {
float distance = glm::dot(_gravity, collision->_contactPoint - oldPosition);
float penetration = glm::dot(_gravity, collision->_penetration);
if (fabsf(distance - distanceToFloor) > penetrationWithFloor || penetration > penetrationWithFloor) {
// resolution of the deepest penetration would not resolve this one
// so we apply the collision
applyHardCollision(collision->_penetration, VOXEL_ELASTICITY, VOXEL_DAMPING);
}
}
}
// we're not pushing into the edge, so let the avatar fall
applyHardCollision(totalPenetration, VOXEL_ELASTICITY, VOXEL_DAMPING);
}
} else {
// no gravity -- apply all collisions
for (int i = 0; i < myCollisions.size(); ++i) {
CollisionInfo* collision = myCollisions[i];
applyHardCollision(collision->_penetration, VOXEL_ELASTICITY, VOXEL_DAMPING);
}
applyHardCollision(totalPenetration, VOXEL_ELASTICITY, VOXEL_DAMPING);
}
const float VOXEL_COLLISION_FREQUENCY = 0.5f;
updateCollisionSound(myCollisions[0]->_penetration, deltaTime, VOXEL_COLLISION_FREQUENCY);
}
_trapDuration = isTrapped ? _trapDuration + deltaTime : 0.0f;
}
void MyAvatar::applyHardCollision(const glm::vec3& penetration, float elasticity, float damping) {
@ -1281,6 +1375,11 @@ void MyAvatar::maybeUpdateBillboard() {
if (_billboardValid || !(_skeletonModel.isLoadedWithTextures() && getHead()->getFaceModel().isLoadedWithTextures())) {
return;
}
foreach (Model* model, _attachmentModels) {
if (!model->isLoadedWithTextures()) {
return;
}
}
QImage image = Application::getInstance()->renderAvatarBillboard();
_billboard.clear();
QBuffer buffer(&_billboard);

View file

@ -38,7 +38,7 @@ public:
void reset();
void update(float deltaTime);
void simulate(float deltaTime);
void updateFromGyros(float deltaTime);
void updateFromFaceTracker(float deltaTime);
void moveWithLean();
void render(const glm::vec3& cameraPosition, RenderMode renderMode = NORMAL_RENDER_MODE);
@ -66,6 +66,9 @@ public:
void saveData(QSettings* settings);
void loadData(QSettings* settings);
void saveAttachmentData(const AttachmentData& attachment) const;
AttachmentData loadAttachmentData(const QUrl& modelURL) const;
// Set what driving keys are being pressed to control thrust levels
void setDriveKeys(int key, float val) { _driveKeys[key] = val; };
bool getDriveKeys(int key) { return _driveKeys[key] != 0.f; };
@ -86,7 +89,12 @@ public:
virtual void clearJointData(int index);
virtual void setFaceModelURL(const QUrl& faceModelURL);
virtual void setSkeletonModelURL(const QUrl& skeletonModelURL);
virtual void setAttachmentData(const QVector<AttachmentData>& attachmentData);
virtual void attach(const QString& modelURL, const QString& jointName = QString(),
const glm::vec3& translation = glm::vec3(), const glm::quat& rotation = glm::quat(), float scale = 1.0f,
bool allowDuplicates = false, bool useSaved = true);
virtual void setCollisionGroups(quint32 collisionGroups);
void setMotionBehaviorsByScript(quint32 flags);
@ -125,6 +133,7 @@ private:
bool _wasPushing;
bool _isPushing;
float _trapDuration; // seconds that avatar has been trapped by collisions
glm::vec3 _thrust; // final acceleration from outside sources for the current frame
glm::vec3 _motorVelocity; // intended velocity of avatar motion
@ -146,7 +155,6 @@ private:
float computeMotorTimescale();
void applyMotor(float deltaTime);
void applyThrust(float deltaTime);
void updateHandMovementAndTouching(float deltaTime);
void updateCollisionWithAvatars(float deltaTime);
void updateCollisionWithEnvironment(float deltaTime, float radius);
void updateCollisionWithVoxels(float deltaTime, float radius);

View file

@ -38,24 +38,23 @@ void SkeletonModel::simulate(float deltaTime, bool fullUpdate) {
Hand* hand = _owningAvatar->getHand();
hand->getLeftRightPalmIndices(leftPalmIndex, rightPalmIndex);
const float HAND_RESTORATION_PERIOD = 1.f; // seconds
float handRestorePercent = glm::clamp(deltaTime / HAND_RESTORATION_PERIOD, 0.f, 1.f);
const float HAND_RESTORATION_RATE = 0.25f;
const FBXGeometry& geometry = _geometry->getFBXGeometry();
if (leftPalmIndex == -1) {
// no Leap data; set hands from mouse
if (_owningAvatar->getHandState() == HAND_STATE_NULL) {
restoreRightHandPosition(handRestorePercent);
restoreRightHandPosition(HAND_RESTORATION_RATE);
} else {
applyHandPosition(geometry.rightHandJointIndex, _owningAvatar->getHandPosition());
}
restoreLeftHandPosition(handRestorePercent);
restoreLeftHandPosition(HAND_RESTORATION_RATE);
} else if (leftPalmIndex == rightPalmIndex) {
// right hand only
applyPalmData(geometry.rightHandJointIndex, geometry.rightFingerJointIndices, geometry.rightFingertipJointIndices,
hand->getPalms()[leftPalmIndex]);
restoreLeftHandPosition(handRestorePercent);
restoreLeftHandPosition(HAND_RESTORATION_RATE);
} else {
applyPalmData(geometry.leftHandJointIndex, geometry.leftFingerJointIndices, geometry.leftFingertipJointIndices,

View file

@ -30,6 +30,7 @@ const float NECK_Z = 300.f; // millimeters
SixenseManager::SixenseManager() {
#ifdef HAVE_SIXENSE
_lastMovement = 0;
_amountMoved = glm::vec3(0.0f);
_calibrationState = CALIBRATION_STATE_IDLE;
// By default we assume the _neckBase (in orb frame) is as high above the orb
@ -122,14 +123,21 @@ void SixenseManager::update(float deltaTime) {
palm->setRawRotation(rotation);
// Compute current velocity from position change
glm::vec3 rawVelocity = (position - palm->getRawPosition()) / deltaTime / 1000.f;
glm::vec3 rawVelocity;
if (deltaTime > 0.f) {
rawVelocity = (position - palm->getRawPosition()) / deltaTime / 1000.f;
} else {
rawVelocity = glm::vec3(0.0f);
}
palm->setRawVelocity(rawVelocity); // meters/sec
palm->setRawPosition(position);
// use the velocity to determine whether there's any movement (if the hand isn't new)
const float MOVEMENT_SPEED_THRESHOLD = 0.05f;
if (glm::length(rawVelocity) > MOVEMENT_SPEED_THRESHOLD && foundHand) {
const float MOVEMENT_DISTANCE_THRESHOLD = 0.003f;
_amountMoved += rawVelocity * deltaTime;
if (glm::length(_amountMoved) > MOVEMENT_DISTANCE_THRESHOLD && foundHand) {
_lastMovement = usecTimestampNow();
_amountMoved = glm::vec3(0.0f);
}
// initialize the "finger" based on the direction
@ -143,7 +151,11 @@ void SixenseManager::update(float deltaTime) {
// Store the one fingertip in the palm structure so we can track velocity
glm::vec3 oldTipPosition = palm->getTipRawPosition();
palm->setTipVelocity((newTipPosition - oldTipPosition) / deltaTime / 1000.f);
if (deltaTime > 0.f) {
palm->setTipVelocity((newTipPosition - oldTipPosition) / deltaTime / 1000.f);
} else {
palm->setTipVelocity(glm::vec3(0.f));
}
palm->setTipPosition(newTipPosition);
// three fingers indicates to the skeleton that we have enough data to determine direction
@ -158,8 +170,8 @@ void SixenseManager::update(float deltaTime) {
}
// if the controllers haven't been moved in a while, disable
const unsigned int MOVEMENT_DISABLE_DURATION = 30 * 1000 * 1000;
if (usecTimestampNow() - _lastMovement > MOVEMENT_DISABLE_DURATION) {
const unsigned int MOVEMENT_DISABLE_SECONDS = 3;
if (usecTimestampNow() - _lastMovement > (MOVEMENT_DISABLE_SECONDS * 1000 * 1000)) {
for (std::vector<PalmData>::iterator it = hand->getPalms().begin(); it != hand->getPalms().end(); it++) {
it->setActive(false);
}

View file

@ -64,6 +64,7 @@ private:
#endif
quint64 _lastMovement;
glm::vec3 _amountMoved;
};
#endif // hifi_SixenseManager_h

View file

@ -39,8 +39,8 @@ void ModelTreeRenderer::update() {
}
}
void ModelTreeRenderer::render() {
OctreeRenderer::render();
void ModelTreeRenderer::render(RenderMode renderMode) {
OctreeRenderer::render(renderMode);
}
Model* ModelTreeRenderer::getModel(const QString& url) {
@ -66,54 +66,130 @@ void ModelTreeRenderer::renderElement(OctreeElement* element, RenderArgs* args)
const QList<ModelItem>& modelItems = modelTreeElement->getModels();
uint16_t numberOfModels = modelItems.size();
bool isShadowMode = args->_renderMode == OctreeRenderer::SHADOW_RENDER_MODE;
bool displayModelBounds = Menu::getInstance()->isOptionChecked(MenuOption::DisplayModelBounds);
bool displayElementProxy = Menu::getInstance()->isOptionChecked(MenuOption::DisplayModelElementProxy);
bool displayElementChildProxies = Menu::getInstance()->isOptionChecked(MenuOption::DisplayModelElementChildProxies);
if (!isShadowMode && displayElementProxy && numberOfModels > 0) {
glm::vec3 elementCenter = modelTreeElement->getAABox().calcCenter() * (float)TREE_SCALE;
float elementSize = modelTreeElement->getScale() * (float)TREE_SCALE;
glColor3f(1.0f, 0.0f, 0.0f);
glPushMatrix();
glTranslatef(elementCenter.x, elementCenter.y, elementCenter.z);
glutWireCube(elementSize);
glPopMatrix();
if (displayElementChildProxies) {
// draw the children
float halfSize = elementSize / 2.0f;
float quarterSize = elementSize / 4.0f;
glColor3f(1.0f, 1.0f, 0.0f);
glPushMatrix();
glTranslatef(elementCenter.x - quarterSize, elementCenter.y - quarterSize, elementCenter.z - quarterSize);
glutWireCube(halfSize);
glPopMatrix();
glColor3f(1.0f, 0.0f, 1.0f);
glPushMatrix();
glTranslatef(elementCenter.x + quarterSize, elementCenter.y - quarterSize, elementCenter.z - quarterSize);
glutWireCube(halfSize);
glPopMatrix();
glColor3f(0.0f, 1.0f, 0.0f);
glPushMatrix();
glTranslatef(elementCenter.x - quarterSize, elementCenter.y + quarterSize, elementCenter.z - quarterSize);
glutWireCube(halfSize);
glPopMatrix();
glColor3f(0.0f, 0.0f, 1.0f);
glPushMatrix();
glTranslatef(elementCenter.x - quarterSize, elementCenter.y - quarterSize, elementCenter.z + quarterSize);
glutWireCube(halfSize);
glPopMatrix();
glColor3f(1.0f, 1.0f, 1.0f);
glPushMatrix();
glTranslatef(elementCenter.x + quarterSize, elementCenter.y + quarterSize, elementCenter.z + quarterSize);
glutWireCube(halfSize);
glPopMatrix();
glColor3f(0.0f, 0.5f, 0.5f);
glPushMatrix();
glTranslatef(elementCenter.x - quarterSize, elementCenter.y + quarterSize, elementCenter.z + quarterSize);
glutWireCube(halfSize);
glPopMatrix();
glColor3f(0.5f, 0.0f, 0.0f);
glPushMatrix();
glTranslatef(elementCenter.x + quarterSize, elementCenter.y - quarterSize, elementCenter.z + quarterSize);
glutWireCube(halfSize);
glPopMatrix();
glColor3f(0.0f, 0.5f, 0.0f);
glPushMatrix();
glTranslatef(elementCenter.x + quarterSize, elementCenter.y + quarterSize, elementCenter.z - quarterSize);
glutWireCube(halfSize);
glPopMatrix();
}
}
for (uint16_t i = 0; i < numberOfModels; i++) {
const ModelItem& modelItem = modelItems[i];
// render modelItem aspoints
glm::vec3 position = modelItem.getPosition() * (float)TREE_SCALE;
glColor3ub(modelItem.getColor()[RED_INDEX],modelItem.getColor()[GREEN_INDEX],modelItem.getColor()[BLUE_INDEX]);
float radius = modelItem.getRadius() * (float)TREE_SCALE;
//glm::vec3 center = position + glm::vec3(radius, radius, radius); // center it around the position
AABox modelBox = modelItem.getAABox();
modelBox.scale(TREE_SCALE);
if (args->_viewFrustum->boxInFrustum(modelBox) != ViewFrustum::OUTSIDE) {
glm::vec3 position = modelItem.getPosition() * (float)TREE_SCALE;
float radius = modelItem.getRadius() * (float)TREE_SCALE;
float size = modelItem.getSize() * (float)TREE_SCALE;
bool drawAsModel = modelItem.hasModel();
bool drawAsModel = modelItem.hasModel();
args->_renderedItems++;
args->_renderedItems++;
if (drawAsModel) {
glPushMatrix();
const float alpha = 1.0f;
if (drawAsModel) {
glPushMatrix();
const float alpha = 1.0f;
Model* model = getModel(modelItem.getModelURL());
Model* model = getModel(modelItem.getModelURL());
model->setScaleToFit(true, radius * 2.0f);
model->setSnapModelToCenter(true);
model->setScaleToFit(true, radius * 2.0f);
model->setSnapModelToCenter(true);
// set the rotation
glm::quat rotation = modelItem.getModelRotation();
model->setRotation(rotation);
// set the rotation
glm::quat rotation = modelItem.getModelRotation();
model->setRotation(rotation);
// set the position
model->setTranslation(position);
// set the position
model->setTranslation(position);
model->simulate(0.0f);
model->simulate(0.0f);
// TODO: should we allow modelItems to have alpha on their models?
Model::RenderMode modelRenderMode = args->_renderMode == OctreeRenderer::SHADOW_RENDER_MODE
? Model::SHADOW_RENDER_MODE : Model::DEFAULT_RENDER_MODE;
model->render(alpha, modelRenderMode);
if (!isShadowMode && displayModelBounds) {
glColor3f(0.0f, 1.0f, 0.0f);
glPushMatrix();
glTranslatef(position.x, position.y, position.z);
glutWireCube(size);
glPopMatrix();
}
model->render(alpha); // TODO: should we allow modelItems to have alpha on their models?
const bool wantDebugSphere = false;
if (wantDebugSphere) {
glPushMatrix();
glTranslatef(position.x, position.y, position.z);
glutWireSphere(radius, 15, 15);
glPopMatrix();
}
glPopMatrix();
} else {
glPushMatrix();
glTranslatef(position.x, position.y, position.z);
glutSolidSphere(radius, 15, 15);
glPopMatrix();
glPopMatrix();
} else {
glColor3ub(modelItem.getColor()[RED_INDEX],modelItem.getColor()[GREEN_INDEX],modelItem.getColor()[BLUE_INDEX]);
glPushMatrix();
glTranslatef(position.x, position.y, position.z);
glutSolidSphere(radius, 15, 15);
glPopMatrix();
}
}
}
}

View file

@ -46,7 +46,7 @@ public:
void processEraseMessage(const QByteArray& dataByteArray, const SharedNodePointer& sourceNode);
virtual void init();
virtual void render();
virtual void render(RenderMode renderMode = DEFAULT_RENDER_MODE);
protected:
Model* getModel(const QString& url);

View file

@ -39,8 +39,8 @@ void ParticleTreeRenderer::update() {
}
}
void ParticleTreeRenderer::render() {
OctreeRenderer::render();
void ParticleTreeRenderer::render(RenderMode renderMode) {
OctreeRenderer::render(renderMode);
}
Model* ParticleTreeRenderer::getModel(const QString& url) {
@ -102,7 +102,11 @@ void ParticleTreeRenderer::renderElement(OctreeElement* element, RenderArgs* arg
model->setScale(scale * MODEL_SCALE * radius * modelScale);
model->simulate(0.0f);
model->render(alpha); // TODO: should we allow particles to have alpha on their models?
// TODO: should we allow particles to have alpha on their models?
Model::RenderMode modelRenderMode = args->_renderMode == OctreeRenderer::SHADOW_RENDER_MODE
? Model::SHADOW_RENDER_MODE : Model::DEFAULT_RENDER_MODE;
model->render(alpha, modelRenderMode);
const bool wantDebugSphere = false;
if (wantDebugSphere) {

View file

@ -43,7 +43,7 @@ public:
void processEraseMessage(const QByteArray& dataByteArray, const SharedNodePointer& sourceNode);
virtual void init();
virtual void render();
virtual void render(RenderMode renderMode = DEFAULT_RENDER_MODE);
protected:
Model* getModel(const QString& url);

View file

@ -789,9 +789,9 @@ bool Model::findSphereCollisions(const glm::vec3& sphereCenter, float sphereRadi
}
if (ShapeCollider::collideShapes(&sphere, _jointShapes[i], collisions)) {
CollisionInfo* collision = collisions.getLastCollision();
collision->_type = MODEL_COLLISION;
collision->_type = COLLISION_TYPE_MODEL;
collision->_data = (void*)(this);
collision->_flags = i;
collision->_intData = i;
collided = true;
}
outerContinue: ;
@ -805,9 +805,9 @@ bool Model::findPlaneCollisions(const glm::vec4& plane, CollisionList& collision
for (int i = 0; i < _jointShapes.size(); i++) {
if (ShapeCollider::collideShapes(&planeShape, _jointShapes[i], collisions)) {
CollisionInfo* collision = collisions.getLastCollision();
collision->_type = MODEL_COLLISION;
collision->_type = COLLISION_TYPE_MODEL;
collision->_data = (void*)(this);
collision->_flags = i;
collision->_intData = i;
collided = true;
}
}
@ -1256,15 +1256,15 @@ void Model::renderBoundingCollisionShapes(float alpha) {
}
bool Model::collisionHitsMoveableJoint(CollisionInfo& collision) const {
if (collision._type == MODEL_COLLISION) {
if (collision._type == COLLISION_TYPE_MODEL) {
// the joint is pokable by a collision if it exists and is free to move
const FBXJoint& joint = _geometry->getFBXGeometry().joints[collision._flags];
const FBXJoint& joint = _geometry->getFBXGeometry().joints[collision._intData];
if (joint.parentIndex == -1 || _jointStates.isEmpty()) {
return false;
}
// an empty freeLineage means the joint can't move
const FBXGeometry& geometry = _geometry->getFBXGeometry();
int jointIndex = collision._flags;
int jointIndex = collision._intData;
const QVector<int>& freeLineage = geometry.joints.at(jointIndex).freeLineage;
return !freeLineage.isEmpty();
}
@ -1272,12 +1272,12 @@ bool Model::collisionHitsMoveableJoint(CollisionInfo& collision) const {
}
void Model::applyCollision(CollisionInfo& collision) {
if (collision._type != MODEL_COLLISION) {
if (collision._type != COLLISION_TYPE_MODEL) {
return;
}
glm::vec3 jointPosition(0.f);
int jointIndex = collision._flags;
int jointIndex = collision._intData;
if (getJointPosition(jointIndex, jointPosition)) {
const FBXJoint& joint = _geometry->getFBXGeometry().joints[jointIndex];
if (joint.parentIndex != -1) {

View file

@ -76,27 +76,28 @@ void AttachmentsDialog::addAttachment(const AttachmentData& data) {
_attachments->insertWidget(_attachments->count() - 1, new AttachmentPanel(this, data));
}
static QDoubleSpinBox* createTranslationBox(AttachmentsDialog* dialog, float value) {
static QDoubleSpinBox* createTranslationBox(AttachmentPanel* panel, float value) {
QDoubleSpinBox* box = new QDoubleSpinBox();
box->setSingleStep(0.01);
box->setMinimum(-FLT_MAX);
box->setMaximum(FLT_MAX);
box->setValue(value);
dialog->connect(box, SIGNAL(valueChanged(double)), SLOT(updateAttachmentData()));
panel->connect(box, SIGNAL(valueChanged(double)), SLOT(updateAttachmentData()));
return box;
}
static QDoubleSpinBox* createRotationBox(AttachmentsDialog* dialog, float value) {
static QDoubleSpinBox* createRotationBox(AttachmentPanel* panel, float value) {
QDoubleSpinBox* box = new QDoubleSpinBox();
box->setMinimum(-180.0);
box->setMaximum(180.0);
box->setWrapping(true);
box->setValue(value);
dialog->connect(box, SIGNAL(valueChanged(double)), SLOT(updateAttachmentData()));
panel->connect(box, SIGNAL(valueChanged(double)), SLOT(updateAttachmentData()));
return box;
}
AttachmentPanel::AttachmentPanel(AttachmentsDialog* dialog, const AttachmentData& data) {
AttachmentPanel::AttachmentPanel(AttachmentsDialog* dialog, const AttachmentData& data) :
_dialog(dialog) {
setFrameStyle(QFrame::StyledPanel);
QFormLayout* layout = new QFormLayout();
@ -107,7 +108,7 @@ AttachmentPanel::AttachmentPanel(AttachmentsDialog* dialog, const AttachmentData
layout->addRow("Model URL:", urlBox);
urlBox->addWidget(_modelURL = new QLineEdit(data.modelURL.toString()), 1);
_modelURL->setText(data.modelURL.toString());
dialog->connect(_modelURL, SIGNAL(returnPressed()), SLOT(updateAttachmentData()));
connect(_modelURL, SIGNAL(returnPressed()), SLOT(modelURLChanged()));
QPushButton* chooseURL = new QPushButton("Choose");
urlBox->addWidget(chooseURL);
connect(chooseURL, SIGNAL(clicked(bool)), SLOT(chooseModelURL()));
@ -120,26 +121,26 @@ AttachmentPanel::AttachmentPanel(AttachmentsDialog* dialog, const AttachmentData
}
}
_jointName->setCurrentText(data.jointName);
dialog->connect(_jointName, SIGNAL(currentIndexChanged(int)), SLOT(updateAttachmentData()));
connect(_jointName, SIGNAL(currentIndexChanged(int)), SLOT(updateAttachmentData()));
QHBoxLayout* translationBox = new QHBoxLayout();
translationBox->addWidget(_translationX = createTranslationBox(dialog, data.translation.x));
translationBox->addWidget(_translationY = createTranslationBox(dialog, data.translation.y));
translationBox->addWidget(_translationZ = createTranslationBox(dialog, data.translation.z));
translationBox->addWidget(_translationX = createTranslationBox(this, data.translation.x));
translationBox->addWidget(_translationY = createTranslationBox(this, data.translation.y));
translationBox->addWidget(_translationZ = createTranslationBox(this, data.translation.z));
layout->addRow("Translation:", translationBox);
QHBoxLayout* rotationBox = new QHBoxLayout();
glm::vec3 eulers = glm::degrees(safeEulerAngles(data.rotation));
rotationBox->addWidget(_rotationX = createRotationBox(dialog, eulers.x));
rotationBox->addWidget(_rotationY = createRotationBox(dialog, eulers.y));
rotationBox->addWidget(_rotationZ = createRotationBox(dialog, eulers.z));
rotationBox->addWidget(_rotationX = createRotationBox(this, eulers.x));
rotationBox->addWidget(_rotationY = createRotationBox(this, eulers.y));
rotationBox->addWidget(_rotationZ = createRotationBox(this, eulers.z));
layout->addRow("Rotation:", rotationBox);
layout->addRow("Scale:", _scale = new QDoubleSpinBox());
_scale->setSingleStep(0.01);
_scale->setMaximum(FLT_MAX);
_scale->setValue(data.scale);
dialog->connect(_scale, SIGNAL(valueChanged(double)), SLOT(updateAttachmentData()));
connect(_scale, SIGNAL(valueChanged(double)), SLOT(updateAttachmentData()));
QPushButton* remove = new QPushButton("Delete");
layout->addRow(remove);
@ -165,5 +166,30 @@ void AttachmentPanel::chooseModelURL() {
void AttachmentPanel::setModelURL(const QString& url) {
_modelURL->setText(url);
emit _modelURL->returnPressed();
modelURLChanged();
}
void AttachmentPanel::modelURLChanged() {
// check for saved attachment data
AttachmentData attachment = Application::getInstance()->getAvatar()->loadAttachmentData(_modelURL->text());
if (!attachment.jointName.isEmpty()) {
_jointName->setCurrentText(attachment.jointName);
_translationX->setValue(attachment.translation.x);
_translationY->setValue(attachment.translation.y);
_translationZ->setValue(attachment.translation.z);
glm::vec3 eulers = glm::degrees(safeEulerAngles(attachment.rotation));
_rotationX->setValue(eulers.x);
_rotationY->setValue(eulers.y);
_rotationZ->setValue(eulers.z);
_scale->setValue(attachment.scale);
}
_dialog->updateAttachmentData();
}
void AttachmentPanel::updateAttachmentData() {
// save the attachment data under the model URL (if any)
if (!_modelURL->text().isEmpty()) {
Application::getInstance()->getAvatar()->saveAttachmentData(getAttachmentData());
}
_dialog->updateAttachmentData();
}

View file

@ -60,9 +60,12 @@ private slots:
void chooseModelURL();
void setModelURL(const QString& url);
void modelURLChanged();
void updateAttachmentData();
private:
AttachmentsDialog* _dialog;
QLineEdit* _modelURL;
QComboBox* _jointName;
QDoubleSpinBox* _translationX;

View file

@ -1711,38 +1711,6 @@ bool VoxelSystem::inspectForExteriorOcclusionsOperation(OctreeElement* element,
return true;
}
void VoxelSystem::cullSharedFaces() {
if (Menu::getInstance()->isOptionChecked(MenuOption::CullSharedFaces)) {
_useVoxelShader = false;
_usePrimitiveRenderer = true;
inspectForOcclusions();
} else {
_usePrimitiveRenderer = false;
clearAllNodesBufferIndex();
}
_writeRenderFullVBO = true;
_tree->setDirtyBit();
setupNewVoxelsForDrawing();
}
void VoxelSystem::showCulledSharedFaces() {
_tree->lockForRead();
if (Menu::getInstance()->isOptionChecked(MenuOption::ShowCulledSharedFaces)) {
_showCulledSharedFaces = true;
} else {
_showCulledSharedFaces = false;
}
_tree->unlock();
if (Menu::getInstance()->isOptionChecked(MenuOption::CullSharedFaces)) {
_writeRenderFullVBO = true;
_tree->setDirtyBit();
setupNewVoxelsForDrawing();
}
}
void VoxelSystem::inspectForOcclusions() {
if (_inOcclusions) {

View file

@ -95,9 +95,7 @@ public slots:
// Methods that recurse tree
void forceRedrawEntireTree();
void clearAllNodesBufferIndex();
void cullSharedFaces();
void showCulledSharedFaces();
void setDisableFastVoxelPipeline(bool disableFastVoxelPipeline);
void setUseVoxelShader(bool useVoxelShader);
void setVoxelsAsPoints(bool voxelsAsPoints);

View file

@ -21,9 +21,19 @@
#include "AudioInjector.h"
AudioInjector::AudioInjector(QObject* parent) :
QObject(parent),
_sound(NULL),
_options(),
_shouldStop(false)
{
}
AudioInjector::AudioInjector(Sound* sound, const AudioInjectorOptions& injectorOptions) :
_sound(sound),
_options(injectorOptions)
_options(injectorOptions),
_shouldStop(false)
{
}
@ -80,7 +90,7 @@ void AudioInjector::injectAudio() {
int numPreAudioDataBytes = injectAudioPacket.size();
// loop to send off our audio in NETWORK_BUFFER_LENGTH_SAMPLES_PER_CHANNEL byte chunks
while (currentSendPosition < soundByteArray.size()) {
while (currentSendPosition < soundByteArray.size() && !_shouldStop) {
int bytesToCopy = std::min(NETWORK_BUFFER_LENGTH_BYTES_PER_CHANNEL,
soundByteArray.size() - currentSendPosition);

View file

@ -24,14 +24,19 @@
class AudioInjector : public QObject {
Q_OBJECT
public:
AudioInjector(QObject* parent);
AudioInjector(Sound* sound, const AudioInjectorOptions& injectorOptions);
public slots:
void injectAudio();
void stop() { _shouldStop = true; }
signals:
void finished();
private:
Sound* _sound;
AudioInjectorOptions _options;
public slots:
void injectAudio();
signals:
void finished();
bool _shouldStop;
};
Q_DECLARE_METATYPE(AudioInjector*)
#endif // hifi_AudioInjector_h

View file

@ -11,7 +11,7 @@
#include "AudioScriptingInterface.h"
void AudioScriptingInterface::playSound(Sound* sound, const AudioInjectorOptions* injectorOptions) {
AudioInjector* AudioScriptingInterface::playSound(Sound* sound, const AudioInjectorOptions* injectorOptions) {
AudioInjector* injector = new AudioInjector(sound, *injectorOptions);
@ -28,6 +28,18 @@ void AudioScriptingInterface::playSound(Sound* sound, const AudioInjectorOptions
connect(injectorThread, SIGNAL(finished()), injectorThread, SLOT(deleteLater()));
injectorThread->start();
return injector;
}
void AudioScriptingInterface::stopInjector(AudioInjector* injector) {
if (injector) {
injector->stop();
}
}
bool AudioScriptingInterface::isInjectorPlaying(AudioInjector* injector) {
return (injector != NULL);
}
void AudioScriptingInterface::startDrumSound(float volume, float frequency, float duration, float decay,

View file

@ -20,7 +20,9 @@ const AudioInjectorOptions DEFAULT_INJECTOR_OPTIONS;
class AudioScriptingInterface : public QObject {
Q_OBJECT
public slots:
static void playSound(Sound* sound, const AudioInjectorOptions* injectorOptions = NULL);
static AudioInjector* playSound(Sound* sound, const AudioInjectorOptions* injectorOptions = NULL);
static void stopInjector(AudioInjector* injector);
static bool isInjectorPlaying(AudioInjector* injector);
static void startDrumSound(float volume, float frequency, float duration, float decay,
const AudioInjectorOptions* injectorOptions = NULL);

View file

@ -89,7 +89,7 @@ void PositionalAudioRingBuffer::updateNextOutputTrailingLoudness() {
const int TRAILING_AVERAGE_FRAMES = 100;
const float CURRENT_FRAME_RATIO = 1.0f / TRAILING_AVERAGE_FRAMES;
const float PREVIOUS_FRAMES_RATIO = 1.0f - CURRENT_FRAME_RATIO;
const float LOUDNESS_EPSILON = 0.01f;
const float LOUDNESS_EPSILON = 0.000001f;
if (nextLoudness >= _nextOutputTrailingLoudness) {
_nextOutputTrailingLoudness = nextLoudness;

View file

@ -23,6 +23,7 @@
#include <NodeList.h>
#include <PacketHeaders.h>
#include <SharedUtil.h>
#include <StreamUtils.h>
#include <UUID.h>
#include <VoxelConstants.h>
@ -660,16 +661,87 @@ void AvatarData::setSkeletonModelURL(const QUrl& skeletonModelURL) {
updateJointMappings();
}
void AvatarData::setAttachmentData(const QVector<AttachmentData>& attachmentData) {
_attachmentData = attachmentData;
}
void AvatarData::setDisplayName(const QString& displayName) {
_displayName = displayName;
qDebug() << "Changing display name for avatar to" << displayName;
}
QVector<AttachmentData> AvatarData::getAttachmentData() const {
if (QThread::currentThread() != thread()) {
QVector<AttachmentData> result;
QMetaObject::invokeMethod(const_cast<AvatarData*>(this), "getAttachmentData", Qt::BlockingQueuedConnection,
Q_RETURN_ARG(QVector<AttachmentData>, result));
return result;
}
return _attachmentData;
}
void AvatarData::setAttachmentData(const QVector<AttachmentData>& attachmentData) {
if (QThread::currentThread() != thread()) {
QMetaObject::invokeMethod(this, "setAttachmentData", Q_ARG(const QVector<AttachmentData>&, attachmentData));
return;
}
_attachmentData = attachmentData;
}
void AvatarData::attach(const QString& modelURL, const QString& jointName, const glm::vec3& translation,
const glm::quat& rotation, float scale, bool allowDuplicates, bool useSaved) {
if (QThread::currentThread() != thread()) {
QMetaObject::invokeMethod(this, "attach", Q_ARG(const QString&, modelURL), Q_ARG(const QString&, jointName),
Q_ARG(const glm::vec3&, translation), Q_ARG(const glm::quat&, rotation),
Q_ARG(float, scale), Q_ARG(bool, allowDuplicates), Q_ARG(bool, useSaved));
return;
}
QVector<AttachmentData> attachmentData = getAttachmentData();
if (!allowDuplicates) {
foreach (const AttachmentData& data, attachmentData) {
if (data.modelURL == modelURL && (jointName.isEmpty() || data.jointName == jointName)) {
return;
}
}
}
AttachmentData data;
data.modelURL = modelURL;
data.jointName = jointName;
data.translation = translation;
data.rotation = rotation;
data.scale = scale;
attachmentData.append(data);
setAttachmentData(attachmentData);
}
void AvatarData::detachOne(const QString& modelURL, const QString& jointName) {
if (QThread::currentThread() != thread()) {
QMetaObject::invokeMethod(this, "detachOne", Q_ARG(const QString&, modelURL), Q_ARG(const QString&, jointName));
return;
}
QVector<AttachmentData> attachmentData = getAttachmentData();
for (QVector<AttachmentData>::iterator it = attachmentData.begin(); it != attachmentData.end(); it++) {
if (it->modelURL == modelURL && (jointName.isEmpty() || it->jointName == jointName)) {
attachmentData.erase(it);
setAttachmentData(attachmentData);
return;
}
}
}
void AvatarData::detachAll(const QString& modelURL, const QString& jointName) {
if (QThread::currentThread() != thread()) {
QMetaObject::invokeMethod(this, "detachAll", Q_ARG(const QString&, modelURL), Q_ARG(const QString&, jointName));
return;
}
QVector<AttachmentData> attachmentData = getAttachmentData();
for (QVector<AttachmentData>::iterator it = attachmentData.begin(); it != attachmentData.end(); ) {
if (it->modelURL == modelURL && (jointName.isEmpty() || it->jointName == jointName)) {
it = attachmentData.erase(it);
} else {
it++;
}
}
setAttachmentData(attachmentData);
}
void AvatarData::setBillboard(const QByteArray& billboard) {
_billboard = billboard;
@ -792,3 +864,59 @@ QDataStream& operator>>(QDataStream& in, AttachmentData& attachment) {
attachment.translation >> attachment.rotation >> attachment.scale;
}
void AttachmentDataObject::setModelURL(const QString& modelURL) const {
AttachmentData data = qscriptvalue_cast<AttachmentData>(thisObject());
data.modelURL = modelURL;
thisObject() = engine()->toScriptValue(data);
}
QString AttachmentDataObject::getModelURL() const {
return qscriptvalue_cast<AttachmentData>(thisObject()).modelURL.toString();
}
void AttachmentDataObject::setJointName(const QString& jointName) const {
AttachmentData data = qscriptvalue_cast<AttachmentData>(thisObject());
data.jointName = jointName;
thisObject() = engine()->toScriptValue(data);
}
QString AttachmentDataObject::getJointName() const {
return qscriptvalue_cast<AttachmentData>(thisObject()).jointName;
}
void AttachmentDataObject::setTranslation(const glm::vec3& translation) const {
AttachmentData data = qscriptvalue_cast<AttachmentData>(thisObject());
data.translation = translation;
thisObject() = engine()->toScriptValue(data);
}
glm::vec3 AttachmentDataObject::getTranslation() const {
return qscriptvalue_cast<AttachmentData>(thisObject()).translation;
}
void AttachmentDataObject::setRotation(const glm::quat& rotation) const {
AttachmentData data = qscriptvalue_cast<AttachmentData>(thisObject());
data.rotation = rotation;
thisObject() = engine()->toScriptValue(data);
}
glm::quat AttachmentDataObject::getRotation() const {
return qscriptvalue_cast<AttachmentData>(thisObject()).rotation;
}
void AttachmentDataObject::setScale(float scale) const {
AttachmentData data = qscriptvalue_cast<AttachmentData>(thisObject());
data.scale = scale;
thisObject() = engine()->toScriptValue(data);
}
float AttachmentDataObject::getScale() const {
return qscriptvalue_cast<AttachmentData>(thisObject()).scale;
}
void registerAvatarTypes(QScriptEngine* engine) {
qScriptRegisterSequenceMetaType<QVector<AttachmentData> >(engine);
engine->setDefaultPrototype(qMetaTypeId<AttachmentData>(), engine->newQObject(
new AttachmentDataObject(), QScriptEngine::ScriptOwnership));
}

View file

@ -41,11 +41,11 @@ typedef unsigned long long quint64;
#include <QtCore/QVector>
#include <QtCore/QVariantMap>
#include <QRect>
#include <QScriptable>
#include <QUuid>
#include <CollisionInfo.h>
#include <RegisteredMetaTypes.h>
#include <StreamUtils.h>
#include <Node.h>
@ -124,6 +124,7 @@ class AvatarData : public QObject {
Q_PROPERTY(QString displayName READ getDisplayName WRITE setDisplayName)
Q_PROPERTY(QString faceModelURL READ getFaceModelURLFromScript WRITE setFaceModelURLFromScript)
Q_PROPERTY(QString skeletonModelURL READ getSkeletonModelURLFromScript WRITE setSkeletonModelURLFromScript)
Q_PROPERTY(QVector<AttachmentData> attachmentData READ getAttachmentData WRITE setAttachmentData)
Q_PROPERTY(QString billboardURL READ getBillboardURL WRITE setBillboardFromURL)
Q_PROPERTY(QStringList jointNames READ getJointNames)
@ -230,13 +231,22 @@ public:
const QUrl& getFaceModelURL() const { return _faceModelURL; }
QString getFaceModelURLString() const { return _faceModelURL.toString(); }
const QUrl& getSkeletonModelURL() const { return _skeletonModelURL; }
const QVector<AttachmentData>& getAttachmentData() const { return _attachmentData; }
const QString& getDisplayName() const { return _displayName; }
virtual void setFaceModelURL(const QUrl& faceModelURL);
virtual void setSkeletonModelURL(const QUrl& skeletonModelURL);
virtual void setAttachmentData(const QVector<AttachmentData>& attachmentData);
virtual void setDisplayName(const QString& displayName);
Q_INVOKABLE QVector<AttachmentData> getAttachmentData() const;
Q_INVOKABLE virtual void setAttachmentData(const QVector<AttachmentData>& attachmentData);
Q_INVOKABLE virtual void attach(const QString& modelURL, const QString& jointName = QString(),
const glm::vec3& translation = glm::vec3(), const glm::quat& rotation = glm::quat(), float scale = 1.0f,
bool allowDuplicates = false, bool useSaved = true);
Q_INVOKABLE void detachOne(const QString& modelURL, const QString& jointName = QString());
Q_INVOKABLE void detachAll(const QString& modelURL, const QString& jointName = QString());
virtual void setBillboard(const QByteArray& billboard);
const QByteArray& getBillboard() const { return _billboard; }
@ -348,4 +358,36 @@ public:
QDataStream& operator<<(QDataStream& out, const AttachmentData& attachment);
QDataStream& operator>>(QDataStream& in, AttachmentData& attachment);
Q_DECLARE_METATYPE(AttachmentData)
Q_DECLARE_METATYPE(QVector<AttachmentData>)
/// Scriptable wrapper for attachments.
class AttachmentDataObject : public QObject, protected QScriptable {
Q_OBJECT
Q_PROPERTY(QString modelURL READ getModelURL WRITE setModelURL)
Q_PROPERTY(QString jointName READ getJointName WRITE setJointName)
Q_PROPERTY(glm::vec3 translation READ getTranslation WRITE setTranslation)
Q_PROPERTY(glm::quat rotation READ getRotation WRITE setRotation)
Q_PROPERTY(float scale READ getScale WRITE setScale)
public:
Q_INVOKABLE void setModelURL(const QString& modelURL) const;
Q_INVOKABLE QString getModelURL() const;
Q_INVOKABLE void setJointName(const QString& jointName) const;
Q_INVOKABLE QString getJointName() const;
Q_INVOKABLE void setTranslation(const glm::vec3& translation) const;
Q_INVOKABLE glm::vec3 getTranslation() const;
Q_INVOKABLE void setRotation(const glm::quat& rotation) const;
Q_INVOKABLE glm::quat getRotation() const;
Q_INVOKABLE void setScale(float scale) const;
Q_INVOKABLE float getScale() const;
};
void registerAvatarTypes(QScriptEngine* engine);
#endif // hifi_AvatarData_h

View file

@ -826,7 +826,7 @@ ExtractedMesh extractMesh(const FBXNode& object) {
while (endIndex < data.polygonIndices.size() && data.polygonIndices.at(endIndex++) >= 0);
QPair<int, int> materialTexture((polygonIndex < materials.size()) ? materials.at(polygonIndex) : 0,
(polygonIndex < textures.size()) ? textures.at(polygonIndex) : -1);
(polygonIndex < textures.size()) ? textures.at(polygonIndex) : 0);
int& partIndex = materialTextureParts[materialTexture];
if (partIndex == 0) {
data.extracted.partMaterialTextures.append(materialTexture);
@ -972,6 +972,18 @@ FBXTexture getTexture(const QString& textureID, const QHash<QString, QByteArray>
return texture;
}
bool checkMaterialsHaveTextures(const QHash<QString, Material>& materials,
const QHash<QString, QByteArray>& textureFilenames, const QMultiHash<QString, QString>& childMap) {
foreach (const QString& materialID, materials.keys()) {
foreach (const QString& childID, childMap.values(materialID)) {
if (textureFilenames.contains(childID)) {
return true;
}
}
}
return false;
}
FBXGeometry extractFBXGeometry(const FBXNode& node, const QVariantHash& mapping) {
QHash<QString, ExtractedMesh> meshes;
QVector<ExtractedBlendshape> blendshapes;
@ -1515,6 +1527,9 @@ FBXGeometry extractFBXGeometry(const FBXNode& node, const QVariantHash& mapping)
geometry.bindExtents.reset();
geometry.meshExtents.reset();
// see if any materials have texture children
bool materialsHaveTextures = checkMaterialsHaveTextures(materials, textureFilenames, childMap);
for (QHash<QString, ExtractedMesh>::iterator it = meshes.begin(); it != meshes.end(); it++) {
ExtractedMesh& extracted = it.value();
@ -1587,7 +1602,8 @@ FBXGeometry extractFBXGeometry(const FBXNode& node, const QVariantHash& mapping)
} else if (textureFilenames.contains(childID)) {
FBXTexture texture = getTexture(childID, textureFilenames, textureContent);
for (int j = 0; j < extracted.partMaterialTextures.size(); j++) {
if (extracted.partMaterialTextures.at(j).second == textureIndex) {
int partTexture = extracted.partMaterialTextures.at(j).second;
if (partTexture == textureIndex && !(partTexture == 0 && materialsHaveTextures)) {
extracted.mesh.parts[j].diffuseTexture = texture;
}
}

View file

@ -85,6 +85,9 @@ public:
/// used by ModelScriptingInterface to return ModelItemProperties for unknown models
void setIsUnknownID() { _id = UNKNOWN_MODEL_ID; _idSet = true; }
glm::vec3 getMinimumPoint() const { return _position - glm::vec3(_radius, _radius, _radius); }
glm::vec3 getMaximumPoint() const { return _position + glm::vec3(_radius, _radius, _radius); }
private:
glm::vec3 _position;
@ -156,11 +159,20 @@ public:
/// get position in domain scale units (0.0 - 1.0)
const glm::vec3& getPosition() const { return _position; }
glm::vec3 getMinimumPoint() const { return _position - glm::vec3(_radius, _radius, _radius); }
glm::vec3 getMaximumPoint() const { return _position + glm::vec3(_radius, _radius, _radius); }
const rgbColor& getColor() const { return _color; }
xColor getXColor() const { xColor color = { _color[RED_INDEX], _color[GREEN_INDEX], _color[BLUE_INDEX] }; return color; }
/// get radius in domain scale units (0.0 - 1.0)
float getRadius() const { return _radius; }
/// get maximum dimension in domain scale units (0.0 - 1.0)
float getSize() const { return _radius * 2.0f; }
/// get maximum dimension in domain scale units (0.0 - 1.0)
AABox getAABox() const { return AABox(getMinimumPoint(), getSize()); }
// model related properties
bool hasModel() const { return !_modelURL.isEmpty(); }

View file

@ -113,13 +113,10 @@ void ModelTree::storeModel(const ModelItem& model, const SharedNodePointer& send
FindAndUpdateModelOperator theOperator(model);
recurseTreeWithOperator(&theOperator);
// if we didn't find it in the tree, then store it...
if (!theOperator.wasFound()) {
glm::vec3 position = model.getPosition();
float size = std::max(MINIMUM_MODEL_ELEMENT_SIZE, model.getRadius());
ModelTreeElement* element = (ModelTreeElement*)getOrCreateChildElementAt(position.x, position.y, position.z, size);
AABox modelBox = model.getAABox();
ModelTreeElement* element = (ModelTreeElement*)getOrCreateChildElementContaining(model.getAABox());
element->storeModel(model);
}
// what else do we need to do here to get reaveraging to work
@ -494,12 +491,29 @@ void ModelTree::update() {
lockForWrite();
_isDirty = true;
// TODO: we don't need to update models yet, but when we do, for example
// when we add animation support, we will revisit this code.
//ModelTreeUpdateArgs args = { };
//recurseTreeWithOperation(updateOperation, &args);
ModelTreeUpdateArgs args = { };
recurseTreeWithOperation(updateOperation, &args);
// Now is a reasonable time to prune the tree...
// now add back any of the particles that moved elements....
int movingModels = args._movingModels.size();
for (int i = 0; i < movingModels; i++) {
bool shouldDie = args._movingModels[i].getShouldDie();
// if the particle is still inside our total bounds, then re-add it
AABox treeBounds = getRoot()->getAABox();
if (!shouldDie && treeBounds.contains(args._movingModels[i].getPosition())) {
storeModel(args._movingModels[i]);
} else {
uint32_t modelItemID = args._movingModels[i].getID();
quint64 deletedAt = usecTimestampNow();
_recentlyDeletedModelsLock.lockForWrite();
_recentlyDeletedModelItemIDs.insert(deletedAt, modelItemID);
_recentlyDeletedModelsLock.unlock();
}
}
// prune the tree...
recurseTreeWithOperation(pruneOperation, NULL);
unlock();
}

View file

@ -47,15 +47,32 @@ ModelTreeElement* ModelTreeElement::addChildAtIndex(int index) {
}
bool ModelTreeElement::appendElementData(OctreePacketData* packetData) const {
bool ModelTreeElement::appendElementData(OctreePacketData* packetData, EncodeBitstreamParams& params) const {
bool success = true; // assume the best...
// write our models out...
uint16_t numberOfModels = _modelItems->size();
// write our models out... first determine which of the models are in view based on our params
uint16_t numberOfModels = 0;
QVector<uint16_t> indexesOfModelsToInclude;
for (uint16_t i = 0; i < _modelItems->size(); i++) {
if (params.viewFrustum) {
const ModelItem& model = (*_modelItems)[i];
AABox modelBox = model.getAABox();
modelBox.scale(TREE_SCALE);
if (params.viewFrustum->boxInFrustum(modelBox) != ViewFrustum::OUTSIDE) {
indexesOfModelsToInclude << i;
numberOfModels++;
}
} else {
indexesOfModelsToInclude << i;
numberOfModels++;
}
}
success = packetData->appendValue(numberOfModels);
if (success) {
for (uint16_t i = 0; i < numberOfModels; i++) {
foreach (uint16_t i, indexesOfModelsToInclude) {
const ModelItem& model = (*_modelItems)[i];
success = model.appendModelData(packetData);
if (!success) {
@ -66,10 +83,25 @@ bool ModelTreeElement::appendElementData(OctreePacketData* packetData) const {
return success;
}
void ModelTreeElement::update(ModelTreeUpdateArgs& args) {
markWithChangedTime();
// TODO: early exit when _modelItems is empty
bool ModelTreeElement::containsModelBounds(const ModelItem& model) const {
return _box.contains(model.getMinimumPoint()) && _box.contains(model.getMaximumPoint());
}
bool ModelTreeElement::bestFitModelBounds(const ModelItem& model) const {
if (_box.contains(model.getMinimumPoint()) && _box.contains(model.getMaximumPoint())) {
int childForMinimumPoint = getMyChildContainingPoint(model.getMinimumPoint());
int childForMaximumPoint = getMyChildContainingPoint(model.getMaximumPoint());
// If I contain both the minimum and maximum point, but two different children of mine
// contain those points, then I am the best fit for that model
if (childForMinimumPoint != childForMaximumPoint) {
return true;
}
}
return false;
}
void ModelTreeElement::update(ModelTreeUpdateArgs& args) {
// update our contained models
QList<ModelItem>::iterator modelItr = _modelItems->begin();
while(modelItr != _modelItems->end()) {
@ -78,19 +110,18 @@ void ModelTreeElement::update(ModelTreeUpdateArgs& args) {
// If the model wants to die, or if it's left our bounding box, then move it
// into the arguments moving models. These will be added back or deleted completely
if (model.getShouldDie() || !_box.contains(model.getPosition())) {
if (model.getShouldDie() || !bestFitModelBounds(model)) {
args._movingModels.push_back(model);
// erase this model
modelItr = _modelItems->erase(modelItr);
// this element has changed so mark it...
markWithChangedTime();
} else {
++modelItr;
}
}
// TODO: if _modelItems is empty after while loop consider freeing memory in _modelItems if
// internal array is too big (QList internal array does not decrease size except in dtor and
// assignment operator). Otherwise _modelItems could become a "resource leak" for large
// roaming piles of models.
}
bool ModelTreeElement::findSpherePenetration(const glm::vec3& center, float radius,
@ -136,6 +167,7 @@ bool ModelTreeElement::updateModel(const ModelItem& model) {
(localOlder ? "OLDER" : "NEWER"),
difference, debug::valueOf(model.isNewlyCreated()) );
}
thisModel.copyChangedProperties(model);
markWithChangedTime();
} else {

View file

@ -74,7 +74,7 @@ public:
virtual bool requiresSplit() const { return false; }
/// Override to serialize the state of this element. This is used for persistance and for transmission across the network.
virtual bool appendElementData(OctreePacketData* packetData) const;
virtual bool appendElementData(OctreePacketData* packetData, EncodeBitstreamParams& params) const;
/// Override to deserialize the state of this element. This is used for loading from a persisted file or from reading
/// from the network.
@ -118,6 +118,9 @@ public:
bool removeModelWithID(uint32_t id);
bool containsModelBounds(const ModelItem& model) const;
bool bestFitModelBounds(const ModelItem& model) const;
protected:
virtual void init(unsigned char * octalCode);

View file

@ -39,7 +39,7 @@ enum PacketType {
PacketTypeRequestAssignment,
PacketTypeCreateAssignment,
PacketTypeDomainOAuthRequest,
PacketTypeDataServerGet, // reusable
PacketTypeMuteEnvironment,
PacketTypeDataServerSend, // reusable
PacketTypeDataServerConfirm,
PacketTypeVoxelQuery,

View file

@ -578,6 +578,10 @@ OctreeElement* Octree::getOrCreateChildElementAt(float x, float y, float z, floa
return getRoot()->getOrCreateChildElementAt(x, y, z, s);
}
OctreeElement* Octree::getOrCreateChildElementContaining(const AABox& box) {
return getRoot()->getOrCreateChildElementContaining(box);
}
// combines the ray cast arguments into a single object
class RayArgs {
@ -1001,7 +1005,7 @@ int Octree::encodeTreeBitstreamRecursion(OctreeElement* element,
// Keep track of how deep we've encoded.
currentEncodeLevel++;
params.maxLevelReached = std::max(currentEncodeLevel,params.maxLevelReached);
params.maxLevelReached = std::max(currentEncodeLevel, params.maxLevelReached);
// If we've reached our max Search Level, then stop searching.
if (currentEncodeLevel >= params.maxEncodeLevel) {
@ -1342,7 +1346,7 @@ int Octree::encodeTreeBitstreamRecursion(OctreeElement* element,
OctreeElement* childElement = element->getChildAtIndex(i);
if (childElement) {
int bytesBeforeChild = packetData->getUncompressedSize();
continueThisLevel = childElement->appendElementData(packetData);
continueThisLevel = childElement->appendElementData(packetData, params);
int bytesAfterChild = packetData->getUncompressedSize();
if (!continueThisLevel) {

View file

@ -227,6 +227,7 @@ public:
OctreeElement* getOctreeEnclosingElementAt(float x, float y, float z, float s) const;
OctreeElement* getOrCreateChildElementAt(float x, float y, float z, float s);
OctreeElement* getOrCreateChildElementContaining(const AABox& box);
void recurseTreeWithOperation(RecurseOctreeOperation operation, void* extraData = NULL);
void recurseTreeWithPostOperation(RecurseOctreeOperation operation, void* extraData = NULL);

View file

@ -1379,3 +1379,90 @@ OctreeElement* OctreeElement::getOrCreateChildElementAt(float x, float y, float
// Now that we have the child to recurse down, let it answer the original question...
return child->getOrCreateChildElementAt(x, y, z, s);
}
OctreeElement* OctreeElement::getOrCreateChildElementContaining(const AABox& box) {
OctreeElement* child = NULL;
float ourScale = getScale();
float boxScale = box.getScale();
if(boxScale > ourScale) {
qDebug("UNEXPECTED -- OctreeElement::getOrCreateChildElementContaining() "
"boxScale=[%f] > ourScale=[%f] ", boxScale, ourScale);
}
// Determine which of our children the minimum and maximum corners of the box live in...
glm::vec3 boxCornerMinimum = box.getCorner();
glm::vec3 boxCornerMaximum = box.calcTopFarLeft();
int childIndexBoxMinimum = getMyChildContainingPoint(boxCornerMinimum);
int childIndexBoxMaximum = getMyChildContainingPoint(boxCornerMaximum);
// If the minimum and maximum corners of the box are in two different children's boxes, then we are the containing element
if (childIndexBoxMinimum != childIndexBoxMaximum) {
return this;
}
// otherwise, they are the same and that child should be considered as the correct element
int childIndex = childIndexBoxMinimum; // both the same...
// Now, check if we have a child at that location
child = getChildAtIndex(childIndex);
if (!child) {
child = addChildAtIndex(childIndex);
}
// Now that we have the child to recurse down, let it answer the original question...
return child->getOrCreateChildElementContaining(box);
}
int OctreeElement::getMyChildContainingPoint(const glm::vec3& point) const {
glm::vec3 ourCenter = _box.calcCenter();
int childIndex = CHILD_UNKNOWN;
// left half
if (point.x > ourCenter.x) {
if (point.y > ourCenter.y) {
// top left
if (point.z > ourCenter.z) {
// top left far
childIndex = CHILD_TOP_LEFT_FAR;
} else {
// top left near
childIndex = CHILD_TOP_LEFT_NEAR;
}
} else {
// bottom left
if (point.z > ourCenter.z) {
// bottom left far
childIndex = CHILD_BOTTOM_LEFT_FAR;
} else {
// bottom left near
childIndex = CHILD_BOTTOM_LEFT_NEAR;
}
}
} else {
// right half
if (point.y > ourCenter.y) {
// top right
if (point.z > ourCenter.z) {
// top right far
childIndex = CHILD_TOP_RIGHT_FAR;
} else {
// top right near
childIndex = CHILD_TOP_RIGHT_NEAR;
}
} else {
// bottom right
if (point.z > ourCenter.z) {
// bottom right far
childIndex = CHILD_BOTTOM_RIGHT_FAR;
} else {
// bottom right near
childIndex = CHILD_BOTTOM_RIGHT_NEAR;
}
}
}
return childIndex;
}

View file

@ -23,14 +23,14 @@
#include "AABox.h"
#include "ViewFrustum.h"
#include "OctreeConstants.h"
//#include "Octree.h"
class EncodeBitstreamParams;
class Octree;
class OctreeElement;
class OctreeElementDeleteHook;
class OctreePacketData;
class VoxelSystem;
class ReadBitstreamToTreeParams;
class VoxelSystem;
// Callers who want delete hook callbacks should implement this class
class OctreeElementDeleteHook {
@ -81,7 +81,7 @@ public:
virtual bool requiresSplit() const { return false; }
/// Override to serialize the state of this element. This is used for persistance and for transmission across the network.
virtual bool appendElementData(OctreePacketData* packetData) const { return true; }
virtual bool appendElementData(OctreePacketData* packetData, EncodeBitstreamParams& params) const { return true; }
/// Override to deserialize the state of this element. This is used for loading from a persisted file or from reading
/// from the network.
@ -217,6 +217,8 @@ public:
OctreeElement* getOrCreateChildElementAt(float x, float y, float z, float s);
OctreeElement* getOrCreateChildElementContaining(const AABox& box);
int getMyChildContainingPoint(const glm::vec3& point) const;
protected:

View file

@ -33,7 +33,7 @@ public:
virtual void renderElement(OctreeElement* element, RenderArgs* args) { /* swallow these */ };
virtual void init();
virtual void render() { /* swallow these */ };
virtual void render(RenderMode renderMode = DEFAULT_RENDER_MODE) { /* swallow these */ };
void setJurisdictionListener(JurisdictionListener* jurisdictionListener) { _jurisdictionListener = jurisdictionListener; }

View file

@ -154,8 +154,8 @@ bool OctreeRenderer::renderOperation(OctreeElement* element, void* extraData) {
return false;
}
void OctreeRenderer::render() {
RenderArgs args = { 0, this, _viewFrustum, getSizeScale(), getBoundaryLevelAdjust() };
void OctreeRenderer::render(RenderMode renderMode) {
RenderArgs args = { 0, this, _viewFrustum, getSizeScale(), getBoundaryLevelAdjust(), renderMode };
if (_tree) {
_tree->lockForRead();
_tree->recurseTreeWithOperation(renderOperation, &args);

View file

@ -25,15 +25,7 @@
#include "ViewFrustum.h"
class OctreeRenderer;
class RenderArgs {
public:
int _renderedItems;
OctreeRenderer* _renderer;
ViewFrustum* _viewFrustum;
float _sizeScale;
int _boundaryLevelAdjust;
};
class RenderArgs;
// Generic client side Octree renderer class.
@ -59,8 +51,10 @@ public:
/// initialize and GPU/rendering related resources
virtual void init();
enum RenderMode { DEFAULT_RENDER_MODE, SHADOW_RENDER_MODE, DIFFUSE_RENDER_MODE, NORMAL_RENDER_MODE };
/// render the content of the octree
virtual void render();
virtual void render(RenderMode renderMode = DEFAULT_RENDER_MODE);
ViewFrustum* getViewFrustum() const { return _viewFrustum; }
void setViewFrustum(ViewFrustum* viewFrustum) { _viewFrustum = viewFrustum; }
@ -75,4 +69,15 @@ protected:
ViewFrustum* _viewFrustum;
};
class RenderArgs {
public:
int _renderedItems;
OctreeRenderer* _renderer;
ViewFrustum* _viewFrustum;
float _sizeScale;
int _boundaryLevelAdjust;
OctreeRenderer::RenderMode _renderMode;
};
#endif // hifi_OctreeRenderer_h

View file

@ -224,7 +224,7 @@ void ParticleCollisionSystem::updateCollisionWithAvatars(Particle* particle) {
// (doing this prevents some "collision snagging" when particle penetrates the object)
// HACK BEGIN: to allow paddle hands to "hold" particles we attenuate soft collisions against them.
if (collision->_type == PADDLE_HAND_COLLISION) {
if (collision->_type == COLLISION_TYPE_PADDLE_HAND) {
// NOTE: the physics are wrong (particles cannot roll) but it IS possible to catch a slow moving particle.
// TODO: make this less hacky when we have more per-collision details
float elasticity = ELASTICITY;

View file

@ -47,7 +47,7 @@ ParticleTreeElement* ParticleTreeElement::addChildAtIndex(int index) {
}
bool ParticleTreeElement::appendElementData(OctreePacketData* packetData) const {
bool ParticleTreeElement::appendElementData(OctreePacketData* packetData, EncodeBitstreamParams& params) const {
bool success = true; // assume the best...
// write our particles out...

View file

@ -76,7 +76,7 @@ public:
virtual bool requiresSplit() const { return false; }
/// Override to serialize the state of this element. This is used for persistance and for transmission across the network.
virtual bool appendElementData(OctreePacketData* packetData) const;
virtual bool appendElementData(OctreePacketData* packetData, EncodeBitstreamParams& params) const;
/// Override to deserialize the state of this element. This is used for loading from a persisted file or from reading
/// from the network.

View file

@ -18,6 +18,7 @@
#include <QtNetwork/QNetworkReply>
#include <QScriptEngine>
#include <AudioInjector.h>
#include <AudioRingBuffer.h>
#include <AvatarData.h>
#include <CollisionInfo.h>
@ -52,6 +53,14 @@ static QScriptValue debugPrint(QScriptContext* context, QScriptEngine* engine){
return QScriptValue();
}
QScriptValue injectorToScriptValue(QScriptEngine *engine, AudioInjector* const &in) {
return engine->newQObject(in);
}
void injectorFromScriptValue(const QScriptValue &object, AudioInjector* &out) {
out = qobject_cast<AudioInjector*>(object.toQObject());
}
ScriptEngine::ScriptEngine(const QString& scriptContents, const QString& fileNameString,
AbstractControllerScriptingInterface* controllerScriptingInterface) :
@ -201,6 +210,7 @@ void ScriptEngine::init() {
registerEventTypes(&_engine);
registerMenuItemProperties(&_engine);
registerAnimationTypes(&_engine);
registerAvatarTypes(&_engine);
qScriptRegisterMetaType(&_engine, ParticlePropertiesToScriptValue, ParticlePropertiesFromScriptValue);
qScriptRegisterMetaType(&_engine, ParticleIDtoScriptValue, ParticleIDfromScriptValue);
@ -226,6 +236,8 @@ void ScriptEngine::init() {
QScriptValue localVoxelsValue = _engine.scriptValueFromQMetaObject<LocalVoxels>();
_engine.globalObject().setProperty("LocalVoxels", localVoxelsValue);
qScriptRegisterMetaType(&_engine, injectorToScriptValue, injectorFromScriptValue);
registerGlobalObject("Script", this);
registerGlobalObject("Audio", &_audioScriptingInterface);

View file

@ -38,19 +38,24 @@ CollisionInfo* CollisionList::getLastCollision() {
}
void CollisionList::clear() {
// we rely on the external context to properly set or clear the data members of a collision
// whenever it is used.
/*
for (int i = 0; i < _size; ++i) {
// we only clear the important stuff
CollisionInfo& collision = _collisions[i];
collision._type = BASE_COLLISION;
collision._data = NULL; // CollisionInfo does not own whatever this points to.
collision._flags = 0;
// we rely on the consumer to properly overwrite these fields when the collision is "created"
collision._type = COLLISION_TYPE_UNKNOWN;
//collision._data = NULL;
//collision._intData = 0;
//collision._floatDAta = 0.0f;
//collision._vecData = glm::vec3(0.0f);
//collision._damping;
//collision._elasticity;
//collision._contactPoint;
//collision._penetration;
//collision._addedVelocity;
}
*/
_size = 0;
}

View file

@ -18,9 +18,14 @@
#include <QVector>
enum CollisionType {
BASE_COLLISION = 0,
PADDLE_HAND_COLLISION,
MODEL_COLLISION,
COLLISION_TYPE_UNKNOWN = 0,
COLLISION_TYPE_PADDLE_HAND,
COLLISION_TYPE_MODEL,
// _data = pointer to Model that owns joint
// _intData = joint index
COLLISION_TYPE_AACUBE,
// _floatData = cube side
// _vecData = cube center
};
const quint32 COLLISION_GROUP_ENVIRONMENT = 1U << 0;
@ -39,7 +44,7 @@ public:
CollisionInfo()
: _type(0),
_data(NULL),
_flags(0),
_intData(0),
_damping(0.f),
_elasticity(1.f),
_contactPoint(0.f),
@ -50,7 +55,7 @@ public:
CollisionInfo(qint32 type)
: _type(type),
_data(NULL),
_flags(0),
_intData(0),
_damping(0.f),
_elasticity(1.f),
_contactPoint(0.f),
@ -60,9 +65,13 @@ public:
~CollisionInfo() {}
qint32 _type; // type of Collision (will determine what is supposed to be in _data and _flags)
void* _data; // pointer to user supplied data
quint32 _flags; // 32 bits for whatever
int _type; // type of Collision
// the value of the *Data fields depend on the type
void* _data;
int _intData;
float _floatData;
glm::vec3 _vecData;
float _damping; // range [0,1] of friction coeficient
float _elasticity; // range [0,1] of energy conservation

View file

@ -132,6 +132,7 @@ bool sphereSphere(const SphereShape* sphereA, const SphereShape* sphereB, Collis
// penetration points from A into B
CollisionInfo* collision = collisions.getNewCollision();
if (collision) {
collision->_type = COLLISION_TYPE_UNKNOWN;
collision->_penetration = BA * (totalRadius - distance);
// contactPoint is on surface of A
collision->_contactPoint = sphereA->getPosition() + sphereA->getRadius() * BA;
@ -179,6 +180,7 @@ bool sphereCapsule(const SphereShape* sphereA, const CapsuleShape* capsuleB, Col
collision->_penetration = (totalRadius - radialDistance) * radialAxis; // points from A into B
// contactPoint is on surface of sphereA
collision->_contactPoint = sphereA->getPosition() + sphereA->getRadius() * radialAxis;
collision->_type = COLLISION_TYPE_UNKNOWN;
} else {
// A is on B's axis, so the penetration is undefined...
if (absAxialDistance > capsuleB->getHalfHeight()) {
@ -200,6 +202,7 @@ bool sphereCapsule(const SphereShape* sphereA, const CapsuleShape* capsuleB, Col
collision->_penetration = (sign * (totalRadius + capsuleB->getHalfHeight() - absAxialDistance)) * capsuleAxis;
// contactPoint is on surface of sphereA
collision->_contactPoint = sphereA->getPosition() + (sign * sphereA->getRadius()) * capsuleAxis;
collision->_type = COLLISION_TYPE_UNKNOWN;
}
return true;
}
@ -215,6 +218,7 @@ bool spherePlane(const SphereShape* sphereA, const PlaneShape* planeB, Collision
}
collision->_penetration = penetration;
collision->_contactPoint = sphereA->getPosition() + sphereA->getRadius() * glm::normalize(penetration);
collision->_type = COLLISION_TYPE_UNKNOWN;
return true;
}
return false;
@ -264,6 +268,7 @@ bool capsuleSphere(const CapsuleShape* capsuleA, const SphereShape* sphereB, Col
collision->_penetration = (radialDistance - totalRadius) * radialAxis; // points from A into B
// contactPoint is on surface of capsuleA
collision->_contactPoint = closestApproach - capsuleA->getRadius() * radialAxis;
collision->_type = COLLISION_TYPE_UNKNOWN;
} else {
// A is on B's axis, so the penetration is undefined...
if (absAxialDistance > capsuleA->getHalfHeight()) {
@ -284,6 +289,7 @@ bool capsuleSphere(const CapsuleShape* capsuleA, const SphereShape* sphereB, Col
collision->_penetration = (sign * (totalRadius + capsuleA->getHalfHeight() - absAxialDistance)) * capsuleAxis;
// contactPoint is on surface of sphereA
collision->_contactPoint = closestApproach + (sign * capsuleA->getRadius()) * capsuleAxis;
collision->_type = COLLISION_TYPE_UNKNOWN;
}
}
return true;
@ -355,6 +361,7 @@ bool capsuleCapsule(const CapsuleShape* capsuleA, const CapsuleShape* capsuleB,
collision->_penetration = BA * (totalRadius - distance);
// contactPoint is on surface of A
collision->_contactPoint = centerA + distanceA * axisA + capsuleA->getRadius() * BA;
collision->_type = COLLISION_TYPE_UNKNOWN;
return true;
}
} else {
@ -420,6 +427,7 @@ bool capsuleCapsule(const CapsuleShape* capsuleA, const CapsuleShape* capsuleB,
// 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;
collision->_type = COLLISION_TYPE_UNKNOWN;
return true;
}
}
@ -439,6 +447,7 @@ bool capsulePlane(const CapsuleShape* capsuleA, const PlaneShape* planeB, Collis
collision->_penetration = penetration;
glm::vec3 deepestEnd = (glm::dot(start, glm::vec3(plane)) < glm::dot(end, glm::vec3(plane))) ? start : end;
collision->_contactPoint = deepestEnd + capsuleA->getRadius() * glm::normalize(penetration);
collision->_type = COLLISION_TYPE_UNKNOWN;
return true;
}
return false;
@ -454,6 +463,7 @@ bool planeSphere(const PlaneShape* planeA, const SphereShape* sphereB, Collision
collision->_penetration = -penetration;
collision->_contactPoint = sphereB->getPosition() +
(sphereB->getRadius() / glm::length(penetration) - 1.0f) * penetration;
collision->_type = COLLISION_TYPE_UNKNOWN;
return true;
}
return false;
@ -472,6 +482,7 @@ bool planeCapsule(const PlaneShape* planeA, const CapsuleShape* capsuleB, Collis
collision->_penetration = -penetration;
glm::vec3 deepestEnd = (glm::dot(start, glm::vec3(plane)) < glm::dot(end, glm::vec3(plane))) ? start : end;
collision->_contactPoint = deepestEnd + (capsuleB->getRadius() / glm::length(penetration) - 1.0f) * penetration;
collision->_type = COLLISION_TYPE_UNKNOWN;
return true;
}
return false;
@ -653,12 +664,19 @@ bool sphereAACube(const glm::vec3& sphereCenter, float sphereRadius, const glm::
glm::vec3 direction;
BA /= maxBA;
glm::modf(BA, direction);
direction = glm::normalize(direction);
float lengthDirection = glm::length(direction);
direction /= lengthDirection;
// compute collision details
collision->_penetration = (halfCubeSide + sphereRadius - distance * glm::dot(BA, direction)) * direction;
collision->_type = COLLISION_TYPE_AACUBE;
collision->_floatData = cubeSide;
collision->_vecData = cubeCenter;
collision->_penetration = (halfCubeSide * lengthDirection + sphereRadius - maxBA * glm::dot(BA, direction)) * direction;
collision->_contactPoint = sphereCenter + sphereRadius * direction;
}
collision->_type = COLLISION_TYPE_AACUBE;
collision->_floatData = cubeSide;
collision->_vecData = cubeCenter;
return true;
} else if (sphereRadius + halfCubeSide > distance) {
// NOTE: for cocentric approximation we collide sphere and cube as two spheres which means
@ -669,6 +687,10 @@ bool sphereAACube(const glm::vec3& sphereCenter, float sphereRadius, const glm::
collision->_penetration = (sphereRadius + halfCubeSide) * glm::vec3(0.0f, -1.0f, 0.0f);
// contactPoint is on surface of A
collision->_contactPoint = sphereCenter + collision->_penetration;
collision->_type = COLLISION_TYPE_AACUBE;
collision->_floatData = cubeSide;
collision->_vecData = cubeCenter;
return true;
}
}

View file

@ -65,7 +65,7 @@ void VoxelTreeElement::splitChildren() {
}
}
bool VoxelTreeElement::appendElementData(OctreePacketData* packetData) const {
bool VoxelTreeElement::appendElementData(OctreePacketData* packetData, EncodeBitstreamParams& params) const {
return packetData->appendColor(getColor());
}

View file

@ -43,7 +43,7 @@ public:
virtual bool hasContent() const { return isColored(); }
virtual void splitChildren();
virtual bool requiresSplit() const;
virtual bool appendElementData(OctreePacketData* packetData) const;
virtual bool appendElementData(OctreePacketData* packetData, EncodeBitstreamParams& params) const;
virtual int readElementDataFromBuffer(const unsigned char* data, int bytesLeftToRead, ReadBitstreamToTreeParams& args);
virtual void calculateAverageFromChildren();
virtual bool collapseChildren();