This commit is contained in:
Philip Rosedale 2014-09-05 15:55:45 -07:00
commit 07a6fa56e0
30 changed files with 539 additions and 358 deletions

View file

@ -23,7 +23,8 @@
MetavoxelServer::MetavoxelServer(const QByteArray& packet) :
ThreadedAssignment(packet),
_nextSender(0) {
_nextSender(0),
_savedDataInitialized(false) {
}
void MetavoxelServer::applyEdit(const MetavoxelEditMessage& edit) {
@ -33,8 +34,20 @@ void MetavoxelServer::applyEdit(const MetavoxelEditMessage& edit) {
}
void MetavoxelServer::setData(const MetavoxelData& data) {
if (_data != data) {
emit dataChanged(_data = data);
if (_data == data) {
return;
}
emit dataChanged(_data = data);
if (!_savedDataInitialized) {
_savedData = data;
_savedDataInitialized = true;
// start the save timer
QTimer* saveTimer = new QTimer(this);
connect(saveTimer, &QTimer::timeout, this, &MetavoxelServer::maybeSaveData);
const int SAVE_INTERVAL = 1000 * 30;
saveTimer->start(SAVE_INTERVAL);
}
}
@ -135,6 +148,12 @@ void MetavoxelServer::maybeDeleteSession(const SharedNodePointer& node) {
}
}
void MetavoxelServer::maybeSaveData() {
if (_savedData != _data) {
QMetaObject::invokeMethod(_persister, "save", Q_ARG(const MetavoxelData&, _savedData = _data));
}
}
MetavoxelSender::MetavoxelSender(MetavoxelServer* server) :
_server(server),
_sendTimer(this) {

View file

@ -52,6 +52,7 @@ private slots:
void maybeAttachSession(const SharedNodePointer& node);
void maybeDeleteSession(const SharedNodePointer& node);
void maybeSaveData();
private:
@ -61,6 +62,8 @@ private:
MetavoxelPersister* _persister;
MetavoxelData _data;
MetavoxelData _savedData;
bool _savedDataInitialized;
};
/// Handles update sending for one thread.

View file

@ -589,8 +589,6 @@ void Application::paintGL() {
bool showWarnings = Menu::getInstance()->isOptionChecked(MenuOption::PipelineWarnings);
PerformanceWarning warn(showWarnings, "Application::paintGL()");
const bool glowEnabled = Menu::getInstance()->isOptionChecked(MenuOption::EnableGlowEffect);
// Set the desired FBO texture size. If it hasn't changed, this does nothing.
// Otherwise, it must rebuild the FBOs
if (OculusManager::isConnected()) {
@ -665,16 +663,11 @@ void Application::paintGL() {
updateShadowMap();
}
//If we aren't using the glow shader, we have to clear the color and depth buffer
if (!glowEnabled) {
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
} else if (OculusManager::isConnected()) {
if (OculusManager::isConnected()) {
//Clear the color buffer to ensure that there isnt any residual color
//Left over from when OR was not connected.
glClear(GL_COLOR_BUFFER_BIT);
}
if (OculusManager::isConnected()) {
//When in mirror mode, use camera rotation. Otherwise, use body rotation
if (whichCamera.getMode() == CAMERA_MODE_MIRROR) {
OculusManager::display(whichCamera.getRotation(), whichCamera.getPosition(), whichCamera);
@ -687,9 +680,7 @@ void Application::paintGL() {
TV3DManager::display(whichCamera);
} else {
if (glowEnabled) {
_glowEffect.prepare();
}
_glowEffect.prepare();
glMatrixMode(GL_MODELVIEW);
glPushMatrix();
@ -697,9 +688,7 @@ void Application::paintGL() {
displaySide(whichCamera);
glPopMatrix();
if (glowEnabled) {
_glowEffect.render();
}
_glowEffect.render();
if (Menu::getInstance()->isOptionChecked(MenuOption::Mirror)) {
renderRearViewMirror(_mirrorViewRect);
@ -2809,6 +2798,10 @@ void Application::displaySide(Camera& whichCamera, bool selfAvatarOnly) {
_stars.render(whichCamera.getFieldOfView(), whichCamera.getAspectRatio(), whichCamera.getNearClip(), alpha);
}
if (Menu::getInstance()->isOptionChecked(MenuOption::Wireframe)) {
glPolygonMode(GL_FRONT_AND_BACK, GL_LINE);
}
// draw the sky dome
if (!selfAvatarOnly && Menu::getInstance()->isOptionChecked(MenuOption::Atmosphere)) {
PerformanceTimer perfTimer("atmosphere");
@ -2947,6 +2940,10 @@ void Application::displaySide(Camera& whichCamera, bool selfAvatarOnly) {
_overlays.render3D();
}
}
if (Menu::getInstance()->isOptionChecked(MenuOption::Wireframe)) {
glPolygonMode(GL_FRONT_AND_BACK, GL_FILL);
}
}
void Application::updateUntranslatedViewMatrix(const glm::vec3& viewMatrixTranslation) {

View file

@ -370,7 +370,7 @@ Menu::Menu() :
shadowGroup->addAction(addCheckableActionToQMenuAndActionHash(shadowMenu, "None", 0, true));
shadowGroup->addAction(addCheckableActionToQMenuAndActionHash(shadowMenu, MenuOption::SimpleShadows, 0, false));
shadowGroup->addAction(addCheckableActionToQMenuAndActionHash(shadowMenu, MenuOption::CascadedShadows, 0, false));
shadowGroup->addAction(addCheckableActionToQMenuAndActionHash(shadowMenu, MenuOption::AvatarsReceiveShadows, 0, true));
addCheckableActionToQMenuAndActionHash(shadowMenu, MenuOption::AvatarsReceiveShadows, 0, true);
addCheckableActionToQMenuAndActionHash(renderOptionsMenu, MenuOption::Stars, Qt::Key_Asterisk, true);
@ -386,6 +386,7 @@ Menu::Menu() :
0,
appInstance->getGlowEffect(),
SLOT(cycleRenderMode()));
addCheckableActionToQMenuAndActionHash(renderOptionsMenu, MenuOption::Wireframe, 0, false);
addActionToQMenuAndActionHash(renderOptionsMenu, MenuOption::LodTools, Qt::SHIFT | Qt::Key_L, this, SLOT(lodTools()));
QMenu* avatarDebugMenu = developerMenu->addMenu("Avatar");

View file

@ -471,6 +471,7 @@ namespace MenuOption {
const QString Voxels = "Voxels";
const QString VoxelTextures = "Voxel Textures";
const QString WalletPrivateKey = "Wallet Private Key...";
const QString Wireframe = "Wireframe";
}
void sendFakeEnterEvent();

View file

@ -50,12 +50,13 @@ Avatar::Avatar() :
AvatarData(),
_skeletonModel(this),
_bodyYawDelta(0.0f),
_velocity(0.0f, 0.0f, 0.0f),
_lastVelocity(0.0f, 0.0f, 0.0f),
_acceleration(0.0f, 0.0f, 0.0f),
_angularVelocity(0.0f, 0.0f, 0.0f),
_lastAngularVelocity(0.0f, 0.0f, 0.0f),
_angularAcceleration(0.0f, 0.0f, 0.0f),
_lastPosition(0.0f),
_velocity(0.0f),
_lastVelocity(0.0f),
_acceleration(0.0f),
_angularVelocity(0.0f),
_lastAngularVelocity(0.0f),
_angularAcceleration(0.0f),
_lastOrientation(),
_leanScale(0.5f),
_scale(1.0f),
@ -185,11 +186,7 @@ void Avatar::simulate(float deltaTime) {
_hair.simulate(deltaTime);
}
}
// update position by velocity, and subtract the change added earlier for gravity
_position += _velocity * deltaTime;
updateAcceleration(deltaTime);
// update animation for display name fade in/out
if ( _displayNameTargetAlpha != _displayNameAlpha) {
// the alpha function is
@ -206,17 +203,23 @@ void Avatar::simulate(float deltaTime) {
}
_displayNameAlpha = abs(_displayNameAlpha - _displayNameTargetAlpha) < 0.01f ? _displayNameTargetAlpha : _displayNameAlpha;
}
_position += _velocity * deltaTime;
measureMotionDerivatives(deltaTime);
}
void Avatar::updateAcceleration(float deltaTime) {
// Linear Component of Acceleration
_acceleration = (_velocity - _lastVelocity) * (1.f / deltaTime);
void Avatar::measureMotionDerivatives(float deltaTime) {
// linear
float invDeltaTime = 1.0f / deltaTime;
_velocity = (_position - _lastPosition) * invDeltaTime;
_lastPosition = _position;
_acceleration = (_velocity - _lastVelocity) * invDeltaTime;
_lastVelocity = _velocity;
// Angular Component of Acceleration
// angular
glm::quat orientation = getOrientation();
glm::quat delta = glm::inverse(_lastOrientation) * orientation;
_angularVelocity = safeEulerAngles(delta) * (1.f / deltaTime);
_angularAcceleration = (_angularVelocity - _lastAngularVelocity) * (1.f / deltaTime);
_angularVelocity = safeEulerAngles(delta) * invDeltaTime;
_angularAcceleration = (_angularVelocity - _lastAngularVelocity) * invDeltaTime;
_lastOrientation = getOrientation();
}

View file

@ -91,7 +91,6 @@ public:
const QVector<Model*>& getAttachmentModels() const { return _attachmentModels; }
glm::vec3 getChestPosition() const;
float getScale() const { return _scale; }
Q_INVOKABLE const glm::vec3& getVelocity() const { return _velocity; }
const Head* getHead() const { return static_cast<const Head*>(_headData); }
Head* getHead() { return static_cast<Head*>(_headData); }
Hand* getHand() { return static_cast<Hand*>(_handData); }
@ -152,6 +151,7 @@ public:
Q_INVOKABLE glm::quat getJointCombinedRotation(int index) const;
Q_INVOKABLE glm::quat getJointCombinedRotation(const QString& name) const;
Q_INVOKABLE glm::vec3 getVelocity() const { return _velocity; }
Q_INVOKABLE glm::vec3 getAcceleration() const { return _acceleration; }
Q_INVOKABLE glm::vec3 getAngularVelocity() const { return _angularVelocity; }
Q_INVOKABLE glm::vec3 getAngularAcceleration() const { return _angularAcceleration; }
@ -172,6 +172,12 @@ protected:
SkeletonModel _skeletonModel;
QVector<Model*> _attachmentModels;
float _bodyYawDelta;
// These position histories and derivatives are in the world-frame.
// The derivatives are the MEASURED results of all external and internal forces
// and are therefor READ-ONLY --> motion control of the Avatar is NOT obtained
// by setting these values.
glm::vec3 _lastPosition;
glm::vec3 _velocity;
glm::vec3 _lastVelocity;
glm::vec3 _acceleration;
@ -179,6 +185,7 @@ protected:
glm::vec3 _lastAngularVelocity;
glm::vec3 _angularAcceleration;
glm::quat _lastOrientation;
float _leanScale;
float _scale;
glm::vec3 _worldUpDirection;
@ -195,7 +202,7 @@ protected:
glm::vec3 getBodyFrontDirection() const { return getOrientation() * IDENTITY_FRONT; }
glm::quat computeRotationFromBodyToWorldUp(float proportion = 1.0f) const;
void setScale(float scale);
void updateAcceleration(float deltaTime);
void measureMotionDerivatives(float deltaTime);
float getSkeletonHeight() const;
float getHeadHeight() const;

View file

@ -62,9 +62,9 @@ MyAvatar::MyAvatar() :
_mousePressed(false),
_bodyPitchDelta(0.0f),
_bodyRollDelta(0.0f),
_shouldJump(false),
_gravity(0.0f, 0.0f, 0.0f),
_distanceToNearestAvatar(std::numeric_limits<float>::max()),
_shouldJump(false),
_wasPushing(false),
_isPushing(false),
_isBraking(false),
@ -74,7 +74,6 @@ MyAvatar::MyAvatar() :
_motorTimescale(DEFAULT_MOTOR_TIMESCALE),
_maxMotorSpeed(MAX_MOTOR_SPEED),
_motionBehaviors(AVATAR_MOTION_DEFAULTS),
_lastBodyPenetration(0.0f),
_lastFloorContactPoint(0.0f),
_lookAtTargetAvatar(),
_shouldRender(true),
@ -103,7 +102,7 @@ void MyAvatar::reset() {
_skeletonModel.reset();
getHead()->reset();
setVelocity(glm::vec3(0.0f));
_velocity = glm::vec3(0.0f);
setThrust(glm::vec3(0.0f));
// Reset the pitch and roll components of the avatar's orientation, preserve yaw direction
glm::vec3 eulers = safeEulerAngles(getOrientation());
@ -1159,6 +1158,11 @@ void MyAvatar::updatePosition(float deltaTime) {
boundingShape.getStartPoint(startCap);
glm::vec3 bottom = startCap - boundingShape.getRadius() * _worldUpDirection;
// velocity is initialized to the measured _velocity but will be modified
// by friction, external thrust, etc
glm::vec3 velocity = _velocity;
// apply friction
if (gravityLength > EPSILON) {
float speedFromGravity = _scale * deltaTime * gravityLength;
float distanceToFall = glm::distance(bottom, _lastFloorContactPoint);
@ -1167,26 +1171,26 @@ void MyAvatar::updatePosition(float deltaTime) {
if (walkingOnFloor) {
// BEGIN HACK: to prevent the avatar from bouncing on a floor surface
if (distanceToFall < deltaTime * speedFromGravity) {
float verticalSpeed = glm::dot(_velocity, _worldUpDirection);
float verticalSpeed = glm::dot(velocity, _worldUpDirection);
if (fabs(verticalSpeed) < speedFromGravity) {
// we're standing on a floor, and nearly at rest so we zero the vertical velocity component
_velocity -= verticalSpeed * _worldUpDirection;
velocity -= verticalSpeed * _worldUpDirection;
}
} else {
// fall with gravity against floor
_velocity -= speedFromGravity * _worldUpDirection;
velocity -= speedFromGravity * _worldUpDirection;
}
// END HACK
} else {
if (!_isBraking) {
// fall with gravity toward floor
_velocity -= speedFromGravity * _worldUpDirection;
velocity -= speedFromGravity * _worldUpDirection;
}
if (_motionBehaviors & AVATAR_MOTION_STAND_ON_NEARBY_FLOORS) {
const float MAX_VERTICAL_FLOOR_DETECTION_SPEED = _scale * MAX_WALKING_SPEED;
if (keyboardInput && glm::dot(_motorVelocity, _worldUpDirection) > 0.0f &&
glm::dot(_velocity, _worldUpDirection) > MAX_VERTICAL_FLOOR_DETECTION_SPEED) {
glm::dot(velocity, _worldUpDirection) > MAX_VERTICAL_FLOOR_DETECTION_SPEED) {
// disable local gravity when flying up
setLocalGravity(glm::vec3(0.0f));
} else {
@ -1198,93 +1202,123 @@ void MyAvatar::updatePosition(float deltaTime) {
}
}
}
} else {
if ((_collisionGroups & COLLISION_GROUP_VOXELS) &&
_motionBehaviors & AVATAR_MOTION_STAND_ON_NEARBY_FLOORS) {
const float MIN_FLOOR_DETECTION_SPEED = _scale * 1.0f;
if (glm::length(_velocity) < MIN_FLOOR_DETECTION_SPEED ) {
// scan for floor under avatar
const float maxFloorDistance = _scale * NEARBY_FLOOR_THRESHOLD;
if (computeDistanceToFloor(bottom) < maxFloorDistance) {
// enable local gravity
setLocalGravity(-_worldUpDirection);
}
} else if ((_collisionGroups & COLLISION_GROUP_VOXELS) &&
_motionBehaviors & AVATAR_MOTION_STAND_ON_NEARBY_FLOORS) {
const float MIN_FLOOR_DETECTION_SPEED = _scale * 1.0f;
if (glm::length(_velocity) < MIN_FLOOR_DETECTION_SPEED ) {
// scan for floor under avatar
const float maxFloorDistance = _scale * NEARBY_FLOOR_THRESHOLD;
if (computeDistanceToFloor(bottom) < maxFloorDistance) {
// enable local gravity
setLocalGravity(-_worldUpDirection);
}
}
}
if (keyboardInput > 0.0f || glm::length2(_velocity) > 0.0f || glm::length2(_thrust) > 0.0f || ! walkingOnFloor) {
// update motor and thrust
updateMotorFromKeyboard(deltaTime, walkingOnFloor);
applyMotor(deltaTime);
applyThrust(deltaTime);
float speed = glm::length(velocity);
if (keyboardInput > 0.0f || speed > 0.0f || glm::length2(_thrust) > 0.0f || ! walkingOnFloor) {
// update motor
if (_motionBehaviors & AVATAR_MOTION_MOTOR_KEYBOARD_ENABLED) {
// Increase motor velocity until its length is equal to _maxMotorSpeed.
glm::vec3 localVelocity = velocity;
if (_motionBehaviors & AVATAR_MOTION_MOTOR_USE_LOCAL_FRAME) {
glm::quat orientation = getHead()->getCameraOrientation();
localVelocity = glm::inverse(orientation) * velocity;
}
// Compute keyboard input
glm::vec3 front = (_driveKeys[FWD] - _driveKeys[BACK]) * IDENTITY_FRONT;
glm::vec3 right = (_driveKeys[RIGHT] - _driveKeys[LEFT]) * IDENTITY_RIGHT;
glm::vec3 up = (_driveKeys[UP] - _driveKeys[DOWN]) * IDENTITY_UP;
glm::vec3 direction = front + right + up;
float directionLength = glm::length(direction);
// Compute motor magnitude
if (directionLength > EPSILON) {
direction /= directionLength;
// the finalMotorSpeed depends on whether we are walking or not
float finalMaxMotorSpeed = walkingOnFloor ? _scale * MAX_WALKING_SPEED : _scale * _maxMotorSpeed;
float motorLength = glm::length(_motorVelocity);
if (motorLength < _scale * MIN_KEYBOARD_CONTROL_SPEED) {
// an active keyboard motor should never be slower than this
_motorVelocity = _scale * MIN_KEYBOARD_CONTROL_SPEED * direction;
} else {
float MOTOR_LENGTH_TIMESCALE = 1.5f;
float tau = glm::clamp(deltaTime / MOTOR_LENGTH_TIMESCALE, 0.0f, 1.0f);
float INCREASE_FACTOR = 2.0f;
//_motorVelocity *= 1.0f + tau * INCREASE_FACTOR;
motorLength *= 1.0f + tau * INCREASE_FACTOR;
if (motorLength > finalMaxMotorSpeed) {
motorLength = finalMaxMotorSpeed;
}
_motorVelocity = motorLength * direction;
}
_isPushing = true;
} else {
// motor opposes motion (wants to be at rest)
_motorVelocity = - localVelocity;
}
}
// apply motor
if (_motionBehaviors & AVATAR_MOTION_MOTOR_ENABLED) {
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;
}
glm::vec3 targetDirection(0.0f);
if (glm::length2(targetVelocity) > EPSILON) {
targetDirection = glm::normalize(targetVelocity);
}
glm::vec3 deltaVelocity = targetVelocity - velocity;
if (_motionBehaviors & AVATAR_MOTION_MOTOR_COLLISION_SURFACE_ONLY && glm::length2(_gravity) > EPSILON) {
// For now we subtract the component parallel to gravity but what we need to do is:
// TODO: subtract the component perp to the local surface normal (motor only pushes in surface plane).
glm::vec3 gravityDirection = glm::normalize(_gravity);
glm::vec3 parallelDelta = glm::dot(deltaVelocity, gravityDirection) * gravityDirection;
if (glm::dot(targetVelocity, velocity) > 0.0f) {
// remove parallel part from deltaVelocity
deltaVelocity -= parallelDelta;
}
}
// simple critical damping
float timescale = computeMotorTimescale(velocity);
float tau = glm::clamp(deltaTime / timescale, 0.0f, 1.0f);
velocity += tau * deltaVelocity;
}
// apply thrust
velocity += _thrust * deltaTime;
speed = glm::length(velocity);
if (speed > MAX_AVATAR_SPEED) {
velocity *= MAX_AVATAR_SPEED / speed;
speed = MAX_AVATAR_SPEED;
}
_thrust = glm::vec3(0.0f);
// update position
if (glm::length2(_velocity) < EPSILON) {
_velocity = glm::vec3(0.0f);
} else {
_position += _velocity * deltaTime;
const float MIN_AVATAR_SPEED = 0.075f;
if (speed > MIN_AVATAR_SPEED) {
_position += velocity * deltaTime;
}
updateAcceleration(deltaTime);
}
// update moving flag based on speed
const float MOVING_SPEED_THRESHOLD = 0.01f;
_moving = glm::length(_velocity) > MOVING_SPEED_THRESHOLD;
_moving = speed > MOVING_SPEED_THRESHOLD;
updateChatCircle(deltaTime);
measureMotionDerivatives(deltaTime);
}
void MyAvatar::updateMotorFromKeyboard(float deltaTime, bool walking) {
// Increase motor velocity until its length is equal to _maxMotorSpeed.
if (!(_motionBehaviors & AVATAR_MOTION_MOTOR_KEYBOARD_ENABLED)) {
// nothing to do
return;
}
glm::vec3 localVelocity = _velocity;
if (_motionBehaviors & AVATAR_MOTION_MOTOR_USE_LOCAL_FRAME) {
glm::quat orientation = getHead()->getCameraOrientation();
localVelocity = glm::inverse(orientation) * _velocity;
}
// Compute keyboard input
glm::vec3 front = (_driveKeys[FWD] - _driveKeys[BACK]) * IDENTITY_FRONT;
glm::vec3 right = (_driveKeys[RIGHT] - _driveKeys[LEFT]) * IDENTITY_RIGHT;
glm::vec3 up = (_driveKeys[UP] - _driveKeys[DOWN]) * IDENTITY_UP;
glm::vec3 direction = front + right + up;
float directionLength = glm::length(direction);
// Compute motor magnitude
if (directionLength > EPSILON) {
direction /= directionLength;
// the finalMotorSpeed depends on whether we are walking or not
float finalMaxMotorSpeed = walking ? _scale * MAX_WALKING_SPEED : _scale * _maxMotorSpeed;
float motorLength = glm::length(_motorVelocity);
if (motorLength < _scale * MIN_KEYBOARD_CONTROL_SPEED) {
// an active keyboard motor should never be slower than this
_motorVelocity = _scale * MIN_KEYBOARD_CONTROL_SPEED * direction;
} else {
float MOTOR_LENGTH_TIMESCALE = 1.5f;
float tau = glm::clamp(deltaTime / MOTOR_LENGTH_TIMESCALE, 0.0f, 1.0f);
float INCREASE_FACTOR = 2.0f;
//_motorVelocity *= 1.0f + tau * INCREASE_FACTOR;
motorLength *= 1.0f + tau * INCREASE_FACTOR;
if (motorLength > finalMaxMotorSpeed) {
motorLength = finalMaxMotorSpeed;
}
_motorVelocity = motorLength * direction;
}
_isPushing = true;
} else {
// motor opposes motion (wants to be at rest)
_motorVelocity = - localVelocity;
}
}
float MyAvatar::computeMotorTimescale() {
float MyAvatar::computeMotorTimescale(const glm::vec3& velocity) {
// The timescale of the motor is the approximate time it takes for the motor to
// accomplish its intended velocity. A short timescale makes the motor strong,
// and a long timescale makes it weak. The value of timescale to use depends
@ -1304,7 +1338,7 @@ float MyAvatar::computeMotorTimescale() {
timescale = _motorTimescale;
_isBraking = false;
} else {
float speed = glm::length(_velocity);
float speed = glm::length(velocity);
_isBraking = _wasPushing || (_isBraking && speed > MIN_BRAKE_SPEED);
if (_isBraking) {
timescale = MIN_MOTOR_TIMESCALE;
@ -1315,160 +1349,6 @@ float MyAvatar::computeMotorTimescale() {
return timescale;
}
void MyAvatar::applyMotor(float deltaTime) {
// TODO: recover extra braking behavior when flying close to nearest avatar
if (!( _motionBehaviors & AVATAR_MOTION_MOTOR_ENABLED)) {
// nothing to do --> early exit
return;
}
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;
}
glm::vec3 targetDirection(0.0f);
if (glm::length2(targetVelocity) > EPSILON) {
targetDirection = glm::normalize(targetVelocity);
}
glm::vec3 deltaVelocity = targetVelocity - _velocity;
if (_motionBehaviors & AVATAR_MOTION_MOTOR_COLLISION_SURFACE_ONLY && glm::length2(_gravity) > EPSILON) {
// For now we subtract the component parallel to gravity but what we need to do is:
// TODO: subtract the component perp to the local surface normal (motor only pushes in surface plane).
glm::vec3 gravityDirection = glm::normalize(_gravity);
glm::vec3 parallelDelta = glm::dot(deltaVelocity, gravityDirection) * gravityDirection;
if (glm::dot(targetVelocity, _velocity) > 0.0f) {
// remove parallel part from deltaVelocity
deltaVelocity -= parallelDelta;
}
}
// simple critical damping
float timescale = computeMotorTimescale();
float tau = glm::clamp(deltaTime / timescale, 0.0f, 1.0f);
_velocity += tau * deltaVelocity;
}
void MyAvatar::applyThrust(float deltaTime) {
_velocity += _thrust * deltaTime;
float speed = glm::length(_velocity);
// cap the speed that thrust can achieve
if (speed > MAX_AVATAR_SPEED) {
_velocity *= MAX_AVATAR_SPEED / speed;
}
// zero thrust so we don't pile up thrust from other sources
_thrust = glm::vec3(0.0f);
}
/* Keep this code for the short term as reference in case we need to further tune the new model
* to achieve legacy movement response.
void MyAvatar::updateThrust(float deltaTime) {
//
// Gather thrust information from keyboard and sensors to apply to avatar motion
//
glm::quat orientation = getHead()->getCameraOrientation();
glm::vec3 front = orientation * IDENTITY_FRONT;
glm::vec3 right = orientation * IDENTITY_RIGHT;
glm::vec3 up = orientation * IDENTITY_UP;
const float THRUST_MAG_UP = 800.0f;
const float THRUST_MAG_DOWN = 300.0f;
const float THRUST_MAG_FWD = 500.0f;
const float THRUST_MAG_BACK = 300.0f;
const float THRUST_MAG_LATERAL = 250.0f;
const float THRUST_JUMP = 120.0f;
// Add Thrusts from keyboard
_thrust += _driveKeys[FWD] * _scale * THRUST_MAG_FWD * _thrustMultiplier * deltaTime * front;
_thrust -= _driveKeys[BACK] * _scale * THRUST_MAG_BACK * _thrustMultiplier * deltaTime * front;
_thrust += _driveKeys[RIGHT] * _scale * THRUST_MAG_LATERAL * _thrustMultiplier * deltaTime * right;
_thrust -= _driveKeys[LEFT] * _scale * THRUST_MAG_LATERAL * _thrustMultiplier * deltaTime * right;
_thrust += _driveKeys[UP] * _scale * THRUST_MAG_UP * _thrustMultiplier * deltaTime * up;
_thrust -= _driveKeys[DOWN] * _scale * THRUST_MAG_DOWN * _thrustMultiplier * deltaTime * up;
// attenuate thrust when in penetration
if (glm::dot(_thrust, _lastBodyPenetration) > EPSILON) {
const float MAX_BODY_PENETRATION_DEPTH = 0.6f * _skeletonModel.getBoundingShapeRadius();
float penetrationFactor = glm::min(1.0f, glm::length(_lastBodyPenetration) / MAX_BODY_PENETRATION_DEPTH);
glm::vec3 penetrationDirection = glm::normalize(_lastBodyPenetration);
// attenuate parallel component
glm::vec3 parallelThrust = glm::dot(_thrust, penetrationDirection) * penetrationDirection;
// attenuate perpendicular component (friction)
glm::vec3 perpendicularThrust = _thrust - parallelThrust;
// recombine to get the final thrust
_thrust = (1.0f - penetrationFactor) * parallelThrust + (1.0f - penetrationFactor * penetrationFactor) * perpendicularThrust;
// attenuate the growth of _thrustMultiplier when in penetration
// otherwise the avatar will eventually be able to tunnel through the obstacle
_thrustMultiplier *= (1.0f - penetrationFactor * penetrationFactor);
} else if (_thrustMultiplier < 1.0f) {
// rapid healing of attenuated thrustMultiplier after penetration event
_thrustMultiplier = 1.0f;
}
_lastBodyPenetration = glm::vec3(0.0f);
// If thrust keys are being held down, slowly increase thrust to allow reaching great speeds
if (_driveKeys[FWD] || _driveKeys[BACK] || _driveKeys[RIGHT] || _driveKeys[LEFT] || _driveKeys[UP] || _driveKeys[DOWN]) {
const float THRUST_INCREASE_RATE = 1.05f;
const float MAX_THRUST_MULTIPLIER = 75.0f;
_thrustMultiplier *= 1.0f + deltaTime * THRUST_INCREASE_RATE;
if (_thrustMultiplier > MAX_THRUST_MULTIPLIER) {
_thrustMultiplier = MAX_THRUST_MULTIPLIER;
}
} else {
_thrustMultiplier = 1.0f;
}
// Add one time jumping force if requested
if (_shouldJump) {
if (glm::length(_gravity) > EPSILON) {
_thrust += _scale * THRUST_JUMP * up;
}
_shouldJump = false;
}
// Update speed brake status
const float MIN_SPEED_BRAKE_VELOCITY = _scale * 0.4f;
if ((glm::length(_thrust) == 0.0f) && _isThrustOn && (glm::length(_velocity) > MIN_SPEED_BRAKE_VELOCITY)) {
_speedBrakes = true;
}
_isThrustOn = (glm::length(_thrust) > EPSILON);
if (_isThrustOn || (_speedBrakes && (glm::length(_velocity) < MIN_SPEED_BRAKE_VELOCITY))) {
_speedBrakes = false;
}
_velocity += _thrust * deltaTime;
// Zero thrust out now that we've added it to velocity in this frame
_thrust = glm::vec3(0.0f);
// apply linear damping
const float MAX_STATIC_FRICTION_SPEED = 0.5f;
const float STATIC_FRICTION_STRENGTH = _scale * 20.0f;
applyStaticFriction(deltaTime, _velocity, MAX_STATIC_FRICTION_SPEED, STATIC_FRICTION_STRENGTH);
const float LINEAR_DAMPING_STRENGTH = 0.5f;
const float SPEED_BRAKE_POWER = _scale * 10.0f;
const float SQUARED_DAMPING_STRENGTH = 0.007f;
const float SLOW_NEAR_RADIUS = 5.0f;
float linearDamping = LINEAR_DAMPING_STRENGTH;
const float NEAR_AVATAR_DAMPING_FACTOR = 50.0f;
if (_distanceToNearestAvatar < _scale * SLOW_NEAR_RADIUS) {
linearDamping *= 1.0f + NEAR_AVATAR_DAMPING_FACTOR *
((SLOW_NEAR_RADIUS - _distanceToNearestAvatar) / SLOW_NEAR_RADIUS);
}
if (_speedBrakes) {
applyDamping(deltaTime, _velocity, linearDamping * SPEED_BRAKE_POWER, SQUARED_DAMPING_STRENGTH * SPEED_BRAKE_POWER);
} else {
applyDamping(deltaTime, _velocity, linearDamping, SQUARED_DAMPING_STRENGTH);
}
}
*/
void MyAvatar::updateCollisionWithEnvironment(float deltaTime, float radius) {
glm::vec3 up = getBodyUpDirection();
const float ENVIRONMENT_SURFACE_ELASTICITY = 0.0f;

View file

@ -54,7 +54,6 @@ public:
// setters
void setMousePressed(bool mousePressed) { _mousePressed = mousePressed; }
void setVelocity(const glm::vec3 velocity) { _velocity = velocity; }
void setLeanScale(float scale) { _leanScale = scale; }
void setLocalGravity(glm::vec3 gravity);
void setShouldRenderLocally(bool shouldRender) { _shouldRender = shouldRender; }
@ -189,23 +188,23 @@ private:
bool _mousePressed;
float _bodyPitchDelta; // degrees
float _bodyRollDelta; // degrees
bool _shouldJump;
float _driveKeys[MAX_DRIVE_KEYS];
glm::vec3 _gravity;
float _distanceToNearestAvatar; // How close is the nearest avatar?
bool _shouldJump;
float _driveKeys[MAX_DRIVE_KEYS];
bool _wasPushing;
bool _isPushing;
bool _isBraking;
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
float _trapDuration; // seconds that avatar has been trapped by collisions
glm::vec3 _thrust; // impulse accumulator for outside sources
glm::vec3 _motorVelocity; // intended velocity of avatar motion (relative to what it's standing on)
float _motorTimescale; // timescale for avatar motor to achieve its desired velocity
float _maxMotorSpeed;
quint32 _motionBehaviors;
glm::vec3 _lastBodyPenetration;
glm::vec3 _lastFloorContactPoint;
QWeakPointer<AvatarData> _lookAtTargetAvatar;
glm::vec3 _targetAvatarPosition;
@ -223,10 +222,7 @@ private:
float computeDistanceToFloor(const glm::vec3& startPoint);
void updateOrientation(float deltaTime);
void updatePosition(float deltaTime);
void updateMotorFromKeyboard(float deltaTime, bool walking);
float computeMotorTimescale();
void applyMotor(float deltaTime);
void applyThrust(float deltaTime);
float computeMotorTimescale(const glm::vec3& velocity);
void updateCollisionWithAvatars(float deltaTime);
void updateCollisionWithEnvironment(float deltaTime, float radius);
void updateCollisionWithVoxels(float deltaTime, float radius);

View file

@ -140,7 +140,7 @@ QOpenGLFramebufferObject* GlowEffect::render(bool toTexture) {
QOpenGLFramebufferObject* destFBO = toTexture ?
Application::getInstance()->getTextureCache()->getSecondaryFramebufferObject() : NULL;
if (_isEmpty && _renderMode != DIFFUSE_ADD_MODE) {
if (!Menu::getInstance()->isOptionChecked(MenuOption::EnableGlowEffect) || (_isEmpty && _renderMode != DIFFUSE_ADD_MODE)) {
// copy the primary to the screen
if (QOpenGLFramebufferObject::hasOpenGLFramebufferBlit()) {
QOpenGLFramebufferObject::blitFramebuffer(destFBO, primaryFBO);

View file

@ -1251,8 +1251,11 @@ QColor VoxelColorBoxTool::getColor() {
}
void VoxelColorBoxTool::applyValue(const glm::vec3& minimum, const glm::vec3& maximum) {
// ensure that color is either 100% transparent or 100% opaque
QColor color = _color->getColor();
color.setAlphaF(color.alphaF() > 0.5f ? 1.0f : 0.0f);
MetavoxelEditMessage message = { QVariant::fromValue(VoxelColorBoxEdit(Box(minimum, maximum),
_editor->getGridSpacing(), _color->getColor())) };
_editor->getGridSpacing(), color)) };
Application::getInstance()->getMetavoxels()->applyEdit(message, true);
}
@ -1279,8 +1282,13 @@ QColor VoxelMaterialBoxTool::getColor() {
void VoxelMaterialBoxTool::applyValue(const glm::vec3& minimum, const glm::vec3& maximum) {
SharedObjectPointer material = _materialEditor->getObject();
_materialEditor->detachObject();
QColor color;
if (_texture) {
color = _texture->getAverageColor();
color.setAlphaF(1.0f);
}
MetavoxelEditMessage message = { QVariant::fromValue(VoxelMaterialBoxEdit(Box(minimum, maximum),
_editor->getGridSpacing(), material, _texture ? _texture->getAverageColor() : QColor())) };
_editor->getGridSpacing(), material, color)) };
Application::getInstance()->getMetavoxels()->applyEdit(message, true);
}
@ -1363,8 +1371,11 @@ QColor VoxelColorSphereTool::getColor() {
}
void VoxelColorSphereTool::applyValue(const glm::vec3& position, float radius) {
// ensure that color is either 100% transparent or 100% opaque
QColor color = _color->getColor();
color.setAlphaF(color.alphaF() > 0.5f ? 1.0f : 0.0f);
MetavoxelEditMessage message = { QVariant::fromValue(VoxelColorSphereEdit(position, radius,
_editor->getGridSpacing(), _color->getColor())) };
_editor->getGridSpacing(), color)) };
Application::getInstance()->getMetavoxels()->applyEdit(message, true);
}
@ -1386,8 +1397,13 @@ QColor VoxelMaterialSphereTool::getColor() {
void VoxelMaterialSphereTool::applyValue(const glm::vec3& position, float radius) {
SharedObjectPointer material = _materialEditor->getObject();
_materialEditor->detachObject();
QColor color;
if (_texture) {
color = _texture->getAverageColor();
color.setAlphaF(1.0f);
}
MetavoxelEditMessage message = { QVariant::fromValue(VoxelMaterialSphereEdit(position, radius,
_editor->getGridSpacing(), material, _texture ? _texture->getAverageColor() : QColor())) };
_editor->getGridSpacing(), material, color)) };
Application::getInstance()->getMetavoxels()->applyEdit(message, true);
}

View file

@ -65,6 +65,9 @@ void AudioInjector::injectAudio() {
int numPreSequenceNumberBytes = injectAudioPacket.size();
packetStream << (quint16)0;
// pack the stereo/mono type of the stream
packetStream << _options.isStereo();
// pack stream identifier (a generated UUID)
packetStream << QUuid::createUuid();

View file

@ -17,6 +17,7 @@ AudioInjectorOptions::AudioInjectorOptions(QObject* parent) :
_volume(1.0f),
_loop(false),
_orientation(glm::vec3(0.0f, 0.0f, 0.0f)),
_isStereo(false),
_loopbackAudioInterface(NULL)
{
}
@ -26,6 +27,7 @@ AudioInjectorOptions::AudioInjectorOptions(const AudioInjectorOptions& other) {
_volume = other._volume;
_loop = other._loop;
_orientation = other._orientation;
_isStereo = other._isStereo;
_loopbackAudioInterface = other._loopbackAudioInterface;
}
@ -34,5 +36,6 @@ void AudioInjectorOptions::operator=(const AudioInjectorOptions& other) {
_volume = other._volume;
_loop = other._loop;
_orientation = other._orientation;
_isStereo = other._isStereo;
_loopbackAudioInterface = other._loopbackAudioInterface;
}

View file

@ -44,6 +44,9 @@ public:
const glm::quat& getOrientation() const { return _orientation; }
void setOrientation(const glm::quat& orientation) { _orientation = orientation; }
const bool isStereo() const { return _isStereo; }
void setIsStereo(const bool isStereo) { _isStereo = isStereo; }
AbstractAudioInterface* getLoopbackAudioInterface() const { return _loopbackAudioInterface; }
void setLoopbackAudioInterface(AbstractAudioInterface* loopbackAudioInterface)
{ _loopbackAudioInterface = loopbackAudioInterface; }
@ -52,6 +55,7 @@ private:
float _volume;
bool _loop;
glm::quat _orientation;
bool _isStereo;
AbstractAudioInterface* _loopbackAudioInterface;
};

View file

@ -36,6 +36,8 @@ int InjectedAudioStream::parseStreamProperties(PacketType type, const QByteArray
// skip the stream identifier
packetStream.skipRawData(NUM_BYTES_RFC4122_UUID);
packetStream >> _isStereo;
// pull the loopback flag and set our boolean
uchar shouldLoopback;

View file

@ -240,8 +240,6 @@ public:
const HeadData* getHeadData() const { return _headData; }
const HandData* getHandData() const { return _handData; }
virtual const glm::vec3& getVelocity() const { return vec3Zero; }
virtual bool findSphereCollisions(const glm::vec3& particleCenter, float particleRadius, CollisionList& collisions) {
return false;
}

View file

@ -467,7 +467,7 @@ int EntityTree::processEditPacketData(PacketType packetType, const unsigned char
if (existingEntity) {
updateEntity(entityItemID, properties);
} else {
qDebug() << "User attempted to edit an unknown entity.";
qDebug() << "User attempted to edit an unknown entity. ID:" << entityItemID;
}
} else {
// this is a new entity... assign a new entityID

View file

@ -67,7 +67,8 @@ public:
virtual int minimumRequiredRootDataBytes() const { return sizeof(uint16_t); }
virtual bool suppressEmptySubtrees() const { return false; }
virtual void releaseSceneEncodeData(OctreeElementExtraEncodeData* extraEncodeData) const;
virtual bool mustIncludeAllChildData() const { return false; }
virtual bool versionHasSVOfileBreaks(PacketVersion thisVersion) const
{ return thisVersion >= VERSION_ENTITIES_HAS_FILE_BREAKS; }
@ -126,7 +127,7 @@ public:
void setContainingElement(const EntityItemID& entityItemID, EntityTreeElement* element);
void resetContainingElement(const EntityItemID& entityItemID, EntityTreeElement* element);
void debugDumpMap();
void dumpTree();
virtual void dumpTree();
void sendEntities(EntityEditPacketSender* packetSender, EntityTree* localTree, float x, float y, float z);

View file

@ -721,9 +721,17 @@ int EntityTreeElement::readElementDataFromBuffer(const unsigned char* data, int
if (bytesLeftToRead >= (int)(numberOfEntities * expectedBytesPerEntity)) {
for (uint16_t i = 0; i < numberOfEntities; i++) {
int bytesForThisEntity = 0;
EntityItemID entityItemID = EntityItemID::readEntityItemIDFromBuffer(dataAt, bytesLeftToRead);
EntityItem* entityItem = _myTree->findEntityByEntityItemID(entityItemID);
EntityItemID entityItemID;
EntityItem* entityItem = NULL;
bool newEntity = false;
// Old model files don't have UUIDs in them. So we don't want to try to read those IDs from the stream.
// Since this can only happen on loading an old file, we can safely treat these as new entity cases,
// which will correctly handle the case of creating models and letting them parse the old format.
if (args.bitstreamVersion >= VERSION_ENTITIES_SUPPORT_SPLIT_MTU) {
entityItemID = EntityItemID::readEntityItemIDFromBuffer(dataAt, bytesLeftToRead);
entityItem = _myTree->findEntityByEntityItemID(entityItemID);
}
// If the item already exists in our tree, we want do the following...
// 1) allow the existing item to read from the databuffer
@ -758,6 +766,7 @@ int EntityTreeElement::readElementDataFromBuffer(const unsigned char* data, int
if (entityItem) {
bytesForThisEntity = entityItem->readEntityDataFromBuffer(dataAt, bytesLeftToRead, args);
addEntityItem(entityItem); // add this new entity to this elements entities
entityItemID = entityItem->getEntityItemID();
_myTree->setContainingElement(entityItemID, this);
newEntity = true;
EntityItem::SimulationState newState = entityItem->getSimulationState();

View file

@ -65,7 +65,9 @@ bool EntityTypes::registerEntityType(EntityType entityType, const char* name, En
return false;
}
EntityItem* EntityTypes::constructEntityItem(EntityType entityType, const EntityItemID& entityID, const EntityItemProperties& properties) {
EntityItem* EntityTypes::constructEntityItem(EntityType entityType, const EntityItemID& entityID,
const EntityItemProperties& properties) {
EntityItem* newEntityItem = NULL;
EntityTypeFactory factory = NULL;
if (entityType >= 0 && entityType <= LAST) {

View file

@ -165,7 +165,8 @@ int ModelEntityItem::oldVersionReadEntityDataFromBuffer(const unsigned char* dat
QString ageAsString = formatSecondsElapsed(getAge());
qDebug() << "Loading old model file, _created = _lastEdited =" << _created
<< " age=" << getAge() << "seconds - " << ageAsString;
<< " age=" << getAge() << "seconds - " << ageAsString
<< "old ID=" << oldID << "new ID=" << _id;
// radius
memcpy(&_radius, dataAt, sizeof(_radius));

View file

@ -2053,19 +2053,64 @@ FBXGeometry readSVO(const QByteArray& model) {
mesh.parts.append(part);
VoxelTree tree;
ReadBitstreamToTreeParams args(WANT_COLOR, NO_EXISTS_BITS);
unsigned char* dataAt = (unsigned char*)model.data();
size_t dataSize = model.size();
if (tree.getWantSVOfileVersions()) {
PacketVersion gotVersion = 0;
// NOTE: SPECIAL CASE for old voxel svo files. The old voxel SVO files didn't have header
// details. They started with the the octalcode for the root. Which was always 00 which matches PacketTypeUnknown
unsigned char* firstByteAt = (unsigned char*)model.data();
unsigned char firstByteValue = *firstByteAt;
if (tree.expectedDataPacketType() == PacketTypeVoxelData && firstByteValue == 0) {
qDebug() << "Detected OLD Voxels format.";
gotVersion = 0;
} else if (tree.getWantSVOfileVersions()) {
// skip the type/version
dataAt += sizeof(PacketType);
dataSize -= sizeof(PacketType);
gotVersion = *dataAt;
dataAt += sizeof(PacketVersion);
dataSize -= sizeof(PacketVersion);
}
tree.readBitstreamToTree(dataAt, dataSize, args);
}
bool hasBufferBreaks = tree.versionHasSVOfileBreaks(gotVersion);
ReadBitstreamToTreeParams args(WANT_COLOR, NO_EXISTS_BITS, NULL, 0,
SharedNodePointer(), false, gotVersion);
if (!hasBufferBreaks) {
tree.readBitstreamToTree(dataAt, dataSize, args);
} else {
const unsigned long MAX_CHUNK_LENGTH = MAX_OCTREE_PACKET_SIZE * 2;
while (dataSize > 0) {
quint16 chunkLength = 0;
chunkLength = *dataAt;
dataAt += sizeof(chunkLength);
dataSize -= sizeof(chunkLength);
if (chunkLength > dataSize) {
qDebug() << "UNEXPECTED chunk size of:" << chunkLength
<< "greater than remaining length:" << dataSize;
break;
}
if (chunkLength > MAX_CHUNK_LENGTH) {
qDebug() << "UNEXPECTED chunk size of:" << chunkLength
<< "greater than MAX_CHUNK_LENGTH:" << MAX_CHUNK_LENGTH;
break;
}
ReadBitstreamToTreeParams args(WANT_COLOR, NO_EXISTS_BITS, NULL, 0,
SharedNodePointer(), false, gotVersion);
tree.readBitstreamToTree(dataAt, chunkLength, args);
dataAt += chunkLength;
dataSize -= chunkLength;
}
}
tree.recurseTreeWithOperation(addMeshVoxelsOperation, &mesh);
geometry.meshes.append(mesh);

View file

@ -83,11 +83,75 @@ PacketVersion versionForPacketType(PacketType type) {
return 1;
case PacketTypeMetavoxelData:
return 3;
case PacketTypeVoxelData:
return VERSION_VOXELS_HAS_FILE_BREAKS;
default:
return 0;
}
}
#define PACKET_TYPE_NAME_LOOKUP(x) case x: return QString(#x);
QString nameForPacketType(PacketType type) {
switch (type) {
PACKET_TYPE_NAME_LOOKUP(PacketTypeUnknown);
PACKET_TYPE_NAME_LOOKUP(PacketTypeStunResponse);
PACKET_TYPE_NAME_LOOKUP(PacketTypeDomainList);
PACKET_TYPE_NAME_LOOKUP(PacketTypePing);
PACKET_TYPE_NAME_LOOKUP(PacketTypePingReply);
PACKET_TYPE_NAME_LOOKUP(PacketTypeKillAvatar);
PACKET_TYPE_NAME_LOOKUP(PacketTypeAvatarData);
PACKET_TYPE_NAME_LOOKUP(PacketTypeInjectAudio);
PACKET_TYPE_NAME_LOOKUP(PacketTypeMixedAudio);
PACKET_TYPE_NAME_LOOKUP(PacketTypeMicrophoneAudioNoEcho);
PACKET_TYPE_NAME_LOOKUP(PacketTypeMicrophoneAudioWithEcho);
PACKET_TYPE_NAME_LOOKUP(PacketTypeBulkAvatarData);
PACKET_TYPE_NAME_LOOKUP(PacketTypeSilentAudioFrame);
PACKET_TYPE_NAME_LOOKUP(PacketTypeEnvironmentData);
PACKET_TYPE_NAME_LOOKUP(PacketTypeDomainListRequest);
PACKET_TYPE_NAME_LOOKUP(PacketTypeRequestAssignment);
PACKET_TYPE_NAME_LOOKUP(PacketTypeCreateAssignment);
PACKET_TYPE_NAME_LOOKUP(PacketTypeDomainOAuthRequest);
PACKET_TYPE_NAME_LOOKUP(PacketTypeMuteEnvironment);
PACKET_TYPE_NAME_LOOKUP(PacketTypeAudioStreamStats);
PACKET_TYPE_NAME_LOOKUP(PacketTypeDataServerConfirm);
PACKET_TYPE_NAME_LOOKUP(PacketTypeVoxelQuery);
PACKET_TYPE_NAME_LOOKUP(PacketTypeVoxelData);
PACKET_TYPE_NAME_LOOKUP(PacketTypeVoxelSet);
PACKET_TYPE_NAME_LOOKUP(PacketTypeVoxelSetDestructive);
PACKET_TYPE_NAME_LOOKUP(PacketTypeVoxelErase);
PACKET_TYPE_NAME_LOOKUP(PacketTypeOctreeStats);
PACKET_TYPE_NAME_LOOKUP(PacketTypeJurisdiction);
PACKET_TYPE_NAME_LOOKUP(PacketTypeJurisdictionRequest);
PACKET_TYPE_NAME_LOOKUP(PacketTypeParticleQuery);
PACKET_TYPE_NAME_LOOKUP(PacketTypeParticleData);
PACKET_TYPE_NAME_LOOKUP(PacketTypeParticleAddOrEdit);
PACKET_TYPE_NAME_LOOKUP(PacketTypeParticleErase);
PACKET_TYPE_NAME_LOOKUP(PacketTypeParticleAddResponse);
PACKET_TYPE_NAME_LOOKUP(PacketTypeMetavoxelData);
PACKET_TYPE_NAME_LOOKUP(PacketTypeAvatarIdentity);
PACKET_TYPE_NAME_LOOKUP(PacketTypeAvatarBillboard);
PACKET_TYPE_NAME_LOOKUP(PacketTypeDomainConnectRequest);
PACKET_TYPE_NAME_LOOKUP(PacketTypeDomainServerRequireDTLS);
PACKET_TYPE_NAME_LOOKUP(PacketTypeNodeJsonStats);
PACKET_TYPE_NAME_LOOKUP(PacketTypeEntityQuery);
PACKET_TYPE_NAME_LOOKUP(PacketTypeEntityData);
PACKET_TYPE_NAME_LOOKUP(PacketTypeEntityAddOrEdit);
PACKET_TYPE_NAME_LOOKUP(PacketTypeEntityErase);
PACKET_TYPE_NAME_LOOKUP(PacketTypeEntityAddResponse);
PACKET_TYPE_NAME_LOOKUP(PacketTypeOctreeDataNack);
PACKET_TYPE_NAME_LOOKUP(PacketTypeVoxelEditNack);
PACKET_TYPE_NAME_LOOKUP(PacketTypeParticleEditNack);
PACKET_TYPE_NAME_LOOKUP(PacketTypeEntityEditNack);
PACKET_TYPE_NAME_LOOKUP(PacketTypeSignedTransactionPayment);
default:
return QString("Type: ") + QString::number((int)type);
}
return QString("unexpected");
}
QByteArray byteArrayWithPopulatedHeader(PacketType type, const QUuid& connectionUUID) {
QByteArray freshByteArray(MAX_PACKET_HEADER_BYTES, 0);
freshByteArray.resize(populatePacketHeader(freshByteArray, type, connectionUUID));

View file

@ -19,7 +19,7 @@
#include "UUID.h"
// NOTE: if adding a new packet type, you can replace one marked usable or add at the end
// NOTE: if you want the name of the packet type to be available for debugging or logging, update nameForPacketType() as well
enum PacketType {
PacketTypeUnknown,
PacketTypeStunResponse,
@ -87,6 +87,7 @@ const int NUM_STATIC_HEADER_BYTES = sizeof(PacketVersion) + NUM_BYTES_RFC4122_UU
const int MAX_PACKET_HEADER_BYTES = sizeof(PacketType) + NUM_BYTES_MD5_HASH + NUM_STATIC_HEADER_BYTES;
PacketVersion versionForPacketType(PacketType type);
QString nameForPacketType(PacketType type);
const QUuid nullUUID = QUuid();
@ -116,5 +117,6 @@ const PacketVersion VERSION_ENTITIES_HAVE_ANIMATION = 1;
const PacketVersion VERSION_ROOT_ELEMENT_HAS_DATA = 2;
const PacketVersion VERSION_ENTITIES_SUPPORT_SPLIT_MTU = 3;
const PacketVersion VERSION_ENTITIES_HAS_FILE_BREAKS = VERSION_ENTITIES_SUPPORT_SPLIT_MTU;
const PacketVersion VERSION_VOXELS_HAS_FILE_BREAKS = 1;
#endif // hifi_PacketHeaders_h

View file

@ -233,15 +233,31 @@ OctreeElement* Octree::createMissingElement(OctreeElement* lastParentElement, co
}
}
int Octree::readElementData(OctreeElement* destinationElement, const unsigned char* nodeData, int bytesLeftToRead,
int Octree::readElementData(OctreeElement* destinationElement, const unsigned char* nodeData, int bytesAvailable,
ReadBitstreamToTreeParams& args) {
int bytesLeftToRead = bytesAvailable;
int bytesRead = 0;
// give this destination element the child mask from the packet
const unsigned char ALL_CHILDREN_ASSUMED_TO_EXIST = 0xFF;
if (bytesLeftToRead < sizeof(unsigned char)) {
qDebug() << "UNEXPECTED: readElementData() only had " << bytesLeftToRead << " bytes. Not enough for meaningful data.";
return bytesAvailable; // assume we read the entire buffer...
}
if (destinationElement->getScale() < SCALE_AT_DANGEROUSLY_DEEP_RECURSION) {
qDebug() << "UNEXPECTED: readElementData() destination element is unreasonably small ["
<< destinationElement->getScale() * (float)TREE_SCALE << " meters] "
<< " Discarding " << bytesAvailable << " remaining bytes.";
return bytesAvailable; // assume we read the entire buffer...
}
unsigned char colorInPacketMask = *nodeData;
bytesRead += sizeof(colorInPacketMask);
bytesLeftToRead -= sizeof(colorInPacketMask);
// instantiate variable for bytes already read
int bytesRead = sizeof(colorInPacketMask);
for (int i = 0; i < NUMBER_OF_CHILDREN; i++) {
// check the colors mask to see if we have a child to color in
if (oneAtBit(colorInPacketMask, i)) {
@ -256,9 +272,13 @@ int Octree::readElementData(OctreeElement* destinationElement, const unsigned ch
OctreeElement* childElementAt = destinationElement->getChildAtIndex(i);
bool nodeIsDirty = false;
if (childElementAt) {
bytesRead += childElementAt->readElementDataFromBuffer(nodeData + bytesRead, bytesLeftToRead, args);
int childElementDataRead = childElementAt->readElementDataFromBuffer(nodeData + bytesRead, bytesLeftToRead, args);
childElementAt->setSourceUUID(args.sourceUUID);
bytesRead += childElementDataRead;
bytesLeftToRead -= childElementDataRead;
// if we had a local version of the element already, it's possible that we have it already but
// with the same color data, so this won't count as a change. To address this we check the following
if (!childElementAt->isDirty() && childElementAt->getShouldRender() && !childElementAt->isRendered()) {
@ -273,17 +293,28 @@ int Octree::readElementData(OctreeElement* destinationElement, const unsigned ch
}
}
// give this destination element the child mask from the packet
unsigned char childrenInTreeMask = args.includeExistsBits ? *(nodeData + bytesRead) : ALL_CHILDREN_ASSUMED_TO_EXIST;
unsigned char childMask = *(nodeData + bytesRead + (args.includeExistsBits ? sizeof(childrenInTreeMask) : 0));
unsigned char childrenInTreeMask = ALL_CHILDREN_ASSUMED_TO_EXIST;
unsigned char childInBufferMask = 0;
int bytesForMasks = args.includeExistsBits ? sizeof(childrenInTreeMask) + sizeof(childInBufferMask)
: sizeof(childInBufferMask);
if (bytesLeftToRead < bytesForMasks) {
qDebug() << "UNEXPECTED: readElementDataFromBuffer() only had " << bytesLeftToRead << " bytes before masks. "
"Not enough for meaningful data.";
return bytesAvailable; // assume we read the entire buffer...
}
childrenInTreeMask = args.includeExistsBits ? *(nodeData + bytesRead) : ALL_CHILDREN_ASSUMED_TO_EXIST;
childInBufferMask = *(nodeData + bytesRead + (args.includeExistsBits ? sizeof(childrenInTreeMask) : 0));
int childIndex = 0;
bytesRead += args.includeExistsBits ? sizeof(childrenInTreeMask) + sizeof(childMask) : sizeof(childMask);
bytesRead += bytesForMasks;
bytesLeftToRead -= bytesForMasks;
while (bytesLeftToRead - bytesRead > 0 && childIndex < NUMBER_OF_CHILDREN) {
while (bytesLeftToRead > 0 && childIndex < NUMBER_OF_CHILDREN) {
// check the exists mask to see if we have a child to traverse into
if (oneAtBit(childMask, childIndex)) {
if (oneAtBit(childInBufferMask, childIndex)) {
if (!destinationElement->getChildAtIndex(childIndex)) {
// add a child at that index, if it doesn't exist
destinationElement->addChildAtIndex(childIndex);
@ -294,8 +325,11 @@ int Octree::readElementData(OctreeElement* destinationElement, const unsigned ch
}
// tell the child to read the subsequent data
bytesRead += readElementData(destinationElement->getChildAtIndex(childIndex),
nodeData + bytesRead, bytesLeftToRead - bytesRead, args);
int lowerLevelBytes = readElementData(destinationElement->getChildAtIndex(childIndex),
nodeData + bytesRead, bytesLeftToRead, args);
bytesRead += lowerLevelBytes;
bytesLeftToRead -= lowerLevelBytes;
}
childIndex++;
}
@ -314,7 +348,9 @@ int Octree::readElementData(OctreeElement* destinationElement, const unsigned ch
// if this is the root, and there is more data to read, allow it to read it's element data...
if (destinationElement == _rootElement && rootElementHasData() && (bytesLeftToRead - bytesRead) > 0) {
// tell the element to read the subsequent data
bytesRead += _rootElement->readElementDataFromBuffer(nodeData + bytesRead, bytesLeftToRead - bytesRead, args);
int rootDataSize = _rootElement->readElementDataFromBuffer(nodeData + bytesRead, bytesLeftToRead - bytesRead, args);
bytesRead += rootDataSize;
bytesLeftToRead -= rootDataSize;
}
return bytesRead;
@ -322,7 +358,6 @@ int Octree::readElementData(OctreeElement* destinationElement, const unsigned ch
void Octree::readBitstreamToTree(const unsigned char * bitstream, unsigned long int bufferSizeBytes,
ReadBitstreamToTreeParams& args) {
int bytesRead = 0;
const unsigned char* bitstreamAt = bitstream;
@ -337,9 +372,18 @@ void Octree::readBitstreamToTree(const unsigned char * bitstream, unsigned long
while (bitstreamAt < bitstream + bufferSizeBytes) {
OctreeElement* bitstreamRootElement = nodeForOctalCode(args.destinationElement, (unsigned char *)bitstreamAt, NULL);
if (*bitstreamAt != *bitstreamRootElement->getOctalCode()) {
// if the octal code returned is not on the same level as
// the code being searched for, we have OctreeElements to create
int numberOfThreeBitSectionsInStream = numberOfThreeBitSectionsInCode(bitstreamAt, bufferSizeBytes);
if (numberOfThreeBitSectionsInStream == OVERFLOWED_OCTCODE_BUFFER) {
qDebug() << "UNEXPECTED: parsing of the octal code would overflow the buffer. This buffer is corrupt. Returning.";
return;
}
int numberOfThreeBitSectionsFromNode = numberOfThreeBitSectionsInCode(bitstreamRootElement->getOctalCode());
// if the octal code returned is not on the same level as the code being searched for, we have OctreeElements to create
if (numberOfThreeBitSectionsInStream != numberOfThreeBitSectionsFromNode) {
// Note: we need to create this element relative to root, because we're assuming that the bitstream for the initial
// octal code is always relative to root!
@ -349,16 +393,18 @@ void Octree::readBitstreamToTree(const unsigned char * bitstream, unsigned long
}
}
int octalCodeBytes = bytesRequiredForCodeLength(*bitstreamAt);
int octalCodeBytes = bytesRequiredForCodeLength(numberOfThreeBitSectionsInStream);
int theseBytesRead = 0;
theseBytesRead += octalCodeBytes;
theseBytesRead += readElementData(bitstreamRootElement, bitstreamAt + octalCodeBytes,
int lowerLevelBytes = readElementData(bitstreamRootElement, bitstreamAt + octalCodeBytes,
bufferSizeBytes - (bytesRead + octalCodeBytes), args);
theseBytesRead += lowerLevelBytes;
// skip bitstream to new startPoint
bitstreamAt += theseBytesRead;
bytesRead += theseBytesRead;
bytesRead += theseBytesRead;
if (args.wantImportProgress) {
emit importProgress((100 * (bitstreamAt - bitstream)) / bufferSizeBytes);
@ -1004,7 +1050,7 @@ int Octree::encodeTreeBitstream(OctreeElement* element,
params.stopReason = EncodeBitstreamParams::DIDNT_FIT;
return bytesWritten;
}
bytesWritten += codeLength; // keep track of byte count
int currentEncodeLevel = 0;
@ -1421,6 +1467,7 @@ int Octree::encodeTreeBitstreamRecursion(OctreeElement* element,
// We wil write this bit mask but we may come back later and update the bits that are actually included
packetData->releaseReservedBytes(sizeof(childrenDataBits));
continueThisLevel = packetData->appendBitMask(childrenDataBits);
int childDataBitsPlaceHolder = packetData->getUncompressedByteOffset(sizeof(childrenDataBits));
unsigned char actualChildrenDataBits = 0;
@ -1470,6 +1517,13 @@ int Octree::encodeTreeBitstreamRecursion(OctreeElement* element,
// Continue this level so long as some part of this child element was appended.
bool childFit = (childAppendState != OctreeElement::NONE);
// some datatypes (like Voxels) assume that all child data will fit, if it doesn't fit
// the data type wants to bail on this element level completely
if (!childFit && mustIncludeAllChildData()) {
continueThisLevel = false;
break;
}
// If the child was partially or fully appended, then mark the actualChildrenDataBits as including
// this child data
if (childFit) {
@ -1500,15 +1554,14 @@ int Octree::encodeTreeBitstreamRecursion(OctreeElement* element,
}
}
if (!continueThisLevel) {
if (!mustIncludeAllChildData() && !continueThisLevel) {
qDebug() << "WARNING UNEXPECTED CASE: reached end of child element data loop with continueThisLevel=FALSE";
qDebug() << "This is not expected!!!! -- continueThisLevel=FALSE....";
}
if (actualChildrenDataBits != childrenDataBits) {
if (continueThisLevel && actualChildrenDataBits != childrenDataBits) {
// repair the child data mask
continueThisLevel = packetData->updatePriorBitMask(childDataBitsPlaceHolder, actualChildrenDataBits);
if (!continueThisLevel) {
qDebug() << "WARNING UNEXPECTED CASE: Failed to update childDataBitsPlaceHolder";
qDebug() << "This is not expected!!!! -- continueThisLevel=FALSE....";
@ -1764,17 +1817,22 @@ int Octree::encodeTreeBitstreamRecursion(OctreeElement* element,
continueThisLevel = packetData->endLevel(thisLevelKey);
} else {
packetData->discardLevel(thisLevelKey);
qDebug() << "WARNING UNEXPECTED CASE: Something failed in attempting to pack this element";
qDebug() << "This is not expected!!!! -- continueThisLevel=FALSE....";
if (!mustIncludeAllChildData()) {
qDebug() << "WARNING UNEXPECTED CASE: Something failed in attempting to pack this element";
qDebug() << "This is not expected!!!! -- continueThisLevel=FALSE....";
}
}
// This happens if the element could not be written at all. In the case of Octree's that support partial
// element data, continueThisLevel will be true. So this only happens if the full element needs to be
// added back to the element bag.
if (!continueThisLevel) {
qDebug() << "WARNING UNEXPECTED CASE - Something failed in attempting to pack this element";
qDebug() << "IS THIS EVER EXPECTED???? -- continueThisLevel=FALSE...." ;
qDebug() << " calling bag.insert(element);.....";
if (!mustIncludeAllChildData()) {
qDebug() << "WARNING UNEXPECTED CASE - Something failed in attempting to pack this element.";
qDebug() << " If the datatype requires all child data, then this might happen. Otherwise" ;
qDebug() << " this is an unexpected case and we should research a potential logic error." ;
}
bag.insert(element);
@ -1826,6 +1884,10 @@ bool Octree::readFromSVOFile(const char* fileName) {
bool wantImportProgress = true;
PacketType expectedType = expectedDataPacketType();
PacketVersion expectedVersion = versionForPacketType(expectedType);
bool hasBufferBreaks = versionHasSVOfileBreaks(expectedVersion);
// before reading the file, check to see if this version of the Octree supports file versions
if (getWantSVOfileVersions()) {
@ -1840,15 +1902,26 @@ bool Octree::readFromSVOFile(const char* fileName) {
unsigned long dataLength = HEADER_LENGTH;
// if so, read the first byte of the file and see if it matches the expected version code
PacketType expectedType = expectedDataPacketType();
PacketType gotType;
memcpy(&gotType, dataAt, sizeof(gotType));
dataAt += sizeof(expectedType);
dataLength -= sizeof(expectedType);
gotVersion = *dataAt;
// NOTE: SPECIAL CASE for old voxel svo files. The old voxel SVO files didn't have header
// details. They started with the the octalcode for the root. Which was always 00 which matches PacketTypeUnknown
unsigned char* firstByteAt = (unsigned char*)&fileHeader;
unsigned char firstByteValue = *firstByteAt;
if (expectedType == PacketTypeVoxelData && firstByteValue == 0) {
gotType = PacketTypeVoxelData;
gotVersion = 0;
qDebug() << "Detected OLD Voxels format.";
headerLength = 0; // old format files don't have headers
file.seekg( 0, std::ios::beg ); // rewind to the beginning so old logic will work
}
if (gotType == expectedType) {
dataAt += sizeof(expectedType);
dataLength -= sizeof(expectedType);
gotVersion = *dataAt;
if (canProcessVersion(gotVersion)) {
dataAt += sizeof(gotVersion);
dataLength -= sizeof(gotVersion);
@ -1857,25 +1930,25 @@ bool Octree::readFromSVOFile(const char* fileName) {
versionForPacketType(expectedDataPacketType()), gotVersion);
hasBufferBreaks = versionHasSVOfileBreaks(gotVersion);
if (hasBufferBreaks) {
qDebug() << " this version includes buffer breaks";
} else {
qDebug() << " this version does not include buffer breaks";
}
} else {
qDebug("SVO file version mismatch. Expected: %d Got: %d",
versionForPacketType(expectedDataPacketType()), gotVersion);
}
} else {
qDebug("SVO file type mismatch. Expected: %c Got: %c", expectedType, gotType);
qDebug() << "SVO file type mismatch. Expected: " << nameForPacketType(expectedType)
<< " Got: " << nameForPacketType(gotType);
}
} else {
qDebug() << " NOTE: this file type does not include type and version information.";
fileOk = true; // assume the file is ok
}
if (hasBufferBreaks) {
qDebug() << " this version includes buffer breaks";
} else {
qDebug() << " this version does not include buffer breaks";
}
if (fileOk) {
@ -1906,7 +1979,6 @@ bool Octree::readFromSVOFile(const char* fileName) {
quint16 chunkLength = 0;
file.read((char*)&chunkLength, sizeof(chunkLength)); // read the chunk size from the file
remainingLength -= sizeof(chunkLength);
if (chunkLength > remainingLength) {
@ -1942,6 +2014,7 @@ bool Octree::readFromSVOFile(const char* fileName) {
file.close();
}
return fileOk;
}
@ -1951,25 +2024,25 @@ void Octree::writeToSVOFile(const char* fileName, OctreeElement* element) {
if(file.is_open()) {
qDebug("Saving to file %s...", fileName);
bool hasBufferBreaks = false;
PacketType expectedType = expectedDataPacketType();
PacketVersion expectedVersion = versionForPacketType(expectedType);
bool hasBufferBreaks = versionHasSVOfileBreaks(expectedVersion);
// before reading the file, check to see if this version of the Octree supports file versions
if (getWantSVOfileVersions()) {
// if so, read the first byte of the file and see if it matches the expected version code
PacketType expectedType = expectedDataPacketType();
PacketVersion expectedVersion = versionForPacketType(expectedType);
file.write(reinterpret_cast<char*>(&expectedType), sizeof(expectedType));
file.write(&expectedVersion, sizeof(expectedVersion));
qDebug("SVO file type: %c version: %d", expectedType, expectedVersion);
qDebug() << "SVO file type: " << nameForPacketType(expectedType) << " version: " << (int)expectedVersion;
hasBufferBreaks = versionHasSVOfileBreaks(expectedVersion);
if (hasBufferBreaks) {
qDebug() << " this version includes buffer breaks";
} else {
qDebug() << " this version does not include buffer breaks";
}
}
if (hasBufferBreaks) {
qDebug() << " this version includes buffer breaks";
} else {
qDebug() << " this version does not include buffer breaks";
}
OctreeElementBag elementBag;
OctreeElementExtraEncodeData extraEncodeData;

View file

@ -238,6 +238,7 @@ public:
virtual int minimumRequiredRootDataBytes() const { return 0; }
virtual bool suppressEmptySubtrees() const { return true; }
virtual void releaseSceneEncodeData(OctreeElementExtraEncodeData* extraEncodeData) const { }
virtual bool mustIncludeAllChildData() const { return true; }
/// some versions of the SVO file will include breaks with buffer lengths between each buffer chunk in the SVO
/// file. If the Octree subclass expects this for this particular version of the file, it should override this
@ -352,6 +353,7 @@ public:
bool getIsClient() const { return !_isServer; } /// Is this a client based tree. Allows guards for certain operations
void setIsClient(bool isClient) { _isServer = !isClient; }
virtual void dumpTree() { };
signals:
void importSize(float x, float y, float z);

View file

@ -37,6 +37,8 @@ const float VIEW_FRUSTUM_FOV_OVERSEND = 60.0f;
// These are guards to prevent our voxel tree recursive routines from spinning out of control
const int UNREASONABLY_DEEP_RECURSION = 20; // use this for something that you want to be shallow, but not spin out
const int DANGEROUSLY_DEEP_RECURSION = 200; // use this for something that needs to go deeper
const float SCALE_AT_UNREASONABLY_DEEP_RECURSION = (1.0f / powf(2.0f, UNREASONABLY_DEEP_RECURSION));
const float SCALE_AT_DANGEROUSLY_DEEP_RECURSION = (1.0f / powf(2.0f, DANGEROUSLY_DEEP_RECURSION));
const int DEFAULT_MAX_OCTREE_PPS = 600; // the default maximum PPS we think any octree based server should send to a client

View file

@ -11,11 +11,10 @@
#include <algorithm>
#include <QtCore/QDebug>
#include <QDebug>
#include <QImage>
#include <QRgb>
#include "VoxelTree.h"
#include "Tags.h"
@ -567,3 +566,26 @@ int VoxelTree::processEditPacketData(PacketType packetType, const unsigned char*
return 0;
}
}
class VoxelTreeDebugOperator : public RecurseOctreeOperator {
public:
virtual bool preRecursion(OctreeElement* element);
virtual bool postRecursion(OctreeElement* element) { return true; }
};
bool VoxelTreeDebugOperator::preRecursion(OctreeElement* element) {
VoxelTreeElement* treeElement = static_cast<VoxelTreeElement*>(element);
qDebug() << "VoxelTreeElement [" << treeElement << ":" << treeElement->getAACube() << "]";
qDebug() << " isLeaf:" << treeElement->isLeaf();
qDebug() << " color:" << treeElement->getColor()[0] << ", "
<< treeElement->getColor()[1] << ", "
<< treeElement->getColor()[2];
return true;
}
void VoxelTree::dumpTree() {
// First, look for the existing entity in the tree..
VoxelTreeDebugOperator theOperator;
recurseTreeWithOperator(&theOperator);
}

View file

@ -51,12 +51,30 @@ public:
void readCodeColorBufferToTree(const unsigned char* codeColorBuffer, bool destructive = false);
virtual bool getWantSVOfileVersions() const { return true; }
virtual bool canProcessVersion(PacketVersion thisVersion) const {
return thisVersion == 0 || thisVersion == versionForPacketType(expectedDataPacketType()); }
virtual PacketVersion expectedVersion() const { return versionForPacketType(expectedDataPacketType()); }
virtual PacketType expectedDataPacketType() const { return PacketTypeVoxelData; }
virtual bool handlesEditPacketType(PacketType packetType) const;
virtual int processEditPacketData(PacketType packetType, const unsigned char* packetData, int packetLength,
const unsigned char* editData, int maxLength, const SharedNodePointer& node);
virtual bool recurseChildrenWithData() const { return false; }
/// some versions of the SVO file will include breaks with buffer lengths between each buffer chunk in the SVO
/// file. If the Octree subclass expects this for this particular version of the file, it should override this
/// method and return true.
virtual bool versionHasSVOfileBreaks(PacketVersion thisVersion) const {
if (thisVersion == 0) {
return false; // old versions didn't have buffer breaks
}
return true;
}
virtual void dumpTree();
private:
// helper functions for nudgeSubTree
void recurseNodeForNudge(VoxelTreeElement* element, RecurseOctreeOperation operation, void* extraData);

View file

@ -73,6 +73,13 @@ OctreeElement::AppendState VoxelTreeElement::appendElementData(OctreePacketData*
int VoxelTreeElement::readElementDataFromBuffer(const unsigned char* data, int bytesLeftToRead,
ReadBitstreamToTreeParams& args) {
const int BYTES_PER_COLOR = 3;
if (bytesLeftToRead < BYTES_PER_COLOR) {
qDebug() << "UNEXPECTED: readElementDataFromBuffer() only had " << bytesLeftToRead << " bytes. "
"Not enough for meaningful data.";
return bytesLeftToRead;
}
// pull the color for this child
nodeColor newColor = { 128, 128, 128, 1};