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

Conflicts:
	libraries/entities/src/EntityItem.cpp
This commit is contained in:
ZappoMan 2015-01-23 15:01:35 -08:00
commit 9cc61e90f7
22 changed files with 606 additions and 405 deletions

View file

@ -0,0 +1,172 @@
// PaddleBall.js
//
// Created by Philip Rosedale on January 21, 2015
// Copyright 2014 High Fidelity, Inc.
//
// Move your hand with the hydra controller, and hit the ball with the paddle.
// Click 'X' button to turn off this script.
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
var BALL_SIZE = 0.08;
var PADDLE_SIZE = 0.20;
var PADDLE_THICKNESS = 0.06;
var PADDLE_COLOR = { red: 184, green: 134, blue: 11 };
var BALL_COLOR = { red: 255, green: 0, blue: 0 };
var LINE_COLOR = { red: 255, green: 255, blue: 0 };
var PADDLE_OFFSET = { x: 0.05, y: 0.0, z: 0.0 };
var GRAVITY = 0.0;
var SPRING_FORCE = 15.0;
var lastSoundTime = 0;
var gameOn = false;
var leftHanded = false;
var controllerID;
if (leftHanded) {
controllerID = 1;
} else {
controllerID = 3;
}
HIFI_PUBLIC_BUCKET = "http://s3.amazonaws.com/hifi-public/";
hitSound = SoundCache.getSound(HIFI_PUBLIC_BUCKET + "sounds/Collisions-ballhitsandcatches/billiards/collision1.wav");
var screenSize = Controller.getViewportDimensions();
var offButton = Overlays.addOverlay("image", {
x: screenSize.x - 48,
y: 96,
width: 32,
height: 32,
imageURL: HIFI_PUBLIC_BUCKET + "images/close.png",
color: { red: 255, green: 255, blue: 255},
alpha: 1
});
var ball, paddle, paddleModel, line;
function createEntities() {
ball = Entities.addEntity(
{ type: "Sphere",
position: Controller.getSpatialControlPosition(controllerID),
dimensions: { x: BALL_SIZE, y: BALL_SIZE, z: BALL_SIZE },
color: BALL_COLOR,
gravity: { x: 0, y: GRAVITY, z: 0 },
ignoreCollisions: false,
damping: 0.50,
collisionsWillMove: true });
paddle = Entities.addEntity(
{ type: "Box",
position: Controller.getSpatialControlPosition(controllerID),
dimensions: { x: PADDLE_SIZE, y: PADDLE_THICKNESS, z: PADDLE_SIZE * 0.80 },
color: PADDLE_COLOR,
gravity: { x: 0, y: 0, z: 0 },
ignoreCollisions: false,
damping: 0.10,
visible: false,
rotation: Quat.multiply(MyAvatar.orientation, Controller.getSpatialControlRawRotation(controllerID)),
collisionsWillMove: false });
modelURL = "http://public.highfidelity.io/models/attachments/pong_paddle.fbx";
paddleModel = Entities.addEntity(
{ type: "Model",
position: Vec3.sum(Controller.getSpatialControlPosition(controllerID), PADDLE_OFFSET),
dimensions: { x: PADDLE_SIZE * 1.5, y: PADDLE_THICKNESS, z: PADDLE_SIZE * 1.25 },
color: PADDLE_COLOR,
gravity: { x: 0, y: 0, z: 0 },
ignoreCollisions: true,
modelURL: modelURL,
damping: 0.10,
rotation: Quat.multiply(MyAvatar.orientation, Controller.getSpatialControlRawRotation(controllerID)),
collisionsWillMove: false });
line = Overlays.addOverlay("line3d", {
start: { x: 0, y: 0, z: 0 },
end: { x: 0, y: 0, z: 0 },
color: LINE_COLOR,
alpha: 1,
visible: true,
lineWidth: 2 });
}
function deleteEntities() {
Entities.deleteEntity(ball);
Entities.deleteEntity(paddle);
Entities.deleteEntity(paddleModel);
Overlays.deleteOverlay(line);
}
function update(deltaTime) {
var palmPosition = Controller.getSpatialControlPosition(controllerID);
var controllerActive = (Vec3.length(palmPosition) > 0);
if (!gameOn && controllerActive) {
createEntities();
gameOn = true;
} else if (gameOn && !controllerActive) {
deleteEntities();
gameOn = false;
}
if (!gameOn || !controllerActive) {
return;
}
if (!paddle.isKnownID) {
paddle = Entities.identifyEntity(paddle);
}
if (!ball.isKnownID) {
ball = Entities.identifyEntity(ball);
} else {
var props = Entities.getEntityProperties(ball);
var spring = Vec3.subtract(palmPosition, props.position);
var paddleWorldOrientation = Quat.multiply(MyAvatar.orientation, Controller.getSpatialControlRawRotation(controllerID));
var springLength = Vec3.length(spring);
spring = Vec3.normalize(spring);
var ballVelocity = Vec3.sum(props.velocity, Vec3.multiply(springLength * SPRING_FORCE * deltaTime, spring));
Entities.editEntity(ball, { velocity: ballVelocity });
Overlays.editOverlay(line, { start: props.position, end: palmPosition });
Entities.editEntity(paddle, { position: palmPosition,
velocity: Controller.getSpatialControlVelocity(controllerID),
rotation: paddleWorldOrientation });
Entities.editEntity(paddleModel, { position: Vec3.sum(palmPosition, Vec3.multiplyQbyV(paddleWorldOrientation, PADDLE_OFFSET)),
velocity: Controller.getSpatialControlVelocity(controllerID),
rotation: paddleWorldOrientation });
}
}
function entityCollisionWithEntity(entity1, entity2, collision) {
if ((entity1.id == ball.id) || (entity2.id ==ball.id)) {
var props1 = Entities.getEntityProperties(entity1);
var props2 = Entities.getEntityProperties(entity2);
var dVel = Vec3.length(Vec3.subtract(props1.velocity, props2.velocity));
var currentTime = new Date().getTime();
var MIN_MSECS_BETWEEN_BOUNCE_SOUNDS = 100;
var MIN_VELOCITY_FOR_SOUND_IMPACT = 0.25;
if ((dVel > MIN_VELOCITY_FOR_SOUND_IMPACT) && (currentTime - lastSoundTime) > MIN_MSECS_BETWEEN_BOUNCE_SOUNDS) {
Audio.playSound(hitSound, { position: props1.position, volume: Math.min(dVel, 1.0) });
lastSoundTime = new Date().getTime();
}
}
}
function mousePressEvent(event) {
var clickedOverlay = Overlays.getOverlayAtPoint({x: event.x, y: event.y});
if (clickedOverlay == offButton) {
Script.stop();
}
}
function scriptEnding() {
if (gameOn) {
deleteEntities();
}
Overlays.deleteOverlay(offButton);
}
Entities.entityCollisionWithEntity.connect(entityCollisionWithEntity);
Controller.mousePressEvent.connect(mousePressEvent);
Script.scriptEnding.connect(scriptEnding);
Script.update.connect(update);

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

@ -1427,6 +1427,8 @@ public:
void setColorMaterial(const StackArray::Entry& entry) { color = entry.color; material = entry.material; }
void mix(const EdgeCrossing& first, const EdgeCrossing& second, float t);
VoxelPoint createPoint(int clampedX, int clampedZ, float step) const;
};
void EdgeCrossing::mix(const EdgeCrossing& first, const EdgeCrossing& second, float t) {
@ -1437,6 +1439,16 @@ void EdgeCrossing::mix(const EdgeCrossing& first, const EdgeCrossing& second, fl
material = (t < 0.5f) ? first.material : second.material;
}
VoxelPoint EdgeCrossing::createPoint(int clampedX, int clampedZ, float step) const {
VoxelPoint voxelPoint = { glm::vec3(clampedX + point.x, point.y, clampedZ + point.z) * step,
{ (quint8)qRed(color), (quint8)qGreen(color), (quint8)qBlue(color) },
{ (char)(normal.x * numeric_limits<qint8>::max()), (char)(normal.y * numeric_limits<qint8>::max()),
(char)(normal.z * numeric_limits<qint8>::max()) },
{ (quint8)material, 0, 0, 0 },
{ numeric_limits<quint8>::max(), 0, 0, 0 } };
return voxelPoint;
}
const int MAX_NORMALS_PER_VERTEX = 4;
class NormalIndex {
@ -1498,6 +1510,84 @@ const NormalIndex& IndexVector::get(int y) const {
return (relative >= 0 && relative < size()) ? at(relative) : invalidIndex;
}
static inline glm::vec3 getNormal(const QVector<VoxelPoint>& vertices, const NormalIndex& i0,
const NormalIndex& i1, const NormalIndex& i2, const NormalIndex& i3) {
// check both triangles in case one is degenerate
const glm::vec3& v0 = vertices.at(i0.indices[0]).vertex;
glm::vec3 normal = glm::cross(vertices.at(i1.indices[0]).vertex - v0, vertices.at(i2.indices[0]).vertex - v0);
if (glm::length(normal) > EPSILON) {
return normal;
}
return glm::cross(vertices.at(i2.indices[0]).vertex - v0, vertices.at(i3.indices[0]).vertex - v0);
}
static inline void appendTriangle(const EdgeCrossing& e0, const EdgeCrossing& e1, const EdgeCrossing& e2,
int clampedX, int clampedZ, float step, QVector<VoxelPoint>& vertices, QVector<int>& indices,
QMultiHash<VoxelCoord, int>& quadIndices) {
int firstIndex = vertices.size();
vertices.append(e0.createPoint(clampedX, clampedZ, step));
vertices.append(e1.createPoint(clampedX, clampedZ, step));
vertices.append(e2.createPoint(clampedX, clampedZ, step));
indices.append(firstIndex);
indices.append(firstIndex + 1);
indices.append(firstIndex + 2);
indices.append(firstIndex + 2);
int minimumY = qMin((int)e0.point.y, qMin((int)e1.point.y, (int)e2.point.y));
int maximumY = qMax((int)e0.point.y, qMax((int)e1.point.y, (int)e2.point.y));
for (int y = minimumY; y <= maximumY; y++) {
quadIndices.insert(qRgb(clampedX, y, clampedZ), firstIndex);
}
}
const int CORNER_COUNT = 4;
static StackArray::Entry getEntry(const StackArray* lineSrc, int stackWidth, int y, float heightfieldHeight,
EdgeCrossing cornerCrossings[CORNER_COUNT], int cornerIndex) {
int offsetX = (cornerIndex & X_MAXIMUM_FLAG) ? 1 : 0;
int offsetZ = (cornerIndex & Y_MAXIMUM_FLAG) ? 1 : 0;
const StackArray& src = lineSrc[offsetZ * stackWidth + offsetX];
int count = src.getEntryCount();
if (count > 0) {
int relative = y - src.getPosition();
if (relative < count && (relative >= 0 || heightfieldHeight == 0.0f)) {
return src.getEntry(y, heightfieldHeight);
}
}
const EdgeCrossing& cornerCrossing = cornerCrossings[cornerIndex];
if (cornerCrossing.point.y == 0.0f) {
return src.getEntry(y, heightfieldHeight);
}
StackArray::Entry entry;
bool set = false;
if (cornerCrossing.point.y >= y) {
entry.color = cornerCrossing.color;
entry.material = cornerCrossing.material;
set = true;
entry.setHermiteY(cornerCrossing.normal, glm::clamp(cornerCrossing.point.y - y, 0.0f, 1.0f));
} else {
entry.material = entry.color = 0;
}
if (!(cornerIndex & X_MAXIMUM_FLAG)) {
const EdgeCrossing& nextCornerCrossingX = cornerCrossings[cornerIndex | X_MAXIMUM_FLAG];
if (nextCornerCrossingX.point.y != 0.0f && (nextCornerCrossingX.point.y >= y) != set) {
float t = glm::clamp((y - cornerCrossing.point.y) /
(nextCornerCrossingX.point.y - cornerCrossing.point.y), 0.0f, 1.0f);
entry.setHermiteX(glm::normalize(glm::mix(cornerCrossing.normal, nextCornerCrossingX.normal, t)), t);
}
}
if (!(cornerIndex & Y_MAXIMUM_FLAG)) {
const EdgeCrossing& nextCornerCrossingZ = cornerCrossings[cornerIndex | Y_MAXIMUM_FLAG];
if (nextCornerCrossingZ.point.y != 0.0f && (nextCornerCrossingZ.point.y >= y) != set) {
float t = glm::clamp((y - cornerCrossing.point.y) /
(nextCornerCrossingZ.point.y - cornerCrossing.point.y), 0.0f, 1.0f);
entry.setHermiteZ(glm::normalize(glm::mix(cornerCrossing.normal, nextCornerCrossingZ.normal, t)), t);
}
}
return entry;
}
void HeightfieldNodeRenderer::render(const HeightfieldNodePointer& node, const glm::vec3& translation,
const glm::quat& rotation, const glm::vec3& scale, bool cursor) {
if (!node->getHeight()) {
@ -1706,7 +1796,6 @@ void HeightfieldNodeRenderer::render(const HeightfieldNodePointer& node, const g
const int LOWER_RIGHT_CORNER = 8;
const int NO_CORNERS = 0;
const int ALL_CORNERS = UPPER_LEFT_CORNER | UPPER_RIGHT_CORNER | LOWER_LEFT_CORNER | LOWER_RIGHT_CORNER;
const int CORNER_COUNT = 4;
const int NEXT_CORNERS[] = { 1, 3, 0, 2 };
int corners = NO_CORNERS;
if (heightfieldHeight != 0.0f) {
@ -1772,6 +1861,15 @@ void HeightfieldNodeRenderer::render(const HeightfieldNodePointer& node, const g
}
minimumY = qMin(minimumY, cornerMinimumY);
maximumY = qMax(maximumY, cornerMaximumY);
if (corners == (LOWER_LEFT_CORNER | UPPER_LEFT_CORNER | UPPER_RIGHT_CORNER)) {
appendTriangle(cornerCrossings[1], cornerCrossings[0], cornerCrossings[2],
clampedX, clampedZ, step, vertices, indices, quadIndices);
} else if (corners == (UPPER_RIGHT_CORNER | LOWER_RIGHT_CORNER | LOWER_LEFT_CORNER)) {
appendTriangle(cornerCrossings[2], cornerCrossings[3], cornerCrossings[1],
clampedX, clampedZ, step, vertices, indices, quadIndices);
}
}
int position = minimumY;
int count = maximumY - minimumY + 1;
@ -1781,7 +1879,7 @@ void HeightfieldNodeRenderer::render(const HeightfieldNodePointer& node, const g
indicesZ[x].position = position;
indicesZ[x].resize(count);
for (int y = position, end = position + count; y < end; y++) {
const StackArray::Entry& entry = lineSrc->getEntry(y, heightfieldHeight);
StackArray::Entry entry = getEntry(lineSrc, stackWidth, y, heightfieldHeight, cornerCrossings, 0);
if (displayHermite && x != 0 && z != 0 && !lineSrc->isEmpty() && y >= lineSrc->getPosition()) {
glm::vec3 normal;
if (entry.hermiteX != 0) {
@ -1846,41 +1944,38 @@ void HeightfieldNodeRenderer::render(const HeightfieldNodePointer& node, const g
if (!(corners & (1 << i))) {
continue;
}
int offsetX = (i & X_MAXIMUM_FLAG) ? 1 : 0;
int offsetZ = (i & Y_MAXIMUM_FLAG) ? 1 : 0;
const quint16* height = heightLineSrc + offsetZ * width + offsetX;
float heightValue = *height * voxelScale;
if (heightValue >= y && heightValue < y + 1) {
const EdgeCrossing& cornerCrossing = cornerCrossings[i];
if (cornerCrossing.point.y >= y && cornerCrossing.point.y < y + 1) {
crossedCorners |= (1 << i);
}
}
switch (crossedCorners) {
case UPPER_LEFT_CORNER:
case LOWER_LEFT_CORNER | UPPER_LEFT_CORNER:
case LOWER_LEFT_CORNER | UPPER_LEFT_CORNER | UPPER_RIGHT_CORNER:
case LOWER_RIGHT_CORNER | LOWER_LEFT_CORNER | UPPER_LEFT_CORNER:
case UPPER_LEFT_CORNER | LOWER_RIGHT_CORNER:
crossings[crossingCount++] = cornerCrossings[0];
crossings[crossingCount - 1].point.y -= y;
break;
case UPPER_RIGHT_CORNER:
case UPPER_LEFT_CORNER | UPPER_RIGHT_CORNER:
case UPPER_LEFT_CORNER | UPPER_RIGHT_CORNER | LOWER_RIGHT_CORNER:
case UPPER_RIGHT_CORNER | LOWER_LEFT_CORNER:
case LOWER_LEFT_CORNER | UPPER_LEFT_CORNER | UPPER_RIGHT_CORNER:
crossings[crossingCount++] = cornerCrossings[1];
crossings[crossingCount - 1].point.y -= y;
break;
case LOWER_LEFT_CORNER:
case LOWER_RIGHT_CORNER | LOWER_LEFT_CORNER:
case LOWER_RIGHT_CORNER | LOWER_LEFT_CORNER | UPPER_LEFT_CORNER:
case UPPER_RIGHT_CORNER | LOWER_RIGHT_CORNER | LOWER_LEFT_CORNER:
crossings[crossingCount++] = cornerCrossings[2];
crossings[crossingCount - 1].point.y -= y;
break;
case LOWER_RIGHT_CORNER:
case UPPER_RIGHT_CORNER | LOWER_RIGHT_CORNER:
case UPPER_RIGHT_CORNER | LOWER_RIGHT_CORNER | LOWER_LEFT_CORNER:
case UPPER_LEFT_CORNER | UPPER_RIGHT_CORNER | LOWER_RIGHT_CORNER:
crossings[crossingCount++] = cornerCrossings[3];
crossings[crossingCount - 1].point.y -= y;
break;
@ -1890,30 +1985,24 @@ void HeightfieldNodeRenderer::render(const HeightfieldNodePointer& node, const g
if (!(corners & (1 << i))) {
continue;
}
int offsetX = (i & X_MAXIMUM_FLAG) ? 1 : 0;
int offsetZ = (i & Y_MAXIMUM_FLAG) ? 1 : 0;
const quint16* height = heightLineSrc + offsetZ * width + offsetX;
float heightValue = *height * voxelScale;
int nextIndex = NEXT_CORNERS[i];
if (!(corners & (1 << nextIndex))) {
continue;
}
int nextOffsetX = (nextIndex & X_MAXIMUM_FLAG) ? 1 : 0;
int nextOffsetZ = (nextIndex & Y_MAXIMUM_FLAG) ? 1 : 0;
const quint16* nextHeight = heightLineSrc + nextOffsetZ * width + nextOffsetX;
float nextHeightValue = *nextHeight * voxelScale;
float divisor = (nextHeightValue - heightValue);
const EdgeCrossing& cornerCrossing = cornerCrossings[i];
const EdgeCrossing& nextCornerCrossing = cornerCrossings[nextIndex];
float divisor = (nextCornerCrossing.point.y - cornerCrossing.point.y);
if (divisor == 0.0f) {
continue;
}
float t1 = (y - heightValue) / divisor;
float t2 = (y + 1 - heightValue) / divisor;
float t1 = (y - cornerCrossing.point.y) / divisor;
float t2 = (y + 1 - cornerCrossing.point.y) / divisor;
if (t1 >= 0.0f && t1 <= 1.0f) {
crossings[crossingCount++].mix(cornerCrossings[i], cornerCrossings[nextIndex], t1);
crossings[crossingCount++].mix(cornerCrossing, nextCornerCrossing, t1);
crossings[crossingCount - 1].point.y -= y;
}
if (t2 >= 0.0f && t2 <= 1.0f) {
crossings[crossingCount++].mix(cornerCrossings[i], cornerCrossings[nextIndex], t2);
crossings[crossingCount++].mix(cornerCrossing, nextCornerCrossing, t2);
crossings[crossingCount - 1].point.y -= y;
}
}
@ -1924,10 +2013,13 @@ void HeightfieldNodeRenderer::render(const HeightfieldNodePointer& node, const g
// the terrifying conditional code that follows checks each cube edge for a crossing, gathering
// its properties (color, material, normal) if one is present; as before, boundary edges are excluded
if (crossingCount == 0) {
const StackArray::Entry& nextEntryY = lineSrc->getEntry(y + 1, heightfieldHeight);
StackArray::Entry nextEntryY = getEntry(lineSrc, stackWidth, y + 1,
heightfieldHeight, cornerCrossings, 0);
if (middleX) {
const StackArray::Entry& nextEntryX = lineSrc[1].getEntry(y, nextHeightfieldHeightX);
const StackArray::Entry& nextEntryXY = lineSrc[1].getEntry(y + 1, nextHeightfieldHeightX);
StackArray::Entry nextEntryX = getEntry(lineSrc, stackWidth, y, nextHeightfieldHeightX,
cornerCrossings, 1);
StackArray::Entry nextEntryXY = getEntry(lineSrc, stackWidth, y + 1, nextHeightfieldHeightX,
cornerCrossings, 1);
if (alpha0 != alpha1) {
EdgeCrossing& crossing = crossings[crossingCount++];
crossing.point = glm::vec3(entry.getHermiteX(crossing.normal), 0.0f, 0.0f);
@ -1944,12 +2036,12 @@ void HeightfieldNodeRenderer::render(const HeightfieldNodePointer& node, const g
crossing.setColorMaterial(alpha2 == 0 ? nextEntryXY : nextEntryY);
}
if (middleZ) {
const StackArray::Entry& nextEntryZ = lineSrc[stackWidth].getEntry(y,
nextHeightfieldHeightZ);
const StackArray::Entry& nextEntryXZ = lineSrc[stackWidth + 1].getEntry(
y, nextHeightfieldHeightXZ);
const StackArray::Entry& nextEntryXYZ = lineSrc[stackWidth + 1].getEntry(
y + 1, nextHeightfieldHeightXZ);
StackArray::Entry nextEntryZ = getEntry(lineSrc, stackWidth, y, nextHeightfieldHeightZ,
cornerCrossings, 2);
StackArray::Entry nextEntryXZ = getEntry(lineSrc, stackWidth, y, nextHeightfieldHeightXZ,
cornerCrossings, 3);
StackArray::Entry nextEntryXYZ = getEntry(lineSrc, stackWidth, y + 1,
nextHeightfieldHeightXZ, cornerCrossings, 3);
if (alpha1 != alpha5) {
EdgeCrossing& crossing = crossings[crossingCount++];
crossing.point = glm::vec3(1.0f, 0.0f, nextEntryX.getHermiteZ(crossing.normal));
@ -1957,8 +2049,8 @@ void HeightfieldNodeRenderer::render(const HeightfieldNodePointer& node, const g
}
if (alpha3 != alpha7) {
EdgeCrossing& crossing = crossings[crossingCount++];
const StackArray::Entry& nextEntryXY = lineSrc[1].getEntry(y + 1,
nextHeightfieldHeightX);
StackArray::Entry nextEntryXY = getEntry(lineSrc, stackWidth, y + 1,
nextHeightfieldHeightX, cornerCrossings, 1);
crossing.point = glm::vec3(1.0f, 1.0f, nextEntryXY.getHermiteZ(crossing.normal));
crossing.setColorMaterial(alpha3 == 0 ? nextEntryXYZ : nextEntryXY);
}
@ -1969,15 +2061,15 @@ void HeightfieldNodeRenderer::render(const HeightfieldNodePointer& node, const g
}
if (alpha5 != alpha7) {
EdgeCrossing& crossing = crossings[crossingCount++];
const StackArray::Entry& nextEntryXZ = lineSrc[stackWidth + 1].getEntry(
y, nextHeightfieldHeightXZ);
StackArray::Entry nextEntryXZ = getEntry(lineSrc, stackWidth, y,
nextHeightfieldHeightXZ, cornerCrossings, 3);
crossing.point = glm::vec3(1.0f, nextEntryXZ.getHermiteY(crossing.normal), 1.0f);
crossing.setColorMaterial(alpha5 == 0 ? nextEntryXYZ : nextEntryXZ);
}
if (alpha6 != alpha7) {
EdgeCrossing& crossing = crossings[crossingCount++];
const StackArray::Entry& nextEntryYZ = lineSrc[stackWidth].getEntry(
y + 1, nextHeightfieldHeightZ);
StackArray::Entry nextEntryYZ = getEntry(lineSrc, stackWidth, y + 1,
nextHeightfieldHeightZ, cornerCrossings, 2);
crossing.point = glm::vec3(nextEntryYZ.getHermiteX(crossing.normal), 1.0f, 1.0f);
crossing.setColorMaterial(alpha6 == 0 ? nextEntryXYZ : nextEntryYZ);
}
@ -1989,9 +2081,10 @@ void HeightfieldNodeRenderer::render(const HeightfieldNodePointer& node, const g
crossing.setColorMaterial(alpha0 == 0 ? nextEntryY : entry);
}
if (middleZ) {
const StackArray::Entry& nextEntryZ = lineSrc[stackWidth].getEntry(y, nextHeightfieldHeightZ);
const StackArray::Entry& nextEntryYZ = lineSrc[stackWidth].getEntry(y + 1,
nextHeightfieldHeightZ);
StackArray::Entry nextEntryZ = getEntry(lineSrc, stackWidth, y,
nextHeightfieldHeightZ, cornerCrossings, 2);
StackArray::Entry nextEntryYZ = getEntry(lineSrc, stackWidth, y + 1,
nextHeightfieldHeightZ, cornerCrossings, 2);
if (alpha0 != alpha4) {
EdgeCrossing& crossing = crossings[crossingCount++];
crossing.point = glm::vec3(0.0f, 0.0f, entry.getHermiteZ(crossing.normal));
@ -2174,10 +2267,7 @@ void HeightfieldNodeRenderer::render(const HeightfieldNodePointer& node, const g
quadIndices.insert(qRgb(reclampedX, y - 1, reclampedZ - 1), indices.size());
quadIndices.insert(qRgb(reclampedX, y, reclampedZ - 1), indices.size());
}
const glm::vec3& first = vertices.at(index.indices[0]).vertex;
glm::vec3 normal = glm::cross(vertices.at(index1.indices[0]).vertex - first,
vertices.at(index3.indices[0]).vertex - first);
glm::vec3 normal = getNormal(vertices, index, index1, index2, index3);
if (alpha0 == 0) { // quad faces negative x
indices.append(index3.getClosestIndex(normal = -normal, vertices));
indices.append(index2.getClosestIndex(normal, vertices));
@ -2206,10 +2296,7 @@ void HeightfieldNodeRenderer::render(const HeightfieldNodePointer& node, const g
if (reclampedZ > 0) {
quadIndices.insert(qRgb(reclampedX, y, reclampedZ - 1), indices.size());
}
const glm::vec3& first = vertices.at(index.indices[0]).vertex;
glm::vec3 normal = glm::cross(vertices.at(index3.indices[0]).vertex - first,
vertices.at(index1.indices[0]).vertex - first);
glm::vec3 normal = getNormal(vertices, index, index3, index2, index1);
if (alpha0 == 0) { // quad faces negative y
indices.append(index3.getClosestIndex(normal, vertices));
indices.append(index2.getClosestIndex(normal, vertices));
@ -2235,10 +2322,7 @@ void HeightfieldNodeRenderer::render(const HeightfieldNodePointer& node, const g
}
quadIndices.insert(qRgb(reclampedX, y - 1, reclampedZ), indices.size());
const glm::vec3& first = vertices.at(index.indices[0]).vertex;
glm::vec3 normal = glm::cross(vertices.at(index1.indices[0]).vertex - first,
vertices.at(index3.indices[0]).vertex - first);
glm::vec3 normal = getNormal(vertices, index, index1, index2, index3);
if (alpha0 == 0) { // quad faces negative z
indices.append(index1.getClosestIndex(normal, vertices));
indices.append(index2.getClosestIndex(normal, vertices));

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

@ -70,7 +70,7 @@
<property name="font">
<font>
<family>Helvetica,Arial,sans-serif</family>
<pointsize>-1</pointsize>
<pointsize>16</pointsize>
<weight>75</weight>
<italic>false</italic>
<bold>true</bold>
@ -78,7 +78,7 @@
</property>
<property name="styleSheet">
<string notr="true">color: #0e7077;
font: bold 16px;
font: bold 16pt;
</string>
</property>
<property name="text">
@ -192,7 +192,7 @@ font: bold 16px;
</sizepolicy>
</property>
<property name="styleSheet">
<string notr="true">font: 14px; color: #5f5f5f; margin: 2px;</string>
<string notr="true">font: 14pt; color: #5f5f5f; margin: 2px;</string>
</property>
<property name="text">
<string>There are no scripts running.</string>
@ -245,8 +245,8 @@ font: bold 16px;
<rect>
<x>0</x>
<y>0</y>
<width>334</width>
<height>20</height>
<width>328</width>
<height>18</height>
</rect>
</property>
<property name="sizePolicy">
@ -259,7 +259,7 @@ font: bold 16px;
<enum>Qt::LeftToRight</enum>
</property>
<property name="styleSheet">
<string notr="true">font-size: 14px;</string>
<string notr="true">font-size: 14pt;</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout_3">
<property name="spacing">
@ -301,7 +301,7 @@ font: bold 16px;
<item>
<widget class="QLabel" name="tipLabel">
<property name="styleSheet">
<string notr="true">font: 14px; color: #5f5f5f; margin: 2px;</string>
<string notr="true">font: 14pt; color: #5f5f5f; margin: 2px;</string>
</property>
<property name="text">
<string>Tip</string>
@ -370,7 +370,7 @@ font: bold 16px;
<widget class="QLabel" name="label">
<property name="styleSheet">
<string notr="true">color: #0e7077;
font: bold 16px;</string>
font: bold 16pt;</string>
</property>
<property name="text">
<string>Load Scripts</string>

View file

@ -551,7 +551,7 @@ int EntityItem::readEntityDataFromBuffer(const unsigned char* data, int bytesLef
bytesRead += readEntitySubclassDataFromBuffer(dataAt, (bytesLeftToRead - bytesRead), args, propertyFlags, overwriteLocalData);
recalculateCollisionShape();
if (overwriteLocalData && (getDirtyFlags() & EntityItem::DIRTY_POSITION)) {
if (overwriteLocalData && (getDirtyFlags() & (EntityItem::DIRTY_POSITION | EntityItem::DIRTY_VELOCITY))) {
// TODO: Andrew & Brad to discuss -- this probably should not be "now" but instead should be the last
// simulated time from server. The logic should maybe be: the position changed from the server, so the
// position we just set can be thought of as the position at the time it was last simulated by the
@ -644,11 +644,6 @@ bool EntityItem::isRestingOnSurface() const {
}
void EntityItem::simulate(const quint64& now) {
if (_physicsInfo) {
// we rely on bullet for simulation, so bail
return;
}
bool wantDebug = false;
if (_lastSimulated == 0) {
@ -698,9 +693,13 @@ void EntityItem::simulate(const quint64& now) {
qDebug() << " ********** EntityItem::simulate() .... SETTING _lastSimulated=" << _lastSimulated;
}
if (hasAngularVelocity()) {
glm::quat rotation = getRotation();
simulateKinematicMotion(timeElapsed);
_lastSimulated = now;
}
void EntityItem::simulateKinematicMotion(float timeElapsed) {
bool wantDebug = false;
if (hasAngularVelocity()) {
// angular damping
glm::vec3 angularVelocity = getAngularVelocity();
if (_angularDamping > 0.0f) {
@ -716,6 +715,9 @@ void EntityItem::simulate(const quint64& now) {
const float EPSILON_ANGULAR_VELOCITY_LENGTH = 0.1f; //
if (angularSpeed < EPSILON_ANGULAR_VELOCITY_LENGTH) {
if (angularSpeed > 0.0f) {
_dirtyFlags |= EntityItem::DIRTY_MOTION_TYPE;
}
setAngularVelocity(ENTITY_ITEM_ZERO_VEC3);
} else {
// NOTE: angularSpeed is currently in degrees/sec!!!
@ -723,7 +725,7 @@ void EntityItem::simulate(const quint64& now) {
float angle = timeElapsed * glm::radians(angularSpeed);
glm::vec3 axis = _angularVelocity / angularSpeed;
glm::quat dQ = glm::angleAxis(angle, axis);
rotation = glm::normalize(dQ * rotation);
glm::quat rotation = glm::normalize(dQ * getRotation());
setRotation(rotation);
}
}
@ -759,87 +761,31 @@ void EntityItem::simulate(const quint64& now) {
position = newPosition;
// handle bounces off the ground... We bounce at the distance to the bottom of our entity
if (position.y <= getDistanceToBottomOfEntity()) {
velocity = velocity * glm::vec3(1,-1,1);
position.y = getDistanceToBottomOfEntity();
}
// apply gravity
if (hasGravity()) {
// handle resting on surface case, this is definitely a bit of a hack, and it only works on the
// "ground" plane of the domain, but for now it's what we've got
if (isRestingOnSurface()) {
velocity.y = 0.0f;
position.y = getDistanceToBottomOfEntity();
} else {
velocity += getGravity() * timeElapsed;
}
}
// NOTE: we don't zero out very small velocities --> we rely on a remote Bullet simulation
// to tell us when the entity has stopped.
// NOTE: the simulation should NOT set any DirtyFlags on this entity
setPosition(position); // this will automatically recalculate our collision shape
setVelocity(velocity);
if (wantDebug) {
qDebug() << " new position:" << position;
qDebug() << " new velocity:" << velocity;
qDebug() << " new AACube:" << getMaximumAACube();
qDebug() << " old getAABox:" << getAABox();
}
}
_lastSimulated = now;
}
void EntityItem::simulateSimpleKinematicMotion(float timeElapsed) {
if (hasAngularVelocity()) {
// angular damping
glm::vec3 angularVelocity = getAngularVelocity();
if (_angularDamping > 0.0f) {
angularVelocity *= powf(1.0f - _angularDamping, timeElapsed);
setAngularVelocity(angularVelocity);
}
float angularSpeed = glm::length(_angularVelocity);
const float EPSILON_ANGULAR_VELOCITY_LENGTH = 0.1f; //
if (angularSpeed < EPSILON_ANGULAR_VELOCITY_LENGTH) {
setAngularVelocity(ENTITY_ITEM_ZERO_VEC3);
} else {
// NOTE: angularSpeed is currently in degrees/sec!!!
// TODO: Andrew to convert to radians/sec
float angle = timeElapsed * glm::radians(angularSpeed);
glm::vec3 axis = _angularVelocity / angularSpeed;
glm::quat dQ = glm::angleAxis(angle, axis);
glm::quat rotation = getRotation();
rotation = glm::normalize(dQ * rotation);
setRotation(rotation);
}
}
if (hasVelocity()) {
// linear damping
glm::vec3 velocity = getVelocity();
if (_damping > 0.0f) {
velocity *= powf(1.0f - _damping, timeElapsed);
}
// integrate position forward
glm::vec3 position = getPosition() + (velocity * timeElapsed);
// apply gravity
if (hasGravity()) {
// handle resting on surface case, this is definitely a bit of a hack, and it only works on the
// "ground" plane of the domain, but for now it's what we've got
velocity += getGravity() * timeElapsed;
}
// NOTE: the simulation should NOT set any DirtyFlags on this entity
setPosition(position); // this will automatically recalculate our collision shape
setVelocity(velocity);
float speed = glm::length(velocity);
const float EPSILON_LINEAR_VELOCITY_LENGTH = 0.001f / (float)TREE_SCALE; // 1mm/sec
if (speed < EPSILON_LINEAR_VELOCITY_LENGTH) {
setVelocity(ENTITY_ITEM_ZERO_VEC3);
if (speed > 0.0f) {
_dirtyFlags |= EntityItem::DIRTY_MOTION_TYPE;
}
} else {
setPosition(position);
setVelocity(velocity);
}
if (wantDebug) {
qDebug() << " new position:" << position;
qDebug() << " new velocity:" << velocity;
qDebug() << " new AACube:" << getMaximumAACube();
qDebug() << " old getAABox:" << getAABox();
}
}
}
@ -923,7 +869,7 @@ bool EntityItem::setProperties(const EntityItemProperties& properties) {
if (_created != UNKNOWN_CREATED_TIME) {
setLastEdited(now);
}
if (getDirtyFlags() & EntityItem::DIRTY_POSITION) {
if (getDirtyFlags() & (EntityItem::DIRTY_POSITION | EntityItem::DIRTY_VELOCITY)) {
// TODO: Andrew & Brad to discuss. Is this correct? Maybe it is. Need to think through all cases.
_lastSimulated = now;
}

View file

@ -82,6 +82,7 @@ public:
void recordCreationTime(); // set _created to 'now'
quint64 getLastSimulated() const { return _lastSimulated; } /// Last simulated time of this entity universal usecs
void setLastSimulated(quint64 now) { _lastSimulated = now; }
/// Last edited time of this entity universal usecs
quint64 getLastEdited() const { return _lastEdited; }
@ -129,9 +130,8 @@ public:
// perform linear extrapolation for SimpleEntitySimulation
void simulate(const quint64& now);
void simulateKinematicMotion(float timeElapsed);
void simulateSimpleKinematicMotion(float timeElapsed);
virtual bool needsToCallUpdate() const { return false; }
virtual void debugDump() const;

View file

@ -14,7 +14,7 @@
#include "BulletUtil.h"
#include "EntityMotionState.h"
#include "SimpleEntityKinematicController.h"
#include "PhysicsEngine.h"
QSet<EntityItem*>* _outgoingEntityList;
@ -41,8 +41,6 @@ EntityMotionState::~EntityMotionState() {
assert(_entity);
_entity->setPhysicsInfo(NULL);
_entity = NULL;
delete _kinematicController;
_kinematicController = NULL;
}
MotionType EntityMotionState::computeMotionType() const {
@ -52,13 +50,16 @@ MotionType EntityMotionState::computeMotionType() const {
return _entity->isMoving() ? MOTION_TYPE_KINEMATIC : MOTION_TYPE_STATIC;
}
void EntityMotionState::addKinematicController() {
if (!_kinematicController) {
_kinematicController = new SimpleEntityKinematicController(_entity);
_kinematicController->start();
} else {
_kinematicController->start();
}
void EntityMotionState::updateKinematicState(uint32_t substep) {
setKinematic(_entity->isMoving(), substep);
}
void EntityMotionState::stepKinematicSimulation(quint64 now) {
assert(_isKinematic);
// NOTE: this is non-physical kinematic motion which steps to real run-time (now)
// which is different from physical kinematic motion (inside getWorldTransform())
// which steps in physics simulation time.
_entity->simulate(now);
}
// This callback is invoked by the physics simulation in two cases:
@ -67,8 +68,16 @@ void EntityMotionState::addKinematicController() {
// (2) at the beginning of each simulation frame for KINEMATIC RigidBody's --
// it is an opportunity for outside code to update the object's simulation position
void EntityMotionState::getWorldTransform(btTransform& worldTrans) const {
if (_kinematicController && _kinematicController->isRunning()) {
_kinematicController->stepForward();
if (_isKinematic) {
// This is physical kinematic motion which steps strictly by the subframe count
// of the physics simulation.
uint32_t substep = PhysicsEngine::getNumSubsteps();
float dt = (substep - _lastKinematicSubstep) * PHYSICS_ENGINE_FIXED_SUBSTEP;
_entity->simulateKinematicMotion(dt);
_entity->setLastSimulated(usecTimestampNow());
// bypass const-ness so we can remember the substep
const_cast<EntityMotionState*>(this)->_lastKinematicSubstep = substep;
}
worldTrans.setOrigin(glmToBullet(_entity->getPositionInMeters() - ObjectMotionState::getWorldOffset()));
worldTrans.setRotation(glmToBullet(_entity->getRotation()));
@ -230,12 +239,14 @@ void EntityMotionState::sendUpdate(OctreeEditPacketSender* packetSender, uint32_
uint32_t EntityMotionState::getIncomingDirtyFlags() const {
uint32_t dirtyFlags = _entity->getDirtyFlags();
// we add DIRTY_MOTION_TYPE if the body's motion type disagrees with entity velocity settings
int bodyFlags = _body->getCollisionFlags();
bool isMoving = _entity->isMoving();
if (((bodyFlags & btCollisionObject::CF_STATIC_OBJECT) && isMoving) ||
(bodyFlags & btCollisionObject::CF_KINEMATIC_OBJECT && !isMoving)) {
dirtyFlags |= EntityItem::DIRTY_MOTION_TYPE;
if (_body) {
// we add DIRTY_MOTION_TYPE if the body's motion type disagrees with entity velocity settings
int bodyFlags = _body->getCollisionFlags();
bool isMoving = _entity->isMoving();
if (((bodyFlags & btCollisionObject::CF_STATIC_OBJECT) && isMoving) ||
(bodyFlags & btCollisionObject::CF_KINEMATIC_OBJECT && !isMoving)) {
dirtyFlags |= EntityItem::DIRTY_MOTION_TYPE;
}
}
return dirtyFlags;
}

View file

@ -14,7 +14,6 @@
#include <AACube.h>
#include "KinematicController.h"
#include "ObjectMotionState.h"
class EntityItem;
@ -39,8 +38,8 @@ public:
/// \return MOTION_TYPE_DYNAMIC or MOTION_TYPE_STATIC based on params set in EntityItem
MotionType computeMotionType() const;
// virtual override for ObjectMotionState
void addKinematicController();
void updateKinematicState(uint32_t substep);
void stepKinematicSimulation(quint64 now);
// this relays incoming position/rotation to the RigidBody
void getWorldTransform(btTransform& worldTrans) const;

View file

@ -1,22 +0,0 @@
//
// KinematicController.cpp
// libraries/physcis/src
//
// Created by Andrew Meadows 2015.01.13
// Copyright 2015 High Fidelity, Inc.
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
#include "KinematicController.h"
#include "PhysicsEngine.h"
KinematicController::KinematicController() {
_lastSubstep = PhysicsEngine::getNumSubsteps();
}
void KinematicController::start() {
_enabled = true;
_lastSubstep = PhysicsEngine::getNumSubsteps();
}

View file

@ -1,36 +0,0 @@
//
// KinematicController.h
// libraries/physcis/src
//
// Created by Andrew Meadows 2015.01.13
// Copyright 2015 High Fidelity, Inc.
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
#ifndef hifi_KinematicController_h
#define hifi_KinematicController_h
#include <stdint.h>
/// KinematicController defines an API for derived classes.
class KinematicController {
public:
KinematicController();
virtual ~KinematicController() {}
virtual void stepForward() = 0;
void start();
void stop() { _enabled = false; }
bool isRunning() const { return _enabled; }
protected:
bool _enabled = false;
uint32_t _lastSubstep;
};
#endif // hifi_KinematicController_h

View file

@ -12,7 +12,6 @@
#include <math.h>
#include "BulletUtil.h"
#include "KinematicController.h"
#include "ObjectMotionState.h"
#include "PhysicsEngine.h"
@ -56,10 +55,6 @@ ObjectMotionState::ObjectMotionState() :
ObjectMotionState::~ObjectMotionState() {
// NOTE: you MUST remove this MotionState from the world before you call the dtor.
assert(_body == NULL);
if (_kinematicController) {
delete _kinematicController;
_kinematicController = NULL;
}
}
void ObjectMotionState::setFriction(float friction) {
@ -108,7 +103,6 @@ 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());
@ -117,7 +111,7 @@ bool ObjectMotionState::shouldSendUpdate(uint32_t simulationFrame) {
_sentFrame = simulationFrame;
return false;
}
float dt = (float)(simulationFrame - _sentFrame) * PHYSICS_ENGINE_FIXED_SUBSTEP;
_sentFrame = simulationFrame;
bool isActive = _body->isActive();
@ -174,13 +168,6 @@ bool ObjectMotionState::shouldSendUpdate(uint32_t simulationFrame) {
return (fabsf(glm::dot(actualRotation, _sentRotation)) < MIN_ROTATION_DOT);
}
void ObjectMotionState::removeKinematicController() {
if (_kinematicController) {
delete _kinematicController;
_kinematicController = NULL;
}
}
void ObjectMotionState::setRigidBody(btRigidBody* body) {
// give the body a (void*) back-pointer to this ObjectMotionState
if (_body != body) {
@ -193,3 +180,8 @@ void ObjectMotionState::setRigidBody(btRigidBody* body) {
}
}
}
void ObjectMotionState::setKinematic(bool kinematic, uint32_t substep) {
_isKinematic = kinematic;
_lastKinematicSubstep = substep;
}

View file

@ -46,7 +46,6 @@ const uint32_t OUTGOING_DIRTY_PHYSICS_FLAGS = EntityItem::DIRTY_POSITION | Entit
class OctreeEditPacketSender;
class KinematicController;
class ObjectMotionState : public btMotionState {
public:
@ -93,11 +92,15 @@ public:
virtual MotionType computeMotionType() const = 0;
virtual void addKinematicController() = 0;
virtual void removeKinematicController();
virtual void updateKinematicState(uint32_t substep) = 0;
btRigidBody* getRigidBody() const { return _body; }
bool isKinematic() const { return _isKinematic; }
void setKinematic(bool kinematic, uint32_t substep);
virtual void stepKinematicSimulation(quint64 now) = 0;
friend class PhysicsEngine;
protected:
void setRigidBody(btRigidBody* body);
@ -114,6 +117,9 @@ protected:
btRigidBody* _body;
bool _isKinematic = false;
uint32_t _lastKinematicSubstep = 0;
bool _sentMoving; // true if last update was moving
int _numNonMovingUpdates; // RELIABLE_SEND_HACK for "not so reliable" resends of packets for non-moving objects
@ -124,8 +130,6 @@ protected:
glm::vec3 _sentVelocity;
glm::vec3 _sentAngularVelocity; // radians per second
glm::vec3 _sentAcceleration;
KinematicController* _kinematicController = NULL;
};
#endif // hifi_ObjectMotionState_h

View file

@ -62,7 +62,13 @@ void PhysicsEngine::addEntityInternal(EntityItem* entity) {
entity->setPhysicsInfo(static_cast<void*>(motionState));
_entityMotionStates.insert(motionState);
addObject(shapeInfo, shape, motionState);
} else {
} else if (entity->isMoving()) {
EntityMotionState* motionState = new EntityMotionState(entity);
entity->setPhysicsInfo(static_cast<void*>(motionState));
_entityMotionStates.insert(motionState);
motionState->setKinematic(true, _numSubsteps);
_nonPhysicalKinematicObjects.insert(motionState);
// We failed to add the entity to the simulation. Probably because we couldn't create a shape for it.
//qDebug() << "failed to add entity " << entity->getEntityItemID() << " to physics engine";
}
@ -74,10 +80,16 @@ void PhysicsEngine::removeEntityInternal(EntityItem* entity) {
void* physicsInfo = entity->getPhysicsInfo();
if (physicsInfo) {
EntityMotionState* motionState = static_cast<EntityMotionState*>(physicsInfo);
removeObject(motionState);
if (motionState->getRigidBody()) {
removeObject(motionState);
} else {
// only need to hunt in this list when there is no RigidBody
_nonPhysicalKinematicObjects.remove(motionState);
}
_entityMotionStates.remove(motionState);
_incomingChanges.remove(motionState);
_outgoingPackets.remove(motionState);
// NOTE: EntityMotionState dtor will remove its backpointer from EntityItem
delete motionState;
}
}
@ -117,6 +129,7 @@ void PhysicsEngine::clearEntitiesInternal() {
delete (*stateItr);
}
_entityMotionStates.clear();
_nonPhysicalKinematicObjects.clear();
_incomingChanges.clear();
_outgoingPackets.clear();
}
@ -127,19 +140,75 @@ void PhysicsEngine::relayIncomingChangesToSimulation() {
QSet<ObjectMotionState*>::iterator stateItr = _incomingChanges.begin();
while (stateItr != _incomingChanges.end()) {
ObjectMotionState* motionState = *stateItr;
++stateItr;
uint32_t flags = motionState->getIncomingDirtyFlags() & DIRTY_PHYSICS_FLAGS;
bool removeMotionState = false;
btRigidBody* body = motionState->getRigidBody();
if (body) {
if (flags & HARD_DIRTY_PHYSICS_FLAGS) {
// a HARD update requires the body be pulled out of physics engine, changed, then reinserted
// but it also handles all EASY changes
updateObjectHard(body, motionState, flags);
bool success = updateObjectHard(body, motionState, flags);
if (!success) {
// NOTE: since updateObjectHard() failed we know that motionState has been removed
// from simulation and body has been deleted. Depending on what else has changed
// we might need to remove motionState altogether...
if (flags & EntityItem::DIRTY_VELOCITY) {
motionState->updateKinematicState(_numSubsteps);
if (motionState->isKinematic()) {
// all is NOT lost, we still need to move this object around kinematically
_nonPhysicalKinematicObjects.insert(motionState);
} else {
// no need to keep motionState around
removeMotionState = true;
}
} else {
// no need to keep motionState around
removeMotionState = true;
}
}
} else if (flags) {
// an EASY update does NOT require that the body be pulled out of physics engine
// hence the MotionState has all the knowledge and authority to perform the update.
motionState->updateObjectEasy(flags, _numSubsteps);
}
} else {
// the only way we should ever get here (motionState exists but no body) is when the object
// is undergoing non-physical kinematic motion.
assert(_nonPhysicalKinematicObjects.contains(motionState));
// it is possible that the changes are such that the object can now be added to the physical simulation
if (flags & EntityItem::DIRTY_SHAPE) {
ShapeInfo shapeInfo;
motionState->computeShapeInfo(shapeInfo);
btCollisionShape* shape = _shapeManager.getShape(shapeInfo);
if (shape) {
addObject(shapeInfo, shape, motionState);
_nonPhysicalKinematicObjects.remove(motionState);
} else if (flags & EntityItem::DIRTY_VELOCITY) {
// although we couldn't add the object to the simulation, might need to update kinematic motion...
motionState->updateKinematicState(_numSubsteps);
if (!motionState->isKinematic()) {
_nonPhysicalKinematicObjects.remove(motionState);
removeMotionState = true;
}
}
} else if (flags & EntityItem::DIRTY_VELOCITY) {
// although we still can't add to physics simulation, might need to update kinematic motion...
motionState->updateKinematicState(_numSubsteps);
if (!motionState->isKinematic()) {
_nonPhysicalKinematicObjects.remove(motionState);
removeMotionState = true;
}
}
}
if (removeMotionState) {
// if we get here then there is no need to keep this motionState around (no physics or kinematics)
_outgoingPackets.remove(motionState);
// NOTE: motionState will clean up its own backpointers in the Object
delete motionState;
continue;
}
// NOTE: the grand order of operations is:
@ -152,7 +221,6 @@ void PhysicsEngine::relayIncomingChangesToSimulation() {
// outgoing changes at this point.
motionState->clearOutgoingPacketFlags(flags); // clear outgoing flags that were trumped
motionState->clearIncomingDirtyFlags(flags); // clear incoming flags that were processed
++stateItr;
}
_incomingChanges.clear();
}
@ -186,22 +254,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);
@ -229,6 +281,7 @@ void PhysicsEngine::stepSimulation() {
// This is step (2).
int numSubsteps = _dynamicsWorld->stepSimulation(timeStep, MAX_NUM_SUBSTEPS, PHYSICS_ENGINE_FIXED_SUBSTEP);
_numSubsteps += (uint32_t)numSubsteps;
stepNonPhysicalKinematics(usecTimestampNow());
unlock();
if (numSubsteps > 0) {
@ -250,6 +303,17 @@ void PhysicsEngine::stepSimulation() {
}
}
void PhysicsEngine::stepNonPhysicalKinematics(const quint64& now) {
QSet<ObjectMotionState*>::iterator stateItr = _nonPhysicalKinematicObjects.begin();
while (stateItr != _nonPhysicalKinematicObjects.end()) {
ObjectMotionState* motionState = *stateItr;
motionState->stepKinematicSimulation(now);
++stateItr;
}
}
// TODO?: need to occasionally scan for stopped non-physical kinematics objects
void PhysicsEngine::computeCollisionEvents() {
// update all contacts every frame
int numManifolds = _collisionDispatcher->getNumManifolds();
@ -348,7 +412,7 @@ void PhysicsEngine::addObject(const ShapeInfo& shapeInfo, btCollisionShape* shap
body->setCollisionFlags(btCollisionObject::CF_KINEMATIC_OBJECT);
body->updateInertiaTensor();
motionState->setRigidBody(body);
motionState->addKinematicController();
motionState->setKinematic(true, _numSubsteps);
const float KINEMATIC_LINEAR_VELOCITY_THRESHOLD = 0.01f; // 1 cm/sec
const float KINEMATIC_ANGULAR_VELOCITY_THRESHOLD = 0.01f; // ~1 deg/sec
body->setSleepingThresholds(KINEMATIC_LINEAR_VELOCITY_THRESHOLD, KINEMATIC_ANGULAR_VELOCITY_THRESHOLD);
@ -360,6 +424,7 @@ void PhysicsEngine::addObject(const ShapeInfo& shapeInfo, btCollisionShape* shap
body = new btRigidBody(mass, motionState, shape, inertia);
body->updateInertiaTensor();
motionState->setRigidBody(body);
motionState->setKinematic(false, _numSubsteps);
motionState->updateObjectVelocities();
// NOTE: Bullet will deactivate any object whose velocity is below these thresholds for longer than 2 seconds.
// (the 2 seconds is determined by: static btRigidBody::gDeactivationTime
@ -374,6 +439,7 @@ void PhysicsEngine::addObject(const ShapeInfo& shapeInfo, btCollisionShape* shap
body->setCollisionFlags(btCollisionObject::CF_STATIC_OBJECT);
body->updateInertiaTensor();
motionState->setRigidBody(body);
motionState->setKinematic(false, _numSubsteps);
break;
}
}
@ -384,7 +450,7 @@ void PhysicsEngine::addObject(const ShapeInfo& shapeInfo, btCollisionShape* shap
_dynamicsWorld->addRigidBody(body);
}
bool PhysicsEngine::removeObject(ObjectMotionState* motionState) {
void PhysicsEngine::removeObject(ObjectMotionState* motionState) {
assert(motionState);
btRigidBody* body = motionState->getRigidBody();
if (body) {
@ -395,16 +461,14 @@ bool PhysicsEngine::removeObject(ObjectMotionState* motionState) {
_shapeManager.releaseShape(shapeInfo);
delete body;
motionState->setRigidBody(NULL);
motionState->removeKinematicController();
motionState->setKinematic(false, _numSubsteps);
removeContacts(motionState);
return true;
}
return false;
}
// private
void PhysicsEngine::updateObjectHard(btRigidBody* body, ObjectMotionState* motionState, uint32_t flags) {
bool PhysicsEngine::updateObjectHard(btRigidBody* body, ObjectMotionState* motionState, uint32_t flags) {
MotionType newType = motionState->computeMotionType();
// pull body out of physics engine
@ -419,7 +483,16 @@ void PhysicsEngine::updateObjectHard(btRigidBody* body, ObjectMotionState* motio
ShapeInfo shapeInfo;
motionState->computeShapeInfo(shapeInfo);
btCollisionShape* newShape = _shapeManager.getShape(shapeInfo);
if (newShape != oldShape) {
if (!newShape) {
// FAIL! we are unable to support these changes!
_shapeManager.releaseShape(oldShape);
delete body;
motionState->setRigidBody(NULL);
motionState->setKinematic(false, _numSubsteps);
removeContacts(motionState);
return false;
} else if (newShape != oldShape) {
// BUG: if shape doesn't change but density does then we won't compute new mass properties
// TODO: fix this BUG by replacing DIRTY_MASS with DIRTY_DENSITY and then fix logic accordingly.
body->setCollisionShape(newShape);
@ -452,7 +525,7 @@ void PhysicsEngine::updateObjectHard(btRigidBody* body, ObjectMotionState* motio
body->setMassProps(0.0f, btVector3(0.0f, 0.0f, 0.0f));
body->updateInertiaTensor();
motionState->addKinematicController();
motionState->setKinematic(true, _numSubsteps);
break;
}
case MOTION_TYPE_DYNAMIC: {
@ -469,7 +542,7 @@ void PhysicsEngine::updateObjectHard(btRigidBody* body, ObjectMotionState* motio
body->updateInertiaTensor();
}
body->forceActivationState(ACTIVE_TAG);
motionState->removeKinematicController();
motionState->setKinematic(false, _numSubsteps);
break;
}
default: {
@ -484,7 +557,7 @@ void PhysicsEngine::updateObjectHard(btRigidBody* body, ObjectMotionState* motio
body->setLinearVelocity(btVector3(0.0f, 0.0f, 0.0f));
body->setAngularVelocity(btVector3(0.0f, 0.0f, 0.0f));
motionState->removeKinematicController();
motionState->setKinematic(false, _numSubsteps);
break;
}
}
@ -493,4 +566,5 @@ void PhysicsEngine::updateObjectHard(btRigidBody* body, ObjectMotionState* motio
_dynamicsWorld->addRigidBody(body);
body->activate();
return true;
}

View file

@ -67,6 +67,7 @@ public:
virtual void init(EntityEditPacketSender* packetSender);
void stepSimulation();
void stepNonPhysicalKinematics(const quint64& now);
void computeCollisionEvents();
@ -81,15 +82,16 @@ public:
void addObject(const ShapeInfo& shapeInfo, btCollisionShape* shape, ObjectMotionState* motionState);
/// \param motionState pointer to Object's MotionState
/// \return true if Object removed
bool removeObject(ObjectMotionState* motionState);
void removeObject(ObjectMotionState* motionState);
/// process queue of changed from external sources
void relayIncomingChangesToSimulation();
private:
void removeContacts(ObjectMotionState* motionState);
void updateObjectHard(btRigidBody* body, ObjectMotionState* motionState, uint32_t flags);
// return 'true' of update was successful
bool updateObjectHard(btRigidBody* body, ObjectMotionState* motionState, uint32_t flags);
void updateObjectEasy(btRigidBody* body, ObjectMotionState* motionState, uint32_t flags);
btClock _clock;
@ -104,6 +106,7 @@ private:
// EntitySimulation stuff
QSet<EntityMotionState*> _entityMotionStates; // all entities that we track
QSet<ObjectMotionState*> _nonPhysicalKinematicObjects; // not in physics simulation, but still need kinematic simulation
QSet<ObjectMotionState*> _incomingChanges; // entities with pending physics changes by script or packet
QSet<ObjectMotionState*> _outgoingPackets; // MotionStates with pending changes that need to be sent over wire

View file

@ -1,21 +0,0 @@
//
// SimpleEntityKinematicController.cpp
// libraries/physcis/src
//
// Created by Andrew Meadows 2015.01.13
// Copyright 2015 High Fidelity, Inc.
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
#include "PhysicsEngine.h"
#include "SimpleEntityKinematicController.h"
void SimpleEntityKinematicController:: stepForward() {
uint32_t substep = PhysicsEngine::getNumSubsteps();
float dt = (substep - _lastSubstep) * PHYSICS_ENGINE_FIXED_SUBSTEP;
_entity->simulateSimpleKinematicMotion(dt);
_lastSubstep = substep;
}

View file

@ -1,38 +0,0 @@
//
// SimpleEntityKinematicController.h
// libraries/physcis/src
//
// Created by Andrew Meadows 2015.01.13
// Copyright 2015 High Fidelity, Inc.
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
#ifndef hifi_SimpleEntityKinematicController_h
#define hifi_SimpleEntityKinematicController_h
/// SimpleKinematicConstroller performs simple exrapolation of velocities.
#include <assert.h>
#include <glm/glm.hpp>
#include <EntityItem.h>
#include "KinematicController.h"
class SimpleEntityKinematicController : public KinematicController {
public:
SimpleEntityKinematicController() = delete; // prevent compiler from making a default ctor
SimpleEntityKinematicController(EntityItem* entity) : KinematicController(), _entity(entity) { assert(entity); }
~SimpleEntityKinematicController() { _entity = NULL; }
void stepForward();
private:
EntityItem* _entity;
};
#endif // hifi_SimpleEntityKinematicController_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.