This commit is contained in:
Andrzej Kapolka 2015-01-22 14:55:37 -08:00
commit c1c9f698df
12 changed files with 321 additions and 150 deletions

View file

@ -1,6 +1,7 @@
// Pool Table
var tableParts = [];
var balls = [];
var cueBall;
var LENGTH = 2.84;
var WIDTH = 1.42;
@ -13,6 +14,8 @@ var HOLE_SIZE = BALL_SIZE;
var DROP_HEIGHT = BALL_SIZE * 3.0;
var GRAVITY = -9.8;
var BALL_GAP = 0.001;
var tableCenter;
var cuePosition;
var startStroke = 0;
@ -23,7 +26,7 @@ var reticle = Overlays.addOverlay("image", {
y: screenSize.y / 2 - 16,
width: 32,
height: 32,
imageURL: HIFI_PUBLIC_BUCKET + "images/reticle.png",
imageURL: HIFI_PUBLIC_BUCKET + "images/billiardsReticle.png",
color: { red: 255, green: 255, blue: 255},
alpha: 1
});
@ -102,7 +105,7 @@ function makeBalls(pos) {
{ red: 128, green: 128, blue: 128}]; // Gray
// Object balls
var ballPosition = { x: pos.x + (LENGTH / 4.0) * SCALE, y: pos.y + DROP_HEIGHT, z: pos.z };
var ballPosition = { x: pos.x + (LENGTH / 4.0) * SCALE, y: pos.y + HEIGHT / 2.0 + DROP_HEIGHT, z: pos.z };
for (var row = 1; row <= 5; row++) {
ballPosition.z = pos.z - ((row - 1.0) / 2.0 * (BALL_SIZE + BALL_GAP) * SCALE);
for (var spot = 0; spot < row; spot++) {
@ -113,23 +116,26 @@ function makeBalls(pos) {
color: colors[balls.length],
gravity: { x: 0, y: GRAVITY, z: 0 },
ignoreCollisions: false,
damping: 0.40,
damping: 0.50,
collisionsWillMove: true }));
ballPosition.z += (BALL_SIZE + BALL_GAP) * SCALE;
}
ballPosition.x += (BALL_GAP + Math.sqrt(3.0) / 2.0 * BALL_SIZE) * SCALE;
}
// Cue Ball
ballPosition = { x: pos.x - (LENGTH / 4.0) * SCALE, y: pos.y + DROP_HEIGHT, z: pos.z };
balls.push(Entities.addEntity(
cuePosition = { x: pos.x - (LENGTH / 4.0) * SCALE, y: pos.y + HEIGHT / 2.0 + DROP_HEIGHT, z: pos.z };
cueBall = Entities.addEntity(
{ type: "Sphere",
position: ballPosition,
position: cuePosition,
dimensions: { x: BALL_SIZE * SCALE, y: BALL_SIZE * SCALE, z: BALL_SIZE * SCALE },
color: { red: 255, green: 255, blue: 255 },
gravity: { x: 0, y: GRAVITY, z: 0 },
angularVelocity: { x: 0, y: 0, z: 0 },
velocity: {x: 0, y: 0, z: 0 },
ignoreCollisions: false,
damping: 0.40,
collisionsWillMove: true }));
damping: 0.50,
collisionsWillMove: true });
}
function shootCue(velocity) {
@ -140,19 +146,23 @@ function shootCue(velocity) {
var velocity = Vec3.multiply(forwardVector, velocity);
var BULLET_LIFETIME = 3.0;
var BULLET_GRAVITY = 0.0;
var SHOOTER_COLOR = { red: 255, green: 0, blue: 0 };
var SHOOTER_SIZE = BALL_SIZE / 1.5 * SCALE;
bulletID = Entities.addEntity(
{ type: "Sphere",
position: cuePosition,
dimensions: { x: BALL_SIZE * SCALE, y: BALL_SIZE * SCALE, z: BALL_SIZE * SCALE },
color: { red: 255, green: 255, blue: 255 },
dimensions: { x: SHOOTER_SIZE, y: SHOOTER_SIZE, z: SHOOTER_SIZE },
color: SHOOTER_COLOR,
velocity: velocity,
lifetime: BULLET_LIFETIME,
gravity: { x: 0, y: BULLET_GRAVITY, z: 0 },
damping: 0.10,
density: 1000,
density: 8000,
ignoreCollisions: false,
collisionsWillMove: true
});
print("Shot, velocity = " + velocity);
}
function keyReleaseEvent(event) {
@ -185,9 +195,25 @@ function cleanup() {
Entities.deleteEntity(balls[i]);
}
Overlays.deleteOverlay(reticle);
Entities.deleteEntity(cueBall);
}
var tableCenter = Vec3.sum(MyAvatar.position, Vec3.multiply(4.0, Quat.getFront(Camera.getOrientation())));
function update(deltaTime) {
if (!cueBall.isKnownID) {
cueBall = Entities.identifyEntity(cueBall);
} else {
// Check if cue ball has fallen off table, re-drop if so
var cueProperties = Entities.getEntityProperties(cueBall);
if (cueProperties.position.y < tableCenter.y) {
// Replace the cueball
Entities.editEntity(cueBall, { position: cuePosition } );
}
}
}
tableCenter = Vec3.sum(MyAvatar.position, Vec3.multiply(4.0, Quat.getFront(Camera.getOrientation())));
makeTable(tableCenter);
makeBalls(tableCenter);
@ -195,3 +221,4 @@ makeBalls(tableCenter);
Script.scriptEnding.connect(cleanup);
Controller.keyPressEvent.connect(keyPressEvent);
Controller.keyReleaseEvent.connect(keyReleaseEvent);
Script.update.connect(update);

View file

@ -47,7 +47,6 @@ var yawFromMouse = 0;
var pitchFromMouse = 0;
var isMouseDown = false;
var BULLET_VELOCITY = 5.0;
var MIN_THROWER_DELAY = 1000;
var MAX_THROWER_DELAY = 1000;
var LEFT_BUTTON_3 = 3;
@ -98,7 +97,7 @@ var reticle = Overlays.addOverlay("image", {
y: screenSize.y / 2 - 16,
width: 32,
height: 32,
imageURL: HIFI_PUBLIC_BUCKET + "images/reticle.png",
imageURL: HIFI_PUBLIC_BUCKET + "images/billiardsReticle.png",
color: { red: 255, green: 255, blue: 255},
alpha: 1
});
@ -113,6 +112,25 @@ var offButton = Overlays.addOverlay("image", {
alpha: 1
});
var platformButton = Overlays.addOverlay("image", {
x: screenSize.x - 48,
y: 130,
width: 32,
height: 32,
imageURL: HIFI_PUBLIC_BUCKET + "images/city.png",
color: { red: 255, green: 255, blue: 255},
alpha: 1
});
var gridButton = Overlays.addOverlay("image", {
x: screenSize.x - 48,
y: 164,
width: 32,
height: 32,
imageURL: HIFI_PUBLIC_BUCKET + "images/blocks.png",
color: { red: 255, green: 255, blue: 255},
alpha: 1
});
if (showScore) {
var text = Overlays.addOverlay("text", {
x: screenSize.x / 2 - 100,
@ -127,24 +145,30 @@ if (showScore) {
});
}
function printVector(string, vector) {
print(string + " " + vector.x + ", " + vector.y + ", " + vector.z);
}
var BULLET_VELOCITY = 10.0;
function shootBullet(position, velocity) {
var BULLET_SIZE = 0.07;
function shootBullet(position, velocity, grenade) {
var BULLET_SIZE = 0.10;
var BULLET_LIFETIME = 10.0;
var BULLET_GRAVITY = 0.0;
var BULLET_GRAVITY = -0.25;
var GRENADE_VELOCITY = 15.0;
var GRENADE_SIZE = 0.35;
var GRENADE_GRAVITY = -9.8;
var bVelocity = grenade ? Vec3.multiply(GRENADE_VELOCITY, Vec3.normalize(velocity)) : velocity;
var bSize = grenade ? GRENADE_SIZE : BULLET_SIZE;
var bGravity = grenade ? GRENADE_GRAVITY : BULLET_GRAVITY;
bulletID = Entities.addEntity(
{ type: "Sphere",
position: position,
dimensions: { x: BULLET_SIZE, y: BULLET_SIZE, z: BULLET_SIZE },
dimensions: { x: bSize, y: bSize, z: bSize },
color: { red: 255, green: 0, blue: 0 },
velocity: velocity,
velocity: bVelocity,
lifetime: BULLET_LIFETIME,
gravity: { x: 0, y: BULLET_GRAVITY, z: 0 },
gravity: { x: 0, y: bGravity, z: 0 },
damping: 0.01,
density: 5000,
density: 8000,
ignoreCollisions: false,
collisionsWillMove: true
});
@ -158,13 +182,15 @@ function shootBullet(position, velocity) {
}
// Kickback the arm
if (elbowKickAngle > 0.0) {
MyAvatar.setJointData("LeftForeArm", rotationBeforeKickback);
}
rotationBeforeKickback = MyAvatar.getJointRotation("LeftForeArm");
var armRotation = MyAvatar.getJointRotation("LeftForeArm");
armRotation = Quat.multiply(armRotation, Quat.fromPitchYawRollDegrees(0.0, 0.0, KICKBACK_ANGLE));
MyAvatar.setJointData("LeftForeArm", armRotation);
elbowKickAngle = KICKBACK_ANGLE;
}
function shootTarget() {
var TARGET_SIZE = 0.50;
var TARGET_GRAVITY = 0.0;
@ -174,7 +200,7 @@ function shootTarget() {
var DISTANCE_TO_LAUNCH_FROM = 5.0;
var ANGLE_RANGE_FOR_LAUNCH = 20.0;
var camera = Camera.getPosition();
//printVector("camera", camera);
var targetDirection = Quat.angleAxis(getRandomFloat(-ANGLE_RANGE_FOR_LAUNCH, ANGLE_RANGE_FOR_LAUNCH), { x:0, y:1, z:0 });
targetDirection = Quat.multiply(Camera.getOrientation(), targetDirection);
var forwardVector = Quat.getFront(targetDirection);
@ -205,6 +231,78 @@ function shootTarget() {
Audio.playSound(targetLaunchSound, audioOptions);
}
function makeGrid(type, scale, size) {
var separation = scale * 2;
var pos = Vec3.sum(Camera.getPosition(), Vec3.multiply(10.0 * scale * separation, Quat.getFront(Camera.getOrientation())));
var x, y, z;
var GRID_LIFE = 60.0;
var dimensions;
for (x = 0; x < size; x++) {
for (y = 0; y < size; y++) {
for (z = 0; z < size; z++) {
dimensions = { x: separation/2.0 * (0.5 + Math.random()), y: separation/2.0 * (0.5 + Math.random()), z: separation/2.0 * (0.5 + Math.random()) / 4.0 };
Entities.addEntity(
{ type: type,
position: { x: pos.x + x * separation, y: pos.y + y * separation, z: pos.z + z * separation },
dimensions: dimensions,
color: { red: Math.random() * 255, green: Math.random() * 255, blue: Math.random() * 255 },
velocity: { x: 0, y: 0, z: 0 },
gravity: { x: 0, y: 0, z: 0 },
lifetime: GRID_LIFE,
rotation: Camera.getOrientation(),
damping: 0.1,
density: 100.0,
collisionsWillMove: true });
}
}
}
}
function makePlatform(gravity, scale, size) {
var separation = scale * 2;
var pos = Vec3.sum(Camera.getPosition(), Vec3.multiply(10.0 * scale * separation, Quat.getFront(Camera.getOrientation())));
pos.y -= separation * size;
var x, y, z;
var TARGET_LIFE = 60.0;
var INITIAL_GAP = 0.5;
var dimensions;
for (x = 0; x < size; x++) {
for (y = 0; y < size; y++) {
for (z = 0; z < size; z++) {
dimensions = { x: separation/2.0, y: separation, z: separation/2.0 };
Entities.addEntity(
{ type: "Box",
position: { x: pos.x - (separation * size / 2.0) + x * separation,
y: pos.y + y * (separation + INITIAL_GAP),
z: pos.z - (separation * size / 2.0) + z * separation },
dimensions: dimensions,
color: { red: Math.random() * 255, green: Math.random() * 255, blue: Math.random() * 255 },
velocity: { x: 0, y: 0, z: 0 },
gravity: { x: 0, y: gravity, z: 0 },
lifetime: TARGET_LIFE,
damping: 0.1,
density: 100.0,
collisionsWillMove: true });
}
}
}
// Make a floor for this stuff to fall onto
Entities.addEntity({
type: "Box",
position: { x: pos.x, y: pos.y - separation / 2.0, z: pos.z },
dimensions: { x: 2.0 * separation * size, y: separation / 2.0, z: 2.0 * separation * size },
color: { red: 128, green: 128, blue: 128 },
lifetime: TARGET_LIFE
});
}
function entityCollisionWithEntity(entity1, entity2, collision) {
if (((entity1.id == bulletID.id) || (entity1.id == targetID.id)) &&
@ -233,7 +331,9 @@ function keyPressEvent(event) {
var time = MIN_THROWER_DELAY + Math.random() * MAX_THROWER_DELAY;
Script.setTimeout(shootTarget, time);
} else if ((event.text == ".") || (event.text == "SPACE")) {
shootFromMouse();
shootFromMouse(false);
} else if (event.text == ",") {
shootFromMouse(true);
} else if (event.text == "r") {
playLoadSound();
} else if (event.text == "s") {
@ -241,7 +341,6 @@ function keyPressEvent(event) {
Quat.print("arm = ", MyAvatar.getJointRotation("LeftArm"));
Quat.print("forearm = ", MyAvatar.getJointRotation("LeftForeArm"));
Quat.print("hand = ", MyAvatar.getJointRotation("LeftHand"));
}
}
@ -287,18 +386,7 @@ function update(deltaTime) {
if (targetID && !targetID.isKnownID) {
targetID = Entities.identifyEntity(targetID);
}
// Check for mouseLook movement, update rotation
// rotate body yaw for yaw received from mouse
var newOrientation = Quat.multiply(MyAvatar.orientation, Quat.fromVec3Radians( { x: 0, y: yawFromMouse, z: 0 } ));
//MyAvatar.orientation = newOrientation;
yawFromMouse = 0;
// apply pitch from mouse
var newPitch = MyAvatar.headPitch + pitchFromMouse;
//MyAvatar.headPitch = newPitch;
pitchFromMouse = 0;
if (activeControllers == 0) {
if (Controller.getNumberOfSpatialControls() > 0) {
activeControllers = Controller.getNumberOfSpatialControls();
@ -372,19 +460,19 @@ function update(deltaTime) {
var velocity = Vec3.multiply(BULLET_VELOCITY, Vec3.normalize(palmToFingerTipVector));
shootBullet(position, velocity);
shootBullet(position, velocity, false);
}
}
}
}
function shootFromMouse() {
function shootFromMouse(grenade) {
var DISTANCE_FROM_CAMERA = 1.0;
var camera = Camera.getPosition();
var forwardVector = Quat.getFront(Camera.getOrientation());
var newPosition = Vec3.sum(camera, Vec3.multiply(forwardVector, DISTANCE_FROM_CAMERA));
var velocity = Vec3.multiply(forwardVector, BULLET_VELOCITY);
shootBullet(newPosition, velocity);
shootBullet(newPosition, velocity, grenade);
}
function mouseReleaseEvent(event) {
@ -392,21 +480,23 @@ function mouseReleaseEvent(event) {
isMouseDown = false;
}
function mouseMoveEvent(event) {
if (isMouseDown) {
var MOUSE_YAW_SCALE = -0.25;
var MOUSE_PITCH_SCALE = -12.5;
var FIXED_MOUSE_TIMESTEP = 0.016;
yawFromMouse += ((event.x - lastX) * MOUSE_YAW_SCALE * FIXED_MOUSE_TIMESTEP);
pitchFromMouse += ((event.y - lastY) * MOUSE_PITCH_SCALE * FIXED_MOUSE_TIMESTEP);
lastX = event.x;
lastY = event.y;
}
function mousePressEvent(event) {
var clickedText = false;
var clickedOverlay = Overlays.getOverlayAtPoint({x: event.x, y: event.y});
if (clickedOverlay == offButton) {
Script.stop();
} else if (clickedOverlay == platformButton) {
makePlatform(-9.8, 1.0, 5);
} else if (clickedOverlay == gridButton) {
makeGrid("Box", 1.0, 3);
}
}
function scriptEnding() {
Overlays.deleteOverlay(reticle);
Overlays.deleteOverlay(offButton);
Overlays.deleteOverlay(platformButton);
Overlays.deleteOverlay(gridButton);
Overlays.deleteOverlay(pointer[0]);
Overlays.deleteOverlay(pointer[1]);
Overlays.deleteOverlay(text);
@ -418,7 +508,7 @@ Entities.entityCollisionWithEntity.connect(entityCollisionWithEntity);
Script.scriptEnding.connect(scriptEnding);
Script.update.connect(update);
Controller.mouseReleaseEvent.connect(mouseReleaseEvent);
Controller.mouseMoveEvent.connect(mouseMoveEvent);
Controller.mousePressEvent.connect(mousePressEvent);
Controller.keyPressEvent.connect(keyPressEvent);

View file

@ -4,6 +4,6 @@ set(TARGET_NAME ice-server)
setup_hifi_project(Network)
# link the shared hifi libraries
link_hifi_libraries(networking shared)
link_hifi_libraries(embedded-webserver networking shared)
include_dependency_includes()

View file

@ -20,14 +20,18 @@
const int CLEAR_INACTIVE_PEERS_INTERVAL_MSECS = 1 * 1000;
const int PEER_SILENCE_THRESHOLD_MSECS = 5 * 1000;
const quint16 ICE_SERVER_MONITORING_PORT = 40110;
IceServer::IceServer(int argc, char* argv[]) :
QCoreApplication(argc, argv),
_id(QUuid::createUuid()),
_serverSocket(),
_activePeers()
_activePeers(),
_httpManager(ICE_SERVER_MONITORING_PORT, QString("%1/web/").arg(QCoreApplication::applicationDirPath()), this)
{
// start the ice-server socket
qDebug() << "ice-server socket is listening on" << ICE_SERVER_DEFAULT_PORT;
qDebug() << "monitoring http endpoint is listening on " << ICE_SERVER_MONITORING_PORT;
_serverSocket.bind(QHostAddress::AnyIPv4, ICE_SERVER_DEFAULT_PORT);
// call our process datagrams slot when the UDP socket has packets ready
@ -165,3 +169,28 @@ void IceServer::clearInactivePeers() {
}
}
}
bool IceServer::handleHTTPRequest(HTTPConnection* connection, const QUrl& url, bool skipSubHandler) {
//
// We need an HTTP handler in order to monitor the health of the ice server
// The correct functioning of the ICE server will first be determined by its HTTP availability,
// and then by the existence of a minimum number of peers in the list, matching the minimum number of
// domains in production by High Fidelity.
//
int MINIMUM_PEERS = 3;
bool IS_HEALTHY = false;
IS_HEALTHY = _activePeers.size() >= MINIMUM_PEERS ? true : false;
if (connection->requestOperation() == QNetworkAccessManager::GetOperation) {
if (url.path() == "/status") {
if (IS_HEALTHY) {
connection->respond(HTTPConnection::StatusCode200, QByteArray::number(_activePeers.size()));
} else {
connection->respond(HTTPConnection::StatusCode404, QByteArray::number(_activePeers.size()));
}
}
}
return true;
}

View file

@ -12,17 +12,21 @@
#ifndef hifi_IceServer_h
#define hifi_IceServer_h
#include <qcoreapplication.h>
#include <qsharedpointer.h>
#include <qudpsocket.h>
#include <QtCore/QCoreApplication>
#include <QtCore/QSharedPointer>
#include <QUdpSocket>
#include <NetworkPeer.h>
#include <HTTPConnection.h>
#include <HTTPManager.h>
typedef QHash<QUuid, SharedNetworkPeer> NetworkPeerHash;
class IceServer : public QCoreApplication {
class IceServer : public QCoreApplication, public HTTPRequestHandler {
Q_OBJECT
public:
IceServer(int argc, char* argv[]);
bool handleHTTPRequest(HTTPConnection* connection, const QUrl& url, bool skipSubHandler = false);
private slots:
void processDatagrams();
void clearInactivePeers();
@ -34,6 +38,7 @@ private:
QUdpSocket _serverSocket;
NetworkPeerHash _activePeers;
QHash<QUuid, QSet<QUuid> > _currentConnections;
HTTPManager _httpManager;
};
#endif // hifi_IceServer_h

View file

@ -22,88 +22,62 @@
#include "ImageOverlay.h"
ImageOverlay::ImageOverlay() :
_textureID(0),
_imageURL(),
_renderImage(false),
_textureBound(false),
_wantClipFromImage(false)
{
_isLoaded = false;
}
ImageOverlay::ImageOverlay(const ImageOverlay* imageOverlay) :
Overlay2D(imageOverlay),
_texture(imageOverlay->_texture),
_imageURL(imageOverlay->_imageURL),
_textureImage(imageOverlay->_textureImage),
_textureID(0),
_fromImage(),
_fromImage(imageOverlay->_fromImage),
_renderImage(imageOverlay->_renderImage),
_textureBound(false),
_wantClipFromImage(false)
_wantClipFromImage(imageOverlay->_wantClipFromImage)
{
}
ImageOverlay::~ImageOverlay() {
if (_parent && _textureID) {
// do we need to call this?
//_parent->deleteTexture(_textureID);
}
}
// TODO: handle setting image multiple times, how do we manage releasing the bound texture?
void ImageOverlay::setImageURL(const QUrl& url) {
_imageURL = url;
_isLoaded = false;
QNetworkAccessManager& networkAccessManager = NetworkAccessManager::getInstance();
QNetworkReply* reply = networkAccessManager.get(QNetworkRequest(url));
connect(reply, &QNetworkReply::finished, this, &ImageOverlay::replyFinished);
}
void ImageOverlay::replyFinished() {
QNetworkReply* reply = static_cast<QNetworkReply*>(sender());
// replace our byte array with the downloaded data
QByteArray rawData = reply->readAll();
_textureImage.loadFromData(rawData);
_renderImage = true;
_isLoaded = true;
reply->deleteLater();
if (url.isEmpty()) {
_isLoaded = true;
_renderImage = false;
_texture.clear();
} else {
_isLoaded = false;
_renderImage = true;
}
}
void ImageOverlay::render(RenderArgs* args) {
if (!_visible || !_isLoaded) {
return; // do nothing if we're not visible
if (!_isLoaded && _renderImage) {
_isLoaded = true;
_texture = DependencyManager::get<TextureCache>()->getTexture(_imageURL);
}
if (_renderImage && !_textureBound) {
_textureID = _parent->bindTexture(_textureImage);
_textureBound = true;
// If we are not visible or loaded, return. If we are trying to render an
// image but the texture hasn't loaded, return.
if (!_visible || !_isLoaded || (_renderImage && !_texture->isLoaded())) {
return;
}
if (_renderImage) {
glEnable(GL_TEXTURE_2D);
glBindTexture(GL_TEXTURE_2D, _textureID);
glBindTexture(GL_TEXTURE_2D, _texture->getID());
}
const float MAX_COLOR = 255.0f;
xColor color = getColor();
float alpha = getAlpha();
glColor4f(color.red / MAX_COLOR, color.green / MAX_COLOR, color.blue / MAX_COLOR, alpha);
float imageWidth = _textureImage.width();
float imageHeight = _textureImage.height();
QRect fromImage;
if (_wantClipFromImage) {
fromImage = _fromImage;
} else {
fromImage.setX(0);
fromImage.setY(0);
fromImage.setWidth(imageWidth);
fromImage.setHeight(imageHeight);
}
float x = fromImage.x() / imageWidth;
float y = fromImage.y() / imageHeight;
float w = fromImage.width() / imageWidth; // ?? is this what we want? not sure
float h = fromImage.height() / imageHeight;
int left = _bounds.left();
int right = _bounds.right() + 1;
int top = _bounds.top();
@ -111,10 +85,29 @@ void ImageOverlay::render(RenderArgs* args) {
glm::vec2 topLeft(left, top);
glm::vec2 bottomRight(right, bottom);
glm::vec2 texCoordTopLeft(x, 1.0f - y);
glm::vec2 texCoordBottomRight(x + w, 1.0f - (y + h));
if (_renderImage) {
float imageWidth = _texture->getWidth();
float imageHeight = _texture->getHeight();
QRect fromImage;
if (_wantClipFromImage) {
fromImage = _fromImage;
} else {
fromImage.setX(0);
fromImage.setY(0);
fromImage.setWidth(imageWidth);
fromImage.setHeight(imageHeight);
}
float x = fromImage.x() / imageWidth;
float y = fromImage.y() / imageHeight;
float w = fromImage.width() / imageWidth; // ?? is this what we want? not sure
float h = fromImage.height() / imageHeight;
glm::vec2 texCoordTopLeft(x, y);
glm::vec2 texCoordBottomRight(x + w, y + h);
DependencyManager::get<GeometryCache>()->renderQuad(topLeft, bottomRight, texCoordTopLeft, texCoordBottomRight);
} else {
DependencyManager::get<GeometryCache>()->renderQuad(topLeft, bottomRight);
@ -153,7 +146,7 @@ void ImageOverlay::setProperties(const QScriptValue& properties) {
subImageRect.setHeight(oldSubImageRect.height());
}
setClipFromSource(subImageRect);
}
}
QScriptValue imageURL = properties.property("imageURL");
if (imageURL.isValid()) {

View file

@ -24,6 +24,7 @@
#include <NetworkAccessManager.h>
#include <SharedUtil.h>
#include <TextureCache.h>
#include "Overlay.h"
#include "Overlay2D.h"
@ -49,18 +50,14 @@ public:
virtual ImageOverlay* createClone() const;
private slots:
void replyFinished(); // we actually want to hide this...
private:
QUrl _imageURL;
QImage _textureImage;
GLuint _textureID;
NetworkTexturePointer _texture;
QRect _fromImage; // where from in the image to sample
bool _renderImage; // is there an image associated with this overlay, or is it just a colored rectangle
bool _textureBound; // has the texture been bound
bool _wantClipFromImage;
};

View file

@ -108,6 +108,16 @@ bool ObjectMotionState::doesNotNeedToSendUpdate() const {
bool ObjectMotionState::shouldSendUpdate(uint32_t simulationFrame) {
assert(_body);
// if we've never checked before, our _sentFrame will be 0, and we need to initialize our state
if (_sentFrame == 0) {
_sentPosition = bulletToGLM(_body->getWorldTransform().getOrigin());
_sentVelocity = bulletToGLM(_body->getLinearVelocity());
_sentAngularVelocity = bulletToGLM(_body->getAngularVelocity());
_sentFrame = simulationFrame;
return false;
}
float dt = (float)(simulationFrame - _sentFrame) * PHYSICS_ENGINE_FIXED_SUBSTEP;
_sentFrame = simulationFrame;
bool isActive = _body->isActive();

View file

@ -186,22 +186,6 @@ void PhysicsEngine::init(EntityEditPacketSender* packetSender) {
// default gravity of the world is zero, so each object must specify its own gravity
// TODO: set up gravity zones
_dynamicsWorld->setGravity(btVector3(0.0f, 0.0f, 0.0f));
// GROUND HACK: add a big planar floor (and walls for testing) to catch falling objects
btTransform groundTransform;
groundTransform.setIdentity();
for (int i = 0; i < 3; ++i) {
btVector3 normal(0.0f, 0.0f, 0.0f);
normal[i] = 1.0f;
btCollisionShape* plane = new btStaticPlaneShape(normal, 0.0f);
btCollisionObject* groundObject = new btCollisionObject();
groundObject->setCollisionFlags(btCollisionObject::CF_STATIC_OBJECT);
groundObject->setCollisionShape(plane);
groundObject->setWorldTransform(groundTransform);
_dynamicsWorld->addCollisionObject(groundObject);
}
}
assert(packetSender);
@ -231,41 +215,67 @@ void PhysicsEngine::stepSimulation() {
_numSubsteps += (uint32_t)numSubsteps;
unlock();
// This is step (3) which is done outside of stepSimulation() so we can lock _entityTree.
//
// Unfortunately we have to unlock the simulation (above) before we try to lock the _entityTree
// to avoid deadlock -- the _entityTree may try to lock its EntitySimulation (from which this
// PhysicsEngine derives) when updating/adding/deleting entities so we need to wait for our own
// lock on the tree before we re-lock ourselves.
//
// TODO: untangle these lock sequences.
_entityTree->lockForWrite();
lock();
_dynamicsWorld->synchronizeMotionStates();
unlock();
_entityTree->unlock();
computeCollisionEvents();
if (numSubsteps > 0) {
// This is step (3) which is done outside of stepSimulation() so we can lock _entityTree.
//
// Unfortunately we have to unlock the simulation (above) before we try to lock the _entityTree
// to avoid deadlock -- the _entityTree may try to lock its EntitySimulation (from which this
// PhysicsEngine derives) when updating/adding/deleting entities so we need to wait for our own
// lock on the tree before we re-lock ourselves.
//
// TODO: untangle these lock sequences.
_entityTree->lockForWrite();
lock();
_dynamicsWorld->synchronizeMotionStates();
unlock();
_entityTree->unlock();
computeCollisionEvents();
}
}
void PhysicsEngine::computeCollisionEvents() {
// update all contacts
// update all contacts every frame
int numManifolds = _collisionDispatcher->getNumManifolds();
for (int i = 0; i < numManifolds; ++i) {
btPersistentManifold* contactManifold = _collisionDispatcher->getManifoldByIndexInternal(i);
if (contactManifold->getNumContacts() > 0) {
// TODO: require scripts to register interest in callbacks for specific objects
// so we can filter out most collision events right here.
const btCollisionObject* objectA = static_cast<const btCollisionObject*>(contactManifold->getBody0());
const btCollisionObject* objectB = static_cast<const btCollisionObject*>(contactManifold->getBody1());
if (!(objectA->isActive() || objectB->isActive())) {
// both objects are inactive so stop tracking this contact,
// which will eventually trigger a CONTACT_EVENT_TYPE_END
continue;
}
void* a = objectA->getUserPointer();
void* b = objectB->getUserPointer();
if (a || b) {
// the manifold has up to 4 distinct points, but only extract info from the first
_contactMap[ContactKey(a, b)].update(_numSubsteps, contactManifold->getContactPoint(0), _originOffset);
_contactMap[ContactKey(a, b)].update(_numContactFrames, contactManifold->getContactPoint(0), _originOffset);
}
}
}
// We harvest collision callbacks every few frames, which contributes the following effects:
//
// (1) There is a maximum collision callback rate per pair: substep_rate / SUBSTEPS_PER_COLLIION_FRAME
// (2) END/START cycles shorter than SUBSTEPS_PER_COLLIION_FRAME will be filtered out
// (3) There is variable lag between when the contact actually starts and when it is reported,
// up to SUBSTEPS_PER_COLLIION_FRAME * time_per_substep
//
const uint32_t SUBSTEPS_PER_COLLISION_FRAME = 2;
if (_numSubsteps - _numContactFrames * SUBSTEPS_PER_COLLISION_FRAME < SUBSTEPS_PER_COLLISION_FRAME) {
// we don't harvest collision callbacks every frame
// this sets a maximum callback-per-contact rate
// and also filters out END/START events that happen on shorter timescales
return;
}
++_numContactFrames;
// scan known contacts and trigger events
ContactMap::iterator contactItr = _contactMap.begin();
while (contactItr != _contactMap.end()) {
@ -289,7 +299,7 @@ void PhysicsEngine::computeCollisionEvents() {
}
// TODO: enable scripts to filter based on contact event type
ContactEventType type = contactItr->second.computeType(_numSubsteps);
ContactEventType type = contactItr->second.computeType(_numContactFrames);
if (type == CONTACT_EVENT_TYPE_END) {
ContactMap::iterator iterToDelete = contactItr;
++contactItr;

View file

@ -110,6 +110,7 @@ private:
EntityEditPacketSender* _entityPacketSender = NULL;
ContactMap _contactMap;
uint32_t _numContactFrames = 0;
};
#endif // hifi_PhysicsEngine_h

View file

@ -385,7 +385,9 @@ Texture::~Texture() {
NetworkTexture::NetworkTexture(const QUrl& url, TextureType type, const QByteArray& content) :
Resource(url, !content.isEmpty()),
_type(type),
_translucent(false) {
_translucent(false),
_width(0),
_height(0) {
if (!url.isValid()) {
_loaded = true;
@ -532,6 +534,8 @@ void NetworkTexture::loadContent(const QByteArray& content) {
void NetworkTexture::setImage(const QImage& image, bool translucent, const QColor& averageColor) {
_translucent = translucent;
_averageColor = averageColor;
_width = image.width();
_height = image.height();
finishedLoading(true);
imageLoaded(image);

View file

@ -150,6 +150,9 @@ public:
/// Returns the lazily-computed average texture color.
const QColor& getAverageColor() const { return _averageColor; }
int getWidth() const { return _width; }
int getHeight() const { return _height; }
protected:
virtual void downloadFinished(QNetworkReply* reply);
@ -163,6 +166,8 @@ private:
TextureType _type;
bool _translucent;
QColor _averageColor;
int _width;
int _height;
};
/// Caches derived, dilated textures.