mirror of
https://github.com/overte-org/overte.git
synced 2025-04-20 04:44:11 +02:00
Merge branch 'master' of https://github.com/highfidelity/hifi into fixGlitches
Conflicts: libraries/entities/src/EntityItem.cpp
This commit is contained in:
commit
9cc61e90f7
22 changed files with 606 additions and 405 deletions
172
examples/controllers/hydra/paddleBall.js
Normal file
172
examples/controllers/hydra/paddleBall.js
Normal 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);
|
|
@ -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()
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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
|
|
@ -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));
|
||||
|
|
|
@ -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()) {
|
||||
|
|
|
@ -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;
|
||||
};
|
||||
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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();
|
||||
}
|
|
@ -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
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
@ -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
|
|
@ -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);
|
||||
|
|
|
@ -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.
|
||||
|
|
Loading…
Reference in a new issue