mirror of
https://github.com/overte-org/overte.git
synced 2025-04-22 16:13:28 +02:00
Merge branch 'master' of https://github.com/worklist/hifi
This commit is contained in:
commit
1df7806464
15 changed files with 404 additions and 73 deletions
|
@ -167,7 +167,8 @@ int main(int argc, const char* argv[]) {
|
|||
|
||||
float minCoefficient = std::min(1.0f,
|
||||
powf(0.5,
|
||||
(logf(DISTANCE_RATIO * distanceToAgent) / logf(2)) - 1));
|
||||
(logf(DISTANCE_RATIO * distanceToAgent) / logf(2.5))
|
||||
- 1));
|
||||
distanceCoefficients[lowAgentIndex][highAgentIndex] = minCoefficient;
|
||||
}
|
||||
|
||||
|
|
|
@ -37,6 +37,8 @@
|
|||
#include <AgentTypes.h>
|
||||
#include <PacketHeaders.h>
|
||||
#include <PerfStat.h>
|
||||
#include <AudioInjectionManager.h>
|
||||
#include <AudioInjector.h>
|
||||
|
||||
#include "Application.h"
|
||||
#include "InterfaceConfig.h"
|
||||
|
@ -1667,7 +1669,8 @@ void Application::displaySide(Camera& whichCamera) {
|
|||
glPopMatrix();
|
||||
|
||||
//draw a grid ground plane....
|
||||
drawGroundPlaneGrid(10.f);
|
||||
const float EDGE_SIZE_GROUND_PLANE = 20.f;
|
||||
drawGroundPlaneGrid(EDGE_SIZE_GROUND_PLANE);
|
||||
|
||||
// Draw voxels
|
||||
if (_renderVoxels->isChecked()) {
|
||||
|
@ -1702,13 +1705,13 @@ void Application::displaySide(Camera& whichCamera) {
|
|||
for (AgentList::iterator agent = agentList->begin(); agent != agentList->end(); agent++) {
|
||||
if (agent->getLinkedData() != NULL && agent->getType() == AGENT_TYPE_AVATAR) {
|
||||
Avatar *avatar = (Avatar *)agent->getLinkedData();
|
||||
avatar->render(false);
|
||||
avatar->render(false, _myCamera.getPosition());
|
||||
}
|
||||
}
|
||||
agentList->unlock();
|
||||
|
||||
// Render my own Avatar
|
||||
_myAvatar.render(_lookingInMirror->isChecked());
|
||||
_myAvatar.render(_lookingInMirror->isChecked(), _myCamera.getPosition());
|
||||
_myAvatar.setDisplayingLookatVectors(_renderLookatOn->isChecked());
|
||||
}
|
||||
|
||||
|
@ -2012,22 +2015,77 @@ void Application::shiftPaintingColor() {
|
|||
_paintingVoxel.blue = (_dominantColor == 2) ? randIntInRange(200, 255) : randIntInRange(40, 100);
|
||||
}
|
||||
|
||||
|
||||
void Application::maybeEditVoxelUnderCursor() {
|
||||
if (_addVoxelMode->isChecked() || _colorVoxelMode->isChecked()) {
|
||||
if (_mouseVoxel.s != 0) {
|
||||
if (_mouseVoxel.s != 0) {
|
||||
PACKET_HEADER message = (_destructiveAddVoxel->isChecked() ?
|
||||
PACKET_HEADER_SET_VOXEL_DESTRUCTIVE : PACKET_HEADER_SET_VOXEL);
|
||||
sendVoxelEditMessage(message, _mouseVoxel);
|
||||
|
||||
// create the voxel locally so it appears immediately
|
||||
// create the voxel locally so it appears immediately
|
||||
_voxels.createVoxel(_mouseVoxel.x, _mouseVoxel.y, _mouseVoxel.z, _mouseVoxel.s,
|
||||
_mouseVoxel.red, _mouseVoxel.green, _mouseVoxel.blue, _destructiveAddVoxel->isChecked());
|
||||
|
||||
|
||||
// remember the position for drag detection
|
||||
_justEditedVoxel = true;
|
||||
|
||||
AudioInjector* voxelInjector = AudioInjectionManager::injectorWithCapacity(11025);
|
||||
voxelInjector->setPosition(glm::vec3(_mouseVoxel.x, _mouseVoxel.y, _mouseVoxel.z));
|
||||
//_myAvatar.getPosition()
|
||||
voxelInjector->setBearing(-1 * _myAvatar.getAbsoluteHeadYaw());
|
||||
voxelInjector->setVolume (16 * pow (_mouseVoxel.s, 2) / .0000001); //255 is max, and also default value
|
||||
// printf("mousevoxelscale is %f\n", _mouseVoxel.s);
|
||||
|
||||
/* for (int i = 0; i
|
||||
< 22050; i++) {
|
||||
if (i % 4 == 0) {
|
||||
voxelInjector->addSample(4000);
|
||||
} else if (i % 4 == 1) {
|
||||
voxelInjector->addSample(0);
|
||||
} else if (i % 4 == 2) {
|
||||
voxelInjector->addSample(-4000);
|
||||
} else {
|
||||
voxelInjector->addSample(0);
|
||||
}
|
||||
*/
|
||||
|
||||
|
||||
const float BIG_VOXEL_MIN_SIZE = .01f;
|
||||
|
||||
for (int i = 0; i < 11025; i++) {
|
||||
|
||||
/*
|
||||
A440 square wave
|
||||
if (sin(i * 2 * PIE / 50)>=0) {
|
||||
voxelInjector->addSample(4000);
|
||||
} else {
|
||||
voxelInjector->addSample(-4000);
|
||||
}
|
||||
*/
|
||||
|
||||
if (_mouseVoxel.s > BIG_VOXEL_MIN_SIZE) {
|
||||
voxelInjector->addSample(20000 * sin((i * 2 * PIE) / (500 * sin((i + 1) / 200))));
|
||||
} else {
|
||||
voxelInjector->addSample(16000 * sin(i / (1.5 * log (_mouseVoxel.s / .0001) * ((i + 11025) / 5512.5)))); //808
|
||||
}
|
||||
}
|
||||
|
||||
//voxelInjector->addSample(32500 * sin(i/(2 * 1 * ((i+5000)/5512.5)))); //80
|
||||
//voxelInjector->addSample(20000 * sin(i/(6 * (_mouseVoxel.s/.001) *((i+5512.5)/5512.5)))); //808
|
||||
//voxelInjector->addSample(20000 * sin(i/(6 * ((i+5512.5)/5512.5)))); //808
|
||||
//voxelInjector->addSample(4000 * sin(i * 2 * PIE /50)); //A440 sine wave
|
||||
//voxelInjector->addSample(4000 * sin(i * 2 * PIE /50) * sin (i/500)); //A440 sine wave with amplitude modulation
|
||||
|
||||
//FM library
|
||||
//voxelInjector->addSample(20000 * sin((i * 2 * PIE) /(500*sin((i+1)/200)))); //FM 1 dubstep
|
||||
//voxelInjector->addSample(20000 * sin((i * 2 * PIE) /(300*sin((i+1)/5.0)))); //FM 2 flange sweep
|
||||
//voxelInjector->addSample(10000 * sin((i * 2 * PIE) /(500*sin((i+1)/500.0)))); //FM 3 resonant pulse
|
||||
|
||||
AudioInjectionManager::threadInjector(voxelInjector);
|
||||
}
|
||||
} else if (_deleteVoxelMode->isChecked()) {
|
||||
deleteVoxelUnderCursor();
|
||||
deleteVoxelUnderCursor();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -2035,10 +2093,21 @@ void Application::deleteVoxelUnderCursor() {
|
|||
if (_mouseVoxel.s != 0) {
|
||||
// sending delete to the server is sufficient, server will send new version so we see updates soon enough
|
||||
sendVoxelEditMessage(PACKET_HEADER_ERASE_VOXEL, _mouseVoxel);
|
||||
AudioInjector* voxelInjector = AudioInjectionManager::injectorWithCapacity(5000);
|
||||
voxelInjector->setPosition(glm::vec3(_mouseVoxel.x, _mouseVoxel.y, _mouseVoxel.z));
|
||||
voxelInjector->setBearing(0); //straight down the z axis
|
||||
voxelInjector->setVolume (255); //255 is max, and also default value
|
||||
|
||||
// remember the position for drag detection
|
||||
_justEditedVoxel = true;
|
||||
|
||||
for (int i = 0; i < 5000; i++) {
|
||||
voxelInjector->addSample(10000 * sin((i * 2 * PIE) / (500 * sin((i + 1) / 500.0)))); //FM 3 resonant pulse
|
||||
// voxelInjector->addSample(20000 * sin((i) /((4 / _mouseVoxel.s) * sin((i)/(20 * _mouseVoxel.s / .001))))); //FM 2 comb filter
|
||||
}
|
||||
|
||||
AudioInjectionManager::threadInjector(voxelInjector);
|
||||
}
|
||||
// remember the position for drag detection
|
||||
_justEditedVoxel = true;
|
||||
}
|
||||
|
||||
void Application::goHome() {
|
||||
|
|
|
@ -80,6 +80,7 @@ Avatar::Avatar(Agent* owningAgent) :
|
|||
_bodyRollDelta(0.0f),
|
||||
_movedHandOffset(0.0f, 0.0f, 0.0f),
|
||||
_rotation(0.0f, 0.0f, 0.0f, 0.0f),
|
||||
_cameraPosition(0.0f, 0.0f, 0.0f),
|
||||
_mode(AVATAR_MODE_STANDING),
|
||||
_handHoldingPosition(0.0f, 0.0f, 0.0f),
|
||||
_velocity(0.0f, 0.0f, 0.0f),
|
||||
|
@ -300,7 +301,6 @@ void Avatar::simulate(float deltaTime, Transmitter* transmitter) {
|
|||
deltaTime *
|
||||
_orientation.getUp();
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -381,11 +381,28 @@ void Avatar::simulate(float deltaTime, Transmitter* transmitter) {
|
|||
const float ACCELERATION_PITCH_DECAY = 0.4f;
|
||||
const float ACCELERATION_YAW_DECAY = 0.4f;
|
||||
|
||||
const float OCULUS_ACCELERATION_PULL_THRESHOLD = 1.0f;
|
||||
const int OCULUS_YAW_OFFSET_THRESHOLD = 10;
|
||||
|
||||
// Decay HeadPitch as a function of acceleration, so that you look straight ahead when
|
||||
// you start moving, but don't do this with an HMD like the Oculus.
|
||||
if (!OculusManager::isConnected()) {
|
||||
_head.setPitch(_head.getPitch() * (1.f - acceleration * ACCELERATION_PITCH_DECAY * deltaTime));
|
||||
_head.setYaw(_head.getYaw() * (1.f - acceleration * ACCELERATION_YAW_DECAY * deltaTime));
|
||||
} else if (fabsf(acceleration) > OCULUS_ACCELERATION_PULL_THRESHOLD
|
||||
&& fabs(_head.getYaw()) > OCULUS_YAW_OFFSET_THRESHOLD) {
|
||||
// if we're wearing the oculus
|
||||
// and this acceleration is above the pull threshold
|
||||
// and the head yaw if off the body by more than OCULUS_YAW_OFFSET_THRESHOLD
|
||||
|
||||
// match the body yaw to the oculus yaw
|
||||
_bodyYaw = getAbsoluteHeadYaw();
|
||||
|
||||
// set the head yaw to zero for this draw
|
||||
_head.setYaw(0);
|
||||
|
||||
// correct the oculus yaw offset
|
||||
OculusManager::updateYawOffset();
|
||||
}
|
||||
|
||||
//apply the head lean values to the springy position...
|
||||
|
@ -729,7 +746,9 @@ void Avatar::setGravity(glm::vec3 gravity) {
|
|||
_head.setGravity(_gravity);
|
||||
}
|
||||
|
||||
void Avatar::render(bool lookingInMirror) {
|
||||
void Avatar::render(bool lookingInMirror, glm::vec3 cameraPosition) {
|
||||
|
||||
_cameraPosition = cameraPosition;
|
||||
|
||||
if (!_owningAgent && usingBigSphereCollisionTest) {
|
||||
// show TEST big sphere
|
||||
|
@ -841,6 +860,8 @@ void Avatar::initializeSkeleton() {
|
|||
_joint[ AVATAR_JOINT_RIGHT_COLLAR ].parent = AVATAR_JOINT_CHEST;
|
||||
_joint[ AVATAR_JOINT_RIGHT_SHOULDER ].parent = AVATAR_JOINT_RIGHT_COLLAR;
|
||||
_joint[ AVATAR_JOINT_RIGHT_ELBOW ].parent = AVATAR_JOINT_RIGHT_SHOULDER;
|
||||
_joint[ AVATAR_JOINT_RIGHT_WRIST ].parent = AVATAR_JOINT_RIGHT_ELBOW;
|
||||
_joint[ AVATAR_JOINT_RIGHT_FINGERTIPS ].parent = AVATAR_JOINT_RIGHT_WRIST;
|
||||
_joint[ AVATAR_JOINT_LEFT_HIP ].parent = AVATAR_JOINT_PELVIS;
|
||||
_joint[ AVATAR_JOINT_LEFT_KNEE ].parent = AVATAR_JOINT_LEFT_HIP;
|
||||
_joint[ AVATAR_JOINT_LEFT_HEEL ].parent = AVATAR_JOINT_LEFT_KNEE;
|
||||
|
@ -1136,11 +1157,11 @@ void Avatar::renderBody(bool lookingInMirror) {
|
|||
|
||||
// Render the body as balls and cones
|
||||
for (int b = 0; b < NUM_AVATAR_JOINTS; b++) {
|
||||
float distanceToCamera = glm::length(getCameraPosition() - _joint[b].position);
|
||||
float distanceToCamera = glm::length(_cameraPosition - _joint[b].position);
|
||||
// Always render other people, and render myself when beyond threshold distance
|
||||
if (b == AVATAR_JOINT_HEAD_BASE) { // the head is rendered as a special case
|
||||
if (lookingInMirror || _owningAgent || distanceToCamera > RENDER_OPAQUE_BEYOND) {
|
||||
_head.render(lookingInMirror);
|
||||
_head.render(lookingInMirror, _cameraPosition);
|
||||
}
|
||||
} else if (_owningAgent || distanceToCamera > RENDER_TRANSLUCENT_BEYOND
|
||||
|| b == AVATAR_JOINT_RIGHT_ELBOW
|
||||
|
|
|
@ -107,7 +107,7 @@ public:
|
|||
Head& getHead() { return _head; }
|
||||
|
||||
void setMousePressed(bool pressed);
|
||||
void render(bool lookingInMirror);
|
||||
void render(bool lookingInMirror, glm::vec3 cameraPosition);
|
||||
void renderBody(bool lookingInMirror);
|
||||
void simulate(float deltaTime, Transmitter* transmitter);
|
||||
void setMovedHandOffset(glm::vec3 movedHandOffset) { _movedHandOffset = movedHandOffset; }
|
||||
|
@ -161,6 +161,7 @@ private:
|
|||
glm::quat _rotation; // the rotation of the avatar body as a whole expressed as a quaternion
|
||||
AvatarJoint _joint[ NUM_AVATAR_JOINTS ];
|
||||
AvatarMode _mode;
|
||||
glm::vec3 _cameraPosition;
|
||||
glm::vec3 _handHoldingPosition;
|
||||
glm::vec3 _velocity;
|
||||
glm::vec3 _thrust;
|
||||
|
|
|
@ -23,6 +23,13 @@ const float HEAD_MOTION_DECAY = 0.1;
|
|||
const float MINIMUM_EYE_ROTATION_DOT = 0.5f; // based on a dot product: 1.0 is straight ahead, 0.0 is 90 degrees off
|
||||
const float EYEBALL_RADIUS = 0.017;
|
||||
const float EYEBALL_COLOR[3] = { 0.9f, 0.9f, 0.8f };
|
||||
const float HAIR_COLOR[3] = { 0.8f, 0.6f, 0.5f };
|
||||
const float HAIR_SPRING_FORCE = 10.0f;
|
||||
const float HAIR_TORQUE_FORCE = 0.1f;
|
||||
const float HAIR_GRAVITY_FORCE = 0.05f;
|
||||
const float HAIR_DRAG = 10.0f;
|
||||
const float HAIR_LENGTH = 0.09f;
|
||||
const float HAIR_THICKNESS = 0.03f;
|
||||
const float IRIS_RADIUS = 0.007;
|
||||
const float IRIS_PROTRUSION = 0.0145f;
|
||||
const char IRIS_TEXTURE_FILENAME[] = "resources/images/iris.png";
|
||||
|
@ -55,18 +62,37 @@ Head::Head(Avatar* owningAvatar) :
|
|||
_audioAttack(0.0f),
|
||||
_returnSpringScale(1.0f),
|
||||
_bodyRotation(0.0f, 0.0f, 0.0f),
|
||||
_renderLookatVectors(false),
|
||||
_mohawkTriangleFan(NULL),
|
||||
_mohawkColors(NULL)
|
||||
{
|
||||
_mohawkColors(NULL),
|
||||
_renderLookatVectors(false) {
|
||||
|
||||
for (int t = 0; t < NUM_HAIR_TUFTS; t ++) {
|
||||
_hairTuft[t].length = HAIR_LENGTH;
|
||||
_hairTuft[t].thickness = HAIR_THICKNESS;
|
||||
_hairTuft[t].basePosition = glm::vec3(0.0f, 0.0f, 0.0f);
|
||||
|
||||
_hairTuft[t].basePosition = glm::vec3(0.0f, 0.0f, 0.0f);
|
||||
_hairTuft[t].midPosition = glm::vec3(0.0f, 0.0f, 0.0f);
|
||||
_hairTuft[t].endPosition = glm::vec3(0.0f, 0.0f, 0.0f);
|
||||
_hairTuft[t].midVelocity = glm::vec3(0.0f, 0.0f, 0.0f);
|
||||
_hairTuft[t].endVelocity = glm::vec3(0.0f, 0.0f, 0.0f);
|
||||
}
|
||||
}
|
||||
|
||||
void Head::reset() {
|
||||
_yaw = _pitch = _roll = 0.0f;
|
||||
_leanForward = _leanSideways = 0.0f;
|
||||
|
||||
for (int t = 0; t < NUM_HAIR_TUFTS; t ++) {
|
||||
|
||||
_hairTuft[t].basePosition = _position + _orientation.getUp() * _scale * 0.9f;
|
||||
_hairTuft[t].midPosition = _hairTuft[t].basePosition + _orientation.getUp() * _hairTuft[t].length * ONE_HALF;
|
||||
_hairTuft[t].endPosition = _hairTuft[t].midPosition + _orientation.getUp() * _hairTuft[t].length * ONE_HALF;
|
||||
_hairTuft[t].midVelocity = glm::vec3(0.0f, 0.0f, 0.0f);
|
||||
_hairTuft[t].endVelocity = glm::vec3(0.0f, 0.0f, 0.0f);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void Head::simulate(float deltaTime, bool isMine) {
|
||||
|
||||
const float HEAD_MOTION_DECAY = 0.00;
|
||||
|
@ -113,7 +139,9 @@ void Head::simulate(float deltaTime, bool isMine) {
|
|||
_browAudioLift *= 0.7f;
|
||||
|
||||
// based on the nature of the lookat position, determine if the eyes can look / are looking at it.
|
||||
determineIfLookingAtSomething();
|
||||
determineIfLookingAtSomething();
|
||||
|
||||
updateHair(deltaTime);
|
||||
}
|
||||
|
||||
void Head::determineIfLookingAtSomething() {
|
||||
|
@ -172,7 +200,7 @@ void Head::calculateGeometry(bool lookingInMirror) {
|
|||
}
|
||||
|
||||
|
||||
void Head::render(bool lookingInMirror) {
|
||||
void Head::render(bool lookingInMirror, glm::vec3 cameraPosition) {
|
||||
|
||||
calculateGeometry(lookingInMirror);
|
||||
|
||||
|
@ -185,6 +213,7 @@ void Head::render(bool lookingInMirror) {
|
|||
renderEars();
|
||||
renderMouth();
|
||||
renderEyeBrows();
|
||||
renderHair(cameraPosition);
|
||||
|
||||
if (_renderLookatVectors && _lookingAtSomething) {
|
||||
renderLookatVectors(_leftEyePosition, _rightEyePosition, _lookAtPosition);
|
||||
|
@ -210,6 +239,7 @@ void Head::createMohawk() {
|
|||
_mohawkTriangleFan[0] = glm::vec3(0, 0, 0);
|
||||
glm::vec3 basicColor(randFloat(), randFloat(), randFloat());
|
||||
_mohawkColors[0] = basicColor;
|
||||
|
||||
for (int i = 1; i < MOHAWK_TRIANGLES; i++) {
|
||||
_mohawkTriangleFan[i] = glm::vec3((randFloat() - 0.5f) * variance,
|
||||
height * cosf(i * RAD_PER_TRIANGLE - PI / 2.f)
|
||||
|
@ -229,7 +259,7 @@ void Head::renderMohawk(bool lookingInMirror) {
|
|||
glTranslatef(_position.x, _position.y, _position.z);
|
||||
glRotatef((lookingInMirror ? (_bodyRotation.y - _yaw) : (_bodyRotation.y + _yaw)), 0, 1, 0);
|
||||
glRotatef(lookingInMirror ? _roll: -_roll, 0, 0, 1);
|
||||
glRotatef(-_pitch, 1, 0, 0);
|
||||
glRotatef(-_pitch - _bodyRotation.x, 1, 0, 0);
|
||||
|
||||
glBegin(GL_TRIANGLE_FAN);
|
||||
for (int i = 0; i < MOHAWK_TRIANGLES; i++) {
|
||||
|
@ -281,6 +311,15 @@ void Head::renderMouth() {
|
|||
glm::vec3 rightTop = _mouthPosition + r * 0.4f + u * 0.7f + f;
|
||||
glm::vec3 leftBottom = _mouthPosition - r * 0.4f - u * 1.0f + f * 0.7f;
|
||||
glm::vec3 rightBottom = _mouthPosition + r * 0.4f - u * 1.0f + f * 0.7f;
|
||||
|
||||
// constrain all mouth vertices to a sphere slightly larger than the head...
|
||||
float constrainedRadius = _scale + 0.001f;
|
||||
leftCorner = _position + glm::normalize(leftCorner - _position) * constrainedRadius;
|
||||
rightCorner = _position + glm::normalize(rightCorner - _position) * constrainedRadius;
|
||||
leftTop = _position + glm::normalize(leftTop - _position) * constrainedRadius;
|
||||
rightTop = _position + glm::normalize(rightTop - _position) * constrainedRadius;
|
||||
leftBottom = _position + glm::normalize(leftBottom - _position) * constrainedRadius;
|
||||
rightBottom = _position + glm::normalize(rightBottom - _position) * constrainedRadius;
|
||||
|
||||
glColor3f(0.2f, 0.0f, 0.0f);
|
||||
|
||||
|
@ -487,4 +526,133 @@ void Head::renderLookatVectors(glm::vec3 leftEyePosition, glm::vec3 rightEyePosi
|
|||
}
|
||||
|
||||
|
||||
void Head::updateHair(float deltaTime) {
|
||||
|
||||
for (int t = 0; t < NUM_HAIR_TUFTS; t ++) {
|
||||
|
||||
float fraction = (float)t / (float)(NUM_HAIR_TUFTS - 1);
|
||||
|
||||
float angle = -20.0f + 40.0f * fraction;
|
||||
|
||||
float radian = angle * PI_OVER_180;
|
||||
glm::vec3 baseDirection
|
||||
= _orientation.getFront() * sinf(radian)
|
||||
+ _orientation.getUp() * cosf(radian);
|
||||
|
||||
_hairTuft[t].basePosition = _position + _scale * 0.9f * baseDirection;
|
||||
|
||||
glm::vec3 midAxis = _hairTuft[t].midPosition - _hairTuft[t].basePosition;
|
||||
glm::vec3 endAxis = _hairTuft[t].endPosition - _hairTuft[t].midPosition;
|
||||
|
||||
float midLength = glm::length(midAxis);
|
||||
float endLength = glm::length(endAxis);
|
||||
|
||||
glm::vec3 midDirection;
|
||||
glm::vec3 endDirection;
|
||||
|
||||
if (midLength > 0.0f) {
|
||||
midDirection = midAxis / midLength;
|
||||
} else {
|
||||
midDirection = _orientation.getUp();
|
||||
}
|
||||
|
||||
if (endLength > 0.0f) {
|
||||
endDirection = endAxis / endLength;
|
||||
} else {
|
||||
endDirection = _orientation.getUp();
|
||||
}
|
||||
|
||||
// add spring force
|
||||
float midForce = midLength - _hairTuft[t].length * ONE_HALF;
|
||||
float endForce = endLength - _hairTuft[t].length * ONE_HALF;
|
||||
_hairTuft[t].midVelocity -= midDirection * midForce * HAIR_SPRING_FORCE * deltaTime;
|
||||
_hairTuft[t].endVelocity -= endDirection * endForce * HAIR_SPRING_FORCE * deltaTime;
|
||||
|
||||
// add gravity force
|
||||
glm::vec3 gravityForce = _gravity * HAIR_GRAVITY_FORCE * deltaTime;
|
||||
_hairTuft[t].midVelocity += gravityForce;
|
||||
_hairTuft[t].endVelocity += gravityForce;
|
||||
|
||||
// add torque force
|
||||
_hairTuft[t].midVelocity += baseDirection * HAIR_TORQUE_FORCE * deltaTime;
|
||||
_hairTuft[t].endVelocity += midDirection * HAIR_TORQUE_FORCE * deltaTime;
|
||||
|
||||
// add drag force
|
||||
float momentum = 1.0f - (HAIR_DRAG * deltaTime);
|
||||
if (momentum < 0.0f) {
|
||||
_hairTuft[t].midVelocity = glm::vec3(0.0f, 0.0f, 0.0f);
|
||||
_hairTuft[t].endVelocity = glm::vec3(0.0f, 0.0f, 0.0f);
|
||||
} else {
|
||||
_hairTuft[t].midVelocity *= momentum;
|
||||
_hairTuft[t].endVelocity *= momentum;
|
||||
}
|
||||
|
||||
// update position by velocity
|
||||
_hairTuft[t].midPosition += _hairTuft[t].midVelocity;
|
||||
_hairTuft[t].endPosition += _hairTuft[t].endVelocity;
|
||||
|
||||
// clamp lengths
|
||||
glm::vec3 newMidVector = _hairTuft[t].midPosition - _hairTuft[t].basePosition;
|
||||
glm::vec3 newEndVector = _hairTuft[t].endPosition - _hairTuft[t].midPosition;
|
||||
|
||||
float newMidLength = glm::length(newMidVector);
|
||||
float newEndLength = glm::length(newEndVector);
|
||||
|
||||
glm::vec3 newMidDirection;
|
||||
glm::vec3 newEndDirection;
|
||||
|
||||
if (newMidLength > 0.0f) {
|
||||
newMidDirection = newMidVector/newMidLength;
|
||||
} else {
|
||||
newMidDirection = _orientation.getUp();
|
||||
}
|
||||
|
||||
if (newEndLength > 0.0f) {
|
||||
newEndDirection = newEndVector/newEndLength;
|
||||
} else {
|
||||
newEndDirection = _orientation.getUp();
|
||||
}
|
||||
|
||||
_hairTuft[t].endPosition = _hairTuft[t].midPosition + newEndDirection * _hairTuft[t].length * ONE_HALF;
|
||||
_hairTuft[t].midPosition = _hairTuft[t].basePosition + newMidDirection * _hairTuft[t].length * ONE_HALF;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
void Head::renderHair(glm::vec3 cameraPosition) {
|
||||
|
||||
for (int t = 0; t < NUM_HAIR_TUFTS; t ++) {
|
||||
|
||||
glm::vec3 baseAxis = _hairTuft[t].midPosition - _hairTuft[t].basePosition;
|
||||
glm::vec3 midAxis = _hairTuft[t].endPosition - _hairTuft[t].midPosition;
|
||||
glm::vec3 viewVector = _hairTuft[t].basePosition - cameraPosition;
|
||||
|
||||
glm::vec3 basePerpendicular = glm::normalize(glm::cross(baseAxis, viewVector));
|
||||
glm::vec3 midPerpendicular = glm::normalize(glm::cross(midAxis, viewVector));
|
||||
|
||||
glm::vec3 base1 = _hairTuft[t].basePosition - basePerpendicular * _hairTuft[t].thickness * ONE_HALF;
|
||||
glm::vec3 base2 = _hairTuft[t].basePosition + basePerpendicular * _hairTuft[t].thickness * ONE_HALF;
|
||||
glm::vec3 mid1 = _hairTuft[t].midPosition - midPerpendicular * _hairTuft[t].thickness * ONE_HALF * ONE_HALF;
|
||||
glm::vec3 mid2 = _hairTuft[t].midPosition + midPerpendicular * _hairTuft[t].thickness * ONE_HALF * ONE_HALF;
|
||||
|
||||
glColor3fv(HAIR_COLOR);
|
||||
|
||||
glBegin(GL_TRIANGLES);
|
||||
|
||||
glVertex3f(base1.x, base1.y, base1.z );
|
||||
glVertex3f(base2.x, base2.y, base2.z );
|
||||
glVertex3f(mid1.x, mid1.y, mid1.z );
|
||||
|
||||
glVertex3f(base2.x, base2.y, base2.z );
|
||||
glVertex3f(mid1.x, mid1.y, mid1.z );
|
||||
glVertex3f(mid2.x, mid2.y, mid2.z );
|
||||
|
||||
glVertex3f(mid1.x, mid1.y, mid1.z );
|
||||
glVertex3f(mid2.x, mid2.y, mid2.z );
|
||||
glVertex3f(_hairTuft[t].endPosition.x, _hairTuft[t].endPosition.y, _hairTuft[t].endPosition.z );
|
||||
|
||||
glEnd();
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -24,6 +24,9 @@ enum eyeContactTargets
|
|||
MOUTH
|
||||
};
|
||||
|
||||
const int NUM_HAIR_TUFTS = 4;
|
||||
const int NUM_HAIR_SEGMENTS = 4;
|
||||
|
||||
class Avatar;
|
||||
|
||||
class Head : public HeadData {
|
||||
|
@ -32,7 +35,7 @@ public:
|
|||
|
||||
void reset();
|
||||
void simulate(float deltaTime, bool isMine);
|
||||
void render(bool lookingInMirror);
|
||||
void render(bool lookingInMirror, glm::vec3 cameraPosition);
|
||||
void renderMohawk(bool lookingInMirror);
|
||||
|
||||
void setScale (float scale ) { _scale = scale; }
|
||||
|
@ -58,6 +61,18 @@ private:
|
|||
Head(const Head&);
|
||||
Head& operator= (const Head&);
|
||||
|
||||
struct HairTuft
|
||||
{
|
||||
float length;
|
||||
float thickness;
|
||||
|
||||
glm::vec3 basePosition;
|
||||
glm::vec3 midPosition;
|
||||
glm::vec3 endPosition;
|
||||
glm::vec3 midVelocity;
|
||||
glm::vec3 endVelocity;
|
||||
};
|
||||
|
||||
bool _returnHeadToCenter;
|
||||
float _audioLoudness;
|
||||
glm::vec3 _skinColor;
|
||||
|
@ -81,6 +96,7 @@ private:
|
|||
Orientation _orientation;
|
||||
glm::vec3 _bodyRotation;
|
||||
bool _renderLookatVectors;
|
||||
HairTuft _hairTuft[NUM_HAIR_TUFTS];
|
||||
glm::vec3* _mohawkTriangleFan;
|
||||
glm::vec3* _mohawkColors;
|
||||
|
||||
|
@ -94,6 +110,8 @@ private:
|
|||
void renderLookatVectors(glm::vec3 leftEyePosition, glm::vec3 rightEyePosition, glm::vec3 lookatPosition);
|
||||
void calculateGeometry( bool lookingInMirror);
|
||||
void determineIfLookingAtSomething();
|
||||
void updateHair(float deltaTime);
|
||||
void renderHair(glm::vec3 cameraPosition);
|
||||
};
|
||||
|
||||
#endif
|
||||
|
|
|
@ -16,6 +16,7 @@ Ptr<DeviceManager> OculusManager::_deviceManager;
|
|||
Ptr<HMDDevice> OculusManager::_hmdDevice;
|
||||
Ptr<SensorDevice> OculusManager::_sensorDevice;
|
||||
SensorFusion OculusManager::_sensorFusion;
|
||||
float OculusManager::_yawOffset = 0;
|
||||
#endif
|
||||
|
||||
void OculusManager::connect() {
|
||||
|
@ -36,12 +37,19 @@ void OculusManager::connect() {
|
|||
#endif
|
||||
}
|
||||
|
||||
void OculusManager::updateYawOffset() {
|
||||
float yaw, pitch, roll;
|
||||
_sensorFusion.GetOrientation().GetEulerAngles<Axis_Y, Axis_X, Axis_Z, Rotate_CCW, Handed_R>(&yaw, &pitch, &roll);
|
||||
_yawOffset = yaw;
|
||||
}
|
||||
|
||||
void OculusManager::getEulerAngles(float& yaw, float& pitch, float& roll) {
|
||||
#ifdef __APPLE__
|
||||
_sensorFusion.GetOrientation().GetEulerAngles<Axis_Y, Axis_X, Axis_Z, Rotate_CCW, Handed_R>(&yaw, &pitch, &roll);
|
||||
|
||||
// convert each angle to degrees
|
||||
yaw = glm::degrees(yaw);
|
||||
// remove the yaw offset from the returned yaw
|
||||
yaw = glm::degrees(yaw - _yawOffset);
|
||||
pitch = glm::degrees(pitch);
|
||||
roll = glm::degrees(roll);
|
||||
#endif
|
||||
|
|
|
@ -21,12 +21,15 @@ public:
|
|||
static bool isConnected() { return _isConnected; }
|
||||
|
||||
static void getEulerAngles(float& yaw, float& pitch, float& roll);
|
||||
|
||||
static void updateYawOffset();
|
||||
private:
|
||||
static bool _isConnected;
|
||||
static Ptr<DeviceManager> _deviceManager;
|
||||
static Ptr<HMDDevice> _hmdDevice;
|
||||
static Ptr<SensorDevice> _sensorDevice;
|
||||
static SensorFusion _sensorFusion;
|
||||
static float _yawOffset;
|
||||
};
|
||||
|
||||
#endif /* defined(__hifi__OculusManager__) */
|
||||
|
|
|
@ -160,8 +160,9 @@ void VoxelSystem::setupNewVoxelsForDrawing() {
|
|||
}
|
||||
|
||||
double sinceLastViewCulling = (start - _lastViewCulling) / 1000.0;
|
||||
// If the view frustum has changed, since last time, then remove nodes that are out of view
|
||||
if ((sinceLastViewCulling >= std::max(_lastViewCullingElapsed, VIEW_CULLING_RATE_IN_MILLISECONDS)) && hasViewChanged()) {
|
||||
// If the view frustum is no longer changing, but has changed, since last time, then remove nodes that are out of view
|
||||
if ((sinceLastViewCulling >= std::max(_lastViewCullingElapsed, VIEW_CULLING_RATE_IN_MILLISECONDS))
|
||||
&& !isViewChanging() && hasViewChanged()) {
|
||||
_lastViewCulling = start;
|
||||
|
||||
// When we call removeOutOfView() voxels, we don't actually remove the voxels from the VBOs, but we do remove
|
||||
|
@ -883,8 +884,10 @@ bool VoxelSystem::removeOutOfViewOperation(VoxelNode* node, void* extraData) {
|
|||
return true; // keep going!
|
||||
}
|
||||
|
||||
bool VoxelSystem::hasViewChanged() {
|
||||
|
||||
bool VoxelSystem::isViewChanging() {
|
||||
bool result = false; // assume the best
|
||||
// If our viewFrustum has changed since our _lastKnowViewFrustum
|
||||
if (_viewFrustum && !_lastKnowViewFrustum.matches(_viewFrustum)) {
|
||||
result = true;
|
||||
_lastKnowViewFrustum = *_viewFrustum; // save last known
|
||||
|
@ -892,6 +895,22 @@ bool VoxelSystem::hasViewChanged() {
|
|||
return result;
|
||||
}
|
||||
|
||||
bool VoxelSystem::hasViewChanged() {
|
||||
bool result = false; // assume the best
|
||||
|
||||
// If we're still changing, report no change yet.
|
||||
if (isViewChanging()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// If our viewFrustum has changed since our _lastKnowViewFrustum
|
||||
if (_viewFrustum && !_lastStableViewFrustum.matches(_viewFrustum)) {
|
||||
result = true;
|
||||
_lastStableViewFrustum = *_viewFrustum; // save last stable
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
void VoxelSystem::removeOutOfView() {
|
||||
PerformanceWarning warn(_renderWarningsOn, "removeOutOfView()");
|
||||
removeOutOfViewArgs args(this);
|
||||
|
|
|
@ -66,6 +66,7 @@ public:
|
|||
|
||||
void removeOutOfView();
|
||||
bool hasViewChanged();
|
||||
bool isViewChanging();
|
||||
|
||||
bool findRayIntersection(const glm::vec3& origin, const glm::vec3& direction,
|
||||
VoxelDetail& detail, float& distance, BoxFace& face);
|
||||
|
@ -147,6 +148,7 @@ private:
|
|||
|
||||
ViewFrustum* _viewFrustum;
|
||||
ViewFrustum _lastKnowViewFrustum;
|
||||
ViewFrustum _lastStableViewFrustum;
|
||||
|
||||
int newTreeToArrays(VoxelNode *currentNode);
|
||||
void cleanupRemovedVoxels();
|
||||
|
|
|
@ -68,23 +68,27 @@ int AudioRingBuffer::parseData(unsigned char* sourceBuffer, int numBytes) {
|
|||
_shouldLoopbackForAgent = false;
|
||||
}
|
||||
}
|
||||
|
||||
if (!_endOfLastWrite) {
|
||||
_endOfLastWrite = _buffer;
|
||||
} else if (diffLastWriteNextOutput() > _ringBufferLengthSamples - _bufferLengthSamples) {
|
||||
_endOfLastWrite = _buffer;
|
||||
_nextOutput = _buffer;
|
||||
_started = false;
|
||||
|
||||
// make sure we have enough bytes left for this to be the right amount of audio
|
||||
// otherwise we should not copy that data, and leave the buffer pointers where they are
|
||||
if (numBytes - (dataBuffer - sourceBuffer) == _bufferLengthSamples * sizeof(int16_t)) {
|
||||
if (!_endOfLastWrite) {
|
||||
_endOfLastWrite = _buffer;
|
||||
} else if (diffLastWriteNextOutput() > _ringBufferLengthSamples - _bufferLengthSamples) {
|
||||
_endOfLastWrite = _buffer;
|
||||
_nextOutput = _buffer;
|
||||
_started = false;
|
||||
}
|
||||
|
||||
memcpy(_endOfLastWrite, dataBuffer, _bufferLengthSamples * sizeof(int16_t));
|
||||
|
||||
_endOfLastWrite += _bufferLengthSamples;
|
||||
|
||||
if (_endOfLastWrite >= _buffer + _ringBufferLengthSamples) {
|
||||
_endOfLastWrite = _buffer;
|
||||
}
|
||||
}
|
||||
|
||||
memcpy(_endOfLastWrite, dataBuffer, _bufferLengthSamples * sizeof(int16_t));
|
||||
|
||||
_endOfLastWrite += _bufferLengthSamples;
|
||||
|
||||
if (_endOfLastWrite >= _buffer + _ringBufferLengthSamples) {
|
||||
_endOfLastWrite = _buffer;
|
||||
}
|
||||
|
||||
|
||||
return numBytes;
|
||||
}
|
||||
|
||||
|
|
|
@ -13,8 +13,8 @@
|
|||
|
||||
#include <glm/glm.hpp>
|
||||
|
||||
const float MIN_HEAD_YAW = -85;
|
||||
const float MAX_HEAD_YAW = 85;
|
||||
const float MIN_HEAD_YAW = -110;
|
||||
const float MAX_HEAD_YAW = 110;
|
||||
const float MIN_HEAD_PITCH = -60;
|
||||
const float MAX_HEAD_PITCH = 60;
|
||||
const float MIN_HEAD_ROLL = -50;
|
||||
|
|
|
@ -220,57 +220,70 @@ ViewFrustum::location ViewFrustum::boxInFrustum(const AABox& box) const {
|
|||
}
|
||||
}
|
||||
return(result);
|
||||
}
|
||||
|
||||
bool ViewFrustum::matches(const ViewFrustum& compareTo) const {
|
||||
bool debug = false;
|
||||
bool result = compareTo._position == _position &&
|
||||
compareTo._direction == _direction &&
|
||||
compareTo._up == _up &&
|
||||
compareTo._right == _right &&
|
||||
compareTo._fieldOfView == _fieldOfView &&
|
||||
compareTo._aspectRatio == _aspectRatio &&
|
||||
compareTo._nearClip == _nearClip &&
|
||||
compareTo._farClip == _farClip &&
|
||||
compareTo._eyeOffsetPosition == _eyeOffsetPosition &&
|
||||
compareTo._eyeOffsetOrientation == _eyeOffsetOrientation;
|
||||
}
|
||||
|
||||
bool testMatches(glm::quat lhs, glm::quat rhs) {
|
||||
return (fabs(lhs.x - rhs.x) <= EPSILON && fabs(lhs.y - rhs.y) <= EPSILON && fabs(lhs.z - rhs.z) <= EPSILON
|
||||
&& fabs(lhs.w - rhs.w) <= EPSILON);
|
||||
}
|
||||
|
||||
bool testMatches(glm::vec3 lhs, glm::vec3 rhs) {
|
||||
return (fabs(lhs.x - rhs.x) <= EPSILON && fabs(lhs.y - rhs.y) <= EPSILON && fabs(lhs.z - rhs.z) <= EPSILON);
|
||||
}
|
||||
|
||||
bool testMatches(float lhs, float rhs) {
|
||||
return (fabs(lhs - rhs) <= EPSILON);
|
||||
}
|
||||
|
||||
bool ViewFrustum::matches(const ViewFrustum& compareTo, bool debug) const {
|
||||
bool result =
|
||||
testMatches(compareTo._position, _position ) &&
|
||||
testMatches(compareTo._direction, _direction ) &&
|
||||
testMatches(compareTo._up, _up ) &&
|
||||
testMatches(compareTo._right, _right ) &&
|
||||
testMatches(compareTo._fieldOfView, _fieldOfView ) &&
|
||||
testMatches(compareTo._aspectRatio, _aspectRatio ) &&
|
||||
testMatches(compareTo._nearClip, _nearClip ) &&
|
||||
testMatches(compareTo._farClip, _farClip ) &&
|
||||
testMatches(compareTo._eyeOffsetPosition, _eyeOffsetPosition ) &&
|
||||
testMatches(compareTo._eyeOffsetOrientation, _eyeOffsetOrientation);
|
||||
|
||||
if (!result && debug) {
|
||||
printLog("ViewFrustum::matches()... result=%s\n", debug::valueOf(result));
|
||||
printLog("%s -- compareTo._position=%f,%f,%f _position=%f,%f,%f\n",
|
||||
(compareTo._position == _position ? "MATCHES " : "NO MATCH"),
|
||||
(testMatches(compareTo._position,_position) ? "MATCHES " : "NO MATCH"),
|
||||
compareTo._position.x, compareTo._position.y, compareTo._position.z,
|
||||
_position.x, _position.y, _position.z );
|
||||
printLog("%s -- compareTo._direction=%f,%f,%f _direction=%f,%f,%f\n",
|
||||
(compareTo._direction == _direction ? "MATCHES " : "NO MATCH"),
|
||||
(testMatches(compareTo._direction, _direction) ? "MATCHES " : "NO MATCH"),
|
||||
compareTo._direction.x, compareTo._direction.y, compareTo._direction.z,
|
||||
_direction.x, _direction.y, _direction.z );
|
||||
printLog("%s -- compareTo._up=%f,%f,%f _up=%f,%f,%f\n",
|
||||
(compareTo._up == _up ? "MATCHES " : "NO MATCH"),
|
||||
(testMatches(compareTo._up, _up) ? "MATCHES " : "NO MATCH"),
|
||||
compareTo._up.x, compareTo._up.y, compareTo._up.z,
|
||||
_up.x, _up.y, _up.z );
|
||||
printLog("%s -- compareTo._right=%f,%f,%f _right=%f,%f,%f\n",
|
||||
(compareTo._right == _right ? "MATCHES " : "NO MATCH"),
|
||||
(testMatches(compareTo._right, _right) ? "MATCHES " : "NO MATCH"),
|
||||
compareTo._right.x, compareTo._right.y, compareTo._right.z,
|
||||
_right.x, _right.y, _right.z );
|
||||
printLog("%s -- compareTo._fieldOfView=%f _fieldOfView=%f\n",
|
||||
(compareTo._fieldOfView == _fieldOfView ? "MATCHES " : "NO MATCH"),
|
||||
(testMatches(compareTo._fieldOfView, _fieldOfView) ? "MATCHES " : "NO MATCH"),
|
||||
compareTo._fieldOfView, _fieldOfView);
|
||||
printLog("%s -- compareTo._aspectRatio=%f _aspectRatio=%f\n",
|
||||
(compareTo._aspectRatio == _aspectRatio ? "MATCHES " : "NO MATCH"),
|
||||
(testMatches(compareTo._aspectRatio, _aspectRatio) ? "MATCHES " : "NO MATCH"),
|
||||
compareTo._aspectRatio, _aspectRatio);
|
||||
printLog("%s -- compareTo._nearClip=%f _nearClip=%f\n",
|
||||
(compareTo._nearClip == _nearClip ? "MATCHES " : "NO MATCH"),
|
||||
(testMatches(compareTo._nearClip, _nearClip) ? "MATCHES " : "NO MATCH"),
|
||||
compareTo._nearClip, _nearClip);
|
||||
printLog("%s -- compareTo._farClip=%f _farClip=%f\n",
|
||||
(compareTo._farClip == _farClip ? "MATCHES " : "NO MATCH"),
|
||||
(testMatches(compareTo._farClip, _farClip) ? "MATCHES " : "NO MATCH"),
|
||||
compareTo._farClip, _farClip);
|
||||
printLog("%s -- compareTo._eyeOffsetPosition=%f,%f,%f _eyeOffsetPosition=%f,%f,%f\n",
|
||||
(compareTo._eyeOffsetPosition == _eyeOffsetPosition ? "MATCHES " : "NO MATCH"),
|
||||
(testMatches(compareTo._eyeOffsetPosition, _eyeOffsetPosition) ? "MATCHES " : "NO MATCH"),
|
||||
compareTo._eyeOffsetPosition.x, compareTo._eyeOffsetPosition.y, compareTo._eyeOffsetPosition.z,
|
||||
_eyeOffsetPosition.x, _eyeOffsetPosition.y, _eyeOffsetPosition.z);
|
||||
printLog("%s -- compareTo._eyeOffsetOrientation=%f,%f,%f,%f _eyeOffsetOrientation=%f,%f,%f,%f\n",
|
||||
(compareTo._eyeOffsetOrientation == _eyeOffsetOrientation ? "MATCHES " : "NO MATCH"),
|
||||
(testMatches(compareTo._eyeOffsetOrientation, _eyeOffsetOrientation) ? "MATCHES " : "NO MATCH"),
|
||||
compareTo._eyeOffsetOrientation.x, compareTo._eyeOffsetOrientation.y,
|
||||
compareTo._eyeOffsetOrientation.z, compareTo._eyeOffsetOrientation.w,
|
||||
_eyeOffsetOrientation.x, _eyeOffsetOrientation.y, _eyeOffsetOrientation.z, _eyeOffsetOrientation.w);
|
||||
|
|
|
@ -108,8 +108,8 @@ public:
|
|||
ViewFrustum::location boxInFrustum(const AABox& box) const;
|
||||
|
||||
// some frustum comparisons
|
||||
bool matches(const ViewFrustum& compareTo) const;
|
||||
bool matches(const ViewFrustum* compareTo) const { return matches(*compareTo); };
|
||||
bool matches(const ViewFrustum& compareTo, bool debug = false) const;
|
||||
bool matches(const ViewFrustum* compareTo, bool debug = false) const { return matches(*compareTo, debug); };
|
||||
|
||||
void computePickRay(float x, float y, glm::vec3& origin, glm::vec3& direction) const;
|
||||
|
||||
|
|
|
@ -66,7 +66,11 @@ bool VoxelAgentData::updateCurrentViewFrustum() {
|
|||
}
|
||||
|
||||
void VoxelAgentData::updateLastKnownViewFrustum() {
|
||||
// save our currentViewFrustum into our lastKnownViewFrustum
|
||||
_lastKnownViewFrustum = _currentViewFrustum;
|
||||
bool frustumChanges = !_lastKnownViewFrustum.matches(_currentViewFrustum);
|
||||
|
||||
if (frustumChanges) {
|
||||
// save our currentViewFrustum into our lastKnownViewFrustum
|
||||
_lastKnownViewFrustum = _currentViewFrustum;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in a new issue