mirror of
https://github.com/HifiExperiments/overte.git
synced 2025-08-13 01:06:14 +02:00
Merge branch 'master' of https://github.com/worklist/hifi into 20224
This commit is contained in:
commit
42cf9e846a
32 changed files with 1091 additions and 936 deletions
29
BUILD_WIN.md
29
BUILD_WIN.md
|
@ -132,6 +132,35 @@ This package contains only headers, so there's nothing to add to the PATH.
|
|||
|
||||
Be careful with glm. For the folder other libraries would normally call 'include', the folder containing the headers, glm opts to use 'glm'. You will have a glm folder nested inside the top-level glm folder.
|
||||
|
||||
###Bullet
|
||||
|
||||
Bullet 2.82 source can be downloaded [here](https://code.google.com/p/bullet/downloads/detail?name=bullet-2.82-r2704.zip). Bullet does not come with prebuilt libraries, you need to make those yourself.
|
||||
|
||||
* Download the zip file and extract into a temporary folder
|
||||
* Create a directory named cmakebuild. Bullet comes with a build\ directory by default, however, that directory is intended for use with premake, and considering premake doesn't support VS2013, I prefer to run the cmake build on its own directory.
|
||||
* Make the following modifications to Bullet's source:
|
||||
1. In file: Extras\HACD\hacdICHull.cpp --- in line: 17 --- insert: #include <algorithm>
|
||||
2. In file: src\MiniCL\cl_MiniCL_Defs.h --- comment lines 364 to 372
|
||||
3. In file: CMakeLists.txt set to ON the option USE_MSVC_RUNTIME_LIBRARY_DLL in line 27
|
||||
|
||||
Then create the Visual Studio solution and build the libraries - run the following commands from a Visual Studio 2013 command prompt, from within the cmakebuild directory created before:
|
||||
|
||||
```shell
|
||||
cmake .. -G "Visual Studio 12"
|
||||
msbuild BULLET_PHYSICS.sln /p:Configuration=Debug
|
||||
```
|
||||
|
||||
This will create Debug libraries in cmakebuild\lib\Debug you can replace Debug with Release in the msbuild command and that will generate Release libraries in cmakebuild\lib\Release.
|
||||
|
||||
You now have Bullet libraries compiled, now you need to put them in the right place for hifi to find them:
|
||||
|
||||
* Create a directory named bullet\ inside your %HIFI_LIB_DIR%
|
||||
* Create two directores named lib\ and include\ inside bullet\
|
||||
* Copy all the contents inside src\ from the bullet unzip path into %HIFI_LIB_DIR%\bullet\include\
|
||||
* Copy all the contents inside cmakebuild\lib\ into %HIFI_LIB_DIR\bullet\lib
|
||||
|
||||
_Note that the INSTALL target should handle the copying of files into an install directory automatically, however, without modifications to Cmake, the install target didn't work right for me, please update this instructions if you get that working right - Leo <leo@highfidelity.io>_
|
||||
|
||||
###Build High Fidelity using Visual Studio
|
||||
Follow the same build steps from the CMake section, but pass a different generator to CMake.
|
||||
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
// Pool Table
|
||||
var tableParts = [];
|
||||
var balls = [];
|
||||
var cueBall;
|
||||
|
||||
var LENGTH = 2.84;
|
||||
var WIDTH = 1.42;
|
||||
|
@ -13,6 +14,8 @@ var HOLE_SIZE = BALL_SIZE;
|
|||
var DROP_HEIGHT = BALL_SIZE * 3.0;
|
||||
var GRAVITY = -9.8;
|
||||
var BALL_GAP = 0.001;
|
||||
var tableCenter;
|
||||
var cuePosition;
|
||||
|
||||
var startStroke = 0;
|
||||
|
||||
|
@ -23,7 +26,7 @@ var reticle = Overlays.addOverlay("image", {
|
|||
y: screenSize.y / 2 - 16,
|
||||
width: 32,
|
||||
height: 32,
|
||||
imageURL: HIFI_PUBLIC_BUCKET + "images/reticle.png",
|
||||
imageURL: HIFI_PUBLIC_BUCKET + "images/billiardsReticle.png",
|
||||
color: { red: 255, green: 255, blue: 255},
|
||||
alpha: 1
|
||||
});
|
||||
|
@ -102,7 +105,7 @@ function makeBalls(pos) {
|
|||
{ red: 128, green: 128, blue: 128}]; // Gray
|
||||
|
||||
// Object balls
|
||||
var ballPosition = { x: pos.x + (LENGTH / 4.0) * SCALE, y: pos.y + DROP_HEIGHT, z: pos.z };
|
||||
var ballPosition = { x: pos.x + (LENGTH / 4.0) * SCALE, y: pos.y + HEIGHT / 2.0 + DROP_HEIGHT, z: pos.z };
|
||||
for (var row = 1; row <= 5; row++) {
|
||||
ballPosition.z = pos.z - ((row - 1.0) / 2.0 * (BALL_SIZE + BALL_GAP) * SCALE);
|
||||
for (var spot = 0; spot < row; spot++) {
|
||||
|
@ -113,23 +116,26 @@ function makeBalls(pos) {
|
|||
color: colors[balls.length],
|
||||
gravity: { x: 0, y: GRAVITY, z: 0 },
|
||||
ignoreCollisions: false,
|
||||
damping: 0.40,
|
||||
damping: 0.50,
|
||||
collisionsWillMove: true }));
|
||||
ballPosition.z += (BALL_SIZE + BALL_GAP) * SCALE;
|
||||
}
|
||||
ballPosition.x += (BALL_GAP + Math.sqrt(3.0) / 2.0 * BALL_SIZE) * SCALE;
|
||||
}
|
||||
// Cue Ball
|
||||
ballPosition = { x: pos.x - (LENGTH / 4.0) * SCALE, y: pos.y + DROP_HEIGHT, z: pos.z };
|
||||
balls.push(Entities.addEntity(
|
||||
cuePosition = { x: pos.x - (LENGTH / 4.0) * SCALE, y: pos.y + HEIGHT / 2.0 + DROP_HEIGHT, z: pos.z };
|
||||
cueBall = Entities.addEntity(
|
||||
{ type: "Sphere",
|
||||
position: ballPosition,
|
||||
position: cuePosition,
|
||||
dimensions: { x: BALL_SIZE * SCALE, y: BALL_SIZE * SCALE, z: BALL_SIZE * SCALE },
|
||||
color: { red: 255, green: 255, blue: 255 },
|
||||
gravity: { x: 0, y: GRAVITY, z: 0 },
|
||||
angularVelocity: { x: 0, y: 0, z: 0 },
|
||||
velocity: {x: 0, y: 0, z: 0 },
|
||||
ignoreCollisions: false,
|
||||
damping: 0.40,
|
||||
collisionsWillMove: true }));
|
||||
damping: 0.50,
|
||||
collisionsWillMove: true });
|
||||
|
||||
}
|
||||
|
||||
function shootCue(velocity) {
|
||||
|
@ -140,19 +146,23 @@ function shootCue(velocity) {
|
|||
var velocity = Vec3.multiply(forwardVector, velocity);
|
||||
var BULLET_LIFETIME = 3.0;
|
||||
var BULLET_GRAVITY = 0.0;
|
||||
var SHOOTER_COLOR = { red: 255, green: 0, blue: 0 };
|
||||
var SHOOTER_SIZE = BALL_SIZE / 1.5 * SCALE;
|
||||
|
||||
bulletID = Entities.addEntity(
|
||||
{ type: "Sphere",
|
||||
position: cuePosition,
|
||||
dimensions: { x: BALL_SIZE * SCALE, y: BALL_SIZE * SCALE, z: BALL_SIZE * SCALE },
|
||||
color: { red: 255, green: 255, blue: 255 },
|
||||
dimensions: { x: SHOOTER_SIZE, y: SHOOTER_SIZE, z: SHOOTER_SIZE },
|
||||
color: SHOOTER_COLOR,
|
||||
velocity: velocity,
|
||||
lifetime: BULLET_LIFETIME,
|
||||
gravity: { x: 0, y: BULLET_GRAVITY, z: 0 },
|
||||
damping: 0.10,
|
||||
density: 1000,
|
||||
density: 8000,
|
||||
ignoreCollisions: false,
|
||||
collisionsWillMove: true
|
||||
});
|
||||
print("Shot, velocity = " + velocity);
|
||||
}
|
||||
|
||||
function keyReleaseEvent(event) {
|
||||
|
@ -185,9 +195,25 @@ function cleanup() {
|
|||
Entities.deleteEntity(balls[i]);
|
||||
}
|
||||
Overlays.deleteOverlay(reticle);
|
||||
Entities.deleteEntity(cueBall);
|
||||
}
|
||||
|
||||
var tableCenter = Vec3.sum(MyAvatar.position, Vec3.multiply(4.0, Quat.getFront(Camera.getOrientation())));
|
||||
function update(deltaTime) {
|
||||
if (!cueBall.isKnownID) {
|
||||
cueBall = Entities.identifyEntity(cueBall);
|
||||
} else {
|
||||
// Check if cue ball has fallen off table, re-drop if so
|
||||
var cueProperties = Entities.getEntityProperties(cueBall);
|
||||
if (cueProperties.position.y < tableCenter.y) {
|
||||
// Replace the cueball
|
||||
Entities.editEntity(cueBall, { position: cuePosition } );
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
tableCenter = Vec3.sum(MyAvatar.position, Vec3.multiply(4.0, Quat.getFront(Camera.getOrientation())));
|
||||
|
||||
makeTable(tableCenter);
|
||||
makeBalls(tableCenter);
|
||||
|
@ -195,3 +221,4 @@ makeBalls(tableCenter);
|
|||
Script.scriptEnding.connect(cleanup);
|
||||
Controller.keyPressEvent.connect(keyPressEvent);
|
||||
Controller.keyReleaseEvent.connect(keyReleaseEvent);
|
||||
Script.update.connect(update);
|
||||
|
|
|
@ -47,7 +47,6 @@ var yawFromMouse = 0;
|
|||
var pitchFromMouse = 0;
|
||||
var isMouseDown = false;
|
||||
|
||||
var BULLET_VELOCITY = 5.0;
|
||||
var MIN_THROWER_DELAY = 1000;
|
||||
var MAX_THROWER_DELAY = 1000;
|
||||
var LEFT_BUTTON_3 = 3;
|
||||
|
@ -98,7 +97,7 @@ var reticle = Overlays.addOverlay("image", {
|
|||
y: screenSize.y / 2 - 16,
|
||||
width: 32,
|
||||
height: 32,
|
||||
imageURL: HIFI_PUBLIC_BUCKET + "images/reticle.png",
|
||||
imageURL: HIFI_PUBLIC_BUCKET + "images/billiardsReticle.png",
|
||||
color: { red: 255, green: 255, blue: 255},
|
||||
alpha: 1
|
||||
});
|
||||
|
@ -113,6 +112,25 @@ var offButton = Overlays.addOverlay("image", {
|
|||
alpha: 1
|
||||
});
|
||||
|
||||
var platformButton = Overlays.addOverlay("image", {
|
||||
x: screenSize.x - 48,
|
||||
y: 130,
|
||||
width: 32,
|
||||
height: 32,
|
||||
imageURL: HIFI_PUBLIC_BUCKET + "images/city.png",
|
||||
color: { red: 255, green: 255, blue: 255},
|
||||
alpha: 1
|
||||
});
|
||||
var gridButton = Overlays.addOverlay("image", {
|
||||
x: screenSize.x - 48,
|
||||
y: 164,
|
||||
width: 32,
|
||||
height: 32,
|
||||
imageURL: HIFI_PUBLIC_BUCKET + "images/blocks.png",
|
||||
color: { red: 255, green: 255, blue: 255},
|
||||
alpha: 1
|
||||
});
|
||||
|
||||
if (showScore) {
|
||||
var text = Overlays.addOverlay("text", {
|
||||
x: screenSize.x / 2 - 100,
|
||||
|
@ -127,24 +145,30 @@ if (showScore) {
|
|||
});
|
||||
}
|
||||
|
||||
function printVector(string, vector) {
|
||||
print(string + " " + vector.x + ", " + vector.y + ", " + vector.z);
|
||||
}
|
||||
var BULLET_VELOCITY = 10.0;
|
||||
|
||||
function shootBullet(position, velocity) {
|
||||
var BULLET_SIZE = 0.07;
|
||||
function shootBullet(position, velocity, grenade) {
|
||||
var BULLET_SIZE = 0.10;
|
||||
var BULLET_LIFETIME = 10.0;
|
||||
var BULLET_GRAVITY = 0.0;
|
||||
var BULLET_GRAVITY = -0.25;
|
||||
var GRENADE_VELOCITY = 15.0;
|
||||
var GRENADE_SIZE = 0.35;
|
||||
var GRENADE_GRAVITY = -9.8;
|
||||
|
||||
var bVelocity = grenade ? Vec3.multiply(GRENADE_VELOCITY, Vec3.normalize(velocity)) : velocity;
|
||||
var bSize = grenade ? GRENADE_SIZE : BULLET_SIZE;
|
||||
var bGravity = grenade ? GRENADE_GRAVITY : BULLET_GRAVITY;
|
||||
|
||||
bulletID = Entities.addEntity(
|
||||
{ type: "Sphere",
|
||||
position: position,
|
||||
dimensions: { x: BULLET_SIZE, y: BULLET_SIZE, z: BULLET_SIZE },
|
||||
dimensions: { x: bSize, y: bSize, z: bSize },
|
||||
color: { red: 255, green: 0, blue: 0 },
|
||||
velocity: velocity,
|
||||
velocity: bVelocity,
|
||||
lifetime: BULLET_LIFETIME,
|
||||
gravity: { x: 0, y: BULLET_GRAVITY, z: 0 },
|
||||
gravity: { x: 0, y: bGravity, z: 0 },
|
||||
damping: 0.01,
|
||||
density: 5000,
|
||||
density: 8000,
|
||||
ignoreCollisions: false,
|
||||
collisionsWillMove: true
|
||||
});
|
||||
|
@ -158,13 +182,15 @@ function shootBullet(position, velocity) {
|
|||
}
|
||||
|
||||
// Kickback the arm
|
||||
if (elbowKickAngle > 0.0) {
|
||||
MyAvatar.setJointData("LeftForeArm", rotationBeforeKickback);
|
||||
}
|
||||
rotationBeforeKickback = MyAvatar.getJointRotation("LeftForeArm");
|
||||
var armRotation = MyAvatar.getJointRotation("LeftForeArm");
|
||||
armRotation = Quat.multiply(armRotation, Quat.fromPitchYawRollDegrees(0.0, 0.0, KICKBACK_ANGLE));
|
||||
MyAvatar.setJointData("LeftForeArm", armRotation);
|
||||
elbowKickAngle = KICKBACK_ANGLE;
|
||||
}
|
||||
|
||||
function shootTarget() {
|
||||
var TARGET_SIZE = 0.50;
|
||||
var TARGET_GRAVITY = 0.0;
|
||||
|
@ -174,7 +200,7 @@ function shootTarget() {
|
|||
var DISTANCE_TO_LAUNCH_FROM = 5.0;
|
||||
var ANGLE_RANGE_FOR_LAUNCH = 20.0;
|
||||
var camera = Camera.getPosition();
|
||||
//printVector("camera", camera);
|
||||
|
||||
var targetDirection = Quat.angleAxis(getRandomFloat(-ANGLE_RANGE_FOR_LAUNCH, ANGLE_RANGE_FOR_LAUNCH), { x:0, y:1, z:0 });
|
||||
targetDirection = Quat.multiply(Camera.getOrientation(), targetDirection);
|
||||
var forwardVector = Quat.getFront(targetDirection);
|
||||
|
@ -205,6 +231,78 @@ function shootTarget() {
|
|||
Audio.playSound(targetLaunchSound, audioOptions);
|
||||
}
|
||||
|
||||
function makeGrid(type, scale, size) {
|
||||
var separation = scale * 2;
|
||||
var pos = Vec3.sum(Camera.getPosition(), Vec3.multiply(10.0 * scale * separation, Quat.getFront(Camera.getOrientation())));
|
||||
var x, y, z;
|
||||
var GRID_LIFE = 60.0;
|
||||
var dimensions;
|
||||
|
||||
for (x = 0; x < size; x++) {
|
||||
for (y = 0; y < size; y++) {
|
||||
for (z = 0; z < size; z++) {
|
||||
|
||||
dimensions = { x: separation/2.0 * (0.5 + Math.random()), y: separation/2.0 * (0.5 + Math.random()), z: separation/2.0 * (0.5 + Math.random()) / 4.0 };
|
||||
|
||||
Entities.addEntity(
|
||||
{ type: type,
|
||||
position: { x: pos.x + x * separation, y: pos.y + y * separation, z: pos.z + z * separation },
|
||||
dimensions: dimensions,
|
||||
color: { red: Math.random() * 255, green: Math.random() * 255, blue: Math.random() * 255 },
|
||||
velocity: { x: 0, y: 0, z: 0 },
|
||||
gravity: { x: 0, y: 0, z: 0 },
|
||||
lifetime: GRID_LIFE,
|
||||
rotation: Camera.getOrientation(),
|
||||
damping: 0.1,
|
||||
density: 100.0,
|
||||
collisionsWillMove: true });
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
function makePlatform(gravity, scale, size) {
|
||||
var separation = scale * 2;
|
||||
var pos = Vec3.sum(Camera.getPosition(), Vec3.multiply(10.0 * scale * separation, Quat.getFront(Camera.getOrientation())));
|
||||
pos.y -= separation * size;
|
||||
var x, y, z;
|
||||
var TARGET_LIFE = 60.0;
|
||||
var INITIAL_GAP = 0.5;
|
||||
var dimensions;
|
||||
|
||||
for (x = 0; x < size; x++) {
|
||||
for (y = 0; y < size; y++) {
|
||||
for (z = 0; z < size; z++) {
|
||||
|
||||
dimensions = { x: separation/2.0, y: separation, z: separation/2.0 };
|
||||
|
||||
Entities.addEntity(
|
||||
{ type: "Box",
|
||||
position: { x: pos.x - (separation * size / 2.0) + x * separation,
|
||||
y: pos.y + y * (separation + INITIAL_GAP),
|
||||
z: pos.z - (separation * size / 2.0) + z * separation },
|
||||
dimensions: dimensions,
|
||||
color: { red: Math.random() * 255, green: Math.random() * 255, blue: Math.random() * 255 },
|
||||
velocity: { x: 0, y: 0, z: 0 },
|
||||
gravity: { x: 0, y: gravity, z: 0 },
|
||||
lifetime: TARGET_LIFE,
|
||||
damping: 0.1,
|
||||
density: 100.0,
|
||||
collisionsWillMove: true });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Make a floor for this stuff to fall onto
|
||||
Entities.addEntity({
|
||||
type: "Box",
|
||||
position: { x: pos.x, y: pos.y - separation / 2.0, z: pos.z },
|
||||
dimensions: { x: 2.0 * separation * size, y: separation / 2.0, z: 2.0 * separation * size },
|
||||
color: { red: 128, green: 128, blue: 128 },
|
||||
lifetime: TARGET_LIFE
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
function entityCollisionWithEntity(entity1, entity2, collision) {
|
||||
|
||||
if (((entity1.id == bulletID.id) || (entity1.id == targetID.id)) &&
|
||||
|
@ -233,7 +331,9 @@ function keyPressEvent(event) {
|
|||
var time = MIN_THROWER_DELAY + Math.random() * MAX_THROWER_DELAY;
|
||||
Script.setTimeout(shootTarget, time);
|
||||
} else if ((event.text == ".") || (event.text == "SPACE")) {
|
||||
shootFromMouse();
|
||||
shootFromMouse(false);
|
||||
} else if (event.text == ",") {
|
||||
shootFromMouse(true);
|
||||
} else if (event.text == "r") {
|
||||
playLoadSound();
|
||||
} else if (event.text == "s") {
|
||||
|
@ -241,7 +341,6 @@ function keyPressEvent(event) {
|
|||
Quat.print("arm = ", MyAvatar.getJointRotation("LeftArm"));
|
||||
Quat.print("forearm = ", MyAvatar.getJointRotation("LeftForeArm"));
|
||||
Quat.print("hand = ", MyAvatar.getJointRotation("LeftHand"));
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -287,18 +386,7 @@ function update(deltaTime) {
|
|||
if (targetID && !targetID.isKnownID) {
|
||||
targetID = Entities.identifyEntity(targetID);
|
||||
}
|
||||
// Check for mouseLook movement, update rotation
|
||||
// rotate body yaw for yaw received from mouse
|
||||
var newOrientation = Quat.multiply(MyAvatar.orientation, Quat.fromVec3Radians( { x: 0, y: yawFromMouse, z: 0 } ));
|
||||
//MyAvatar.orientation = newOrientation;
|
||||
yawFromMouse = 0;
|
||||
|
||||
// apply pitch from mouse
|
||||
var newPitch = MyAvatar.headPitch + pitchFromMouse;
|
||||
//MyAvatar.headPitch = newPitch;
|
||||
pitchFromMouse = 0;
|
||||
|
||||
|
||||
if (activeControllers == 0) {
|
||||
if (Controller.getNumberOfSpatialControls() > 0) {
|
||||
activeControllers = Controller.getNumberOfSpatialControls();
|
||||
|
@ -372,19 +460,19 @@ function update(deltaTime) {
|
|||
|
||||
var velocity = Vec3.multiply(BULLET_VELOCITY, Vec3.normalize(palmToFingerTipVector));
|
||||
|
||||
shootBullet(position, velocity);
|
||||
shootBullet(position, velocity, false);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function shootFromMouse() {
|
||||
function shootFromMouse(grenade) {
|
||||
var DISTANCE_FROM_CAMERA = 1.0;
|
||||
var camera = Camera.getPosition();
|
||||
var forwardVector = Quat.getFront(Camera.getOrientation());
|
||||
var newPosition = Vec3.sum(camera, Vec3.multiply(forwardVector, DISTANCE_FROM_CAMERA));
|
||||
var velocity = Vec3.multiply(forwardVector, BULLET_VELOCITY);
|
||||
shootBullet(newPosition, velocity);
|
||||
shootBullet(newPosition, velocity, grenade);
|
||||
}
|
||||
|
||||
function mouseReleaseEvent(event) {
|
||||
|
@ -392,21 +480,23 @@ function mouseReleaseEvent(event) {
|
|||
isMouseDown = false;
|
||||
}
|
||||
|
||||
function mouseMoveEvent(event) {
|
||||
if (isMouseDown) {
|
||||
var MOUSE_YAW_SCALE = -0.25;
|
||||
var MOUSE_PITCH_SCALE = -12.5;
|
||||
var FIXED_MOUSE_TIMESTEP = 0.016;
|
||||
yawFromMouse += ((event.x - lastX) * MOUSE_YAW_SCALE * FIXED_MOUSE_TIMESTEP);
|
||||
pitchFromMouse += ((event.y - lastY) * MOUSE_PITCH_SCALE * FIXED_MOUSE_TIMESTEP);
|
||||
lastX = event.x;
|
||||
lastY = event.y;
|
||||
}
|
||||
function mousePressEvent(event) {
|
||||
var clickedText = false;
|
||||
var clickedOverlay = Overlays.getOverlayAtPoint({x: event.x, y: event.y});
|
||||
if (clickedOverlay == offButton) {
|
||||
Script.stop();
|
||||
} else if (clickedOverlay == platformButton) {
|
||||
makePlatform(-9.8, 1.0, 5);
|
||||
} else if (clickedOverlay == gridButton) {
|
||||
makeGrid("Box", 1.0, 3);
|
||||
}
|
||||
}
|
||||
|
||||
function scriptEnding() {
|
||||
Overlays.deleteOverlay(reticle);
|
||||
Overlays.deleteOverlay(offButton);
|
||||
Overlays.deleteOverlay(platformButton);
|
||||
Overlays.deleteOverlay(gridButton);
|
||||
Overlays.deleteOverlay(pointer[0]);
|
||||
Overlays.deleteOverlay(pointer[1]);
|
||||
Overlays.deleteOverlay(text);
|
||||
|
@ -418,7 +508,7 @@ Entities.entityCollisionWithEntity.connect(entityCollisionWithEntity);
|
|||
Script.scriptEnding.connect(scriptEnding);
|
||||
Script.update.connect(update);
|
||||
Controller.mouseReleaseEvent.connect(mouseReleaseEvent);
|
||||
Controller.mouseMoveEvent.connect(mouseMoveEvent);
|
||||
Controller.mousePressEvent.connect(mousePressEvent);
|
||||
Controller.keyPressEvent.connect(keyPressEvent);
|
||||
|
||||
|
||||
|
|
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);
|
|
@ -8,7 +8,7 @@ body {
|
|||
background-color: rgb(76, 76, 76);
|
||||
color: rgb(204, 204, 204);
|
||||
font-family: Arial;
|
||||
font-size: 11px;
|
||||
font-size: 8.25pt;
|
||||
|
||||
-webkit-touch-callout: none;
|
||||
-webkit-user-select: none;
|
||||
|
@ -31,25 +31,25 @@ body {
|
|||
|
||||
.color-box {
|
||||
display: inline-block;
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
border: 1px solid black;
|
||||
margin: 2px;
|
||||
width: 15pt;
|
||||
height: 15pt;
|
||||
border: 0.75pt solid black;
|
||||
margin: 1.5pt;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.color-box.highlight {
|
||||
width: 18px;
|
||||
height: 18px;
|
||||
border: 2px solid black;
|
||||
width: 13.5pt;
|
||||
height: 13.5pt;
|
||||
border: 1.5pt solid black;
|
||||
}
|
||||
|
||||
.section-header {
|
||||
background: #AAA;
|
||||
border-bottom: 1px solid #CCC;
|
||||
border-bottom: 0.75pt solid #CCC;
|
||||
background-color: #333333;
|
||||
color: #999;
|
||||
padding: 4px;
|
||||
padding: 3pt;
|
||||
}
|
||||
|
||||
.section-header label {
|
||||
|
@ -61,7 +61,7 @@ body {
|
|||
.property-section {
|
||||
display: block;
|
||||
margin: 10 10;
|
||||
height: 30px;
|
||||
height: 22.5pt;
|
||||
}
|
||||
|
||||
.property-section label {
|
||||
|
@ -73,7 +73,7 @@ body {
|
|||
}
|
||||
|
||||
.grid-section {
|
||||
border-top: 1px solid #DDD;
|
||||
border-top: 0.75pt solid #DDD;
|
||||
background-color: #efefef;
|
||||
}
|
||||
|
||||
|
@ -81,8 +81,8 @@ input[type=button] {
|
|||
cursor: pointer;
|
||||
background-color: #608e96;
|
||||
border-color: #608e96;
|
||||
border-radius: 5px;
|
||||
padding: 5px 10px;
|
||||
border-radius: 3.75pt;
|
||||
padding: 3.75pt 7.5pt;
|
||||
border: 0;
|
||||
color: #fff;
|
||||
font-weight: bold;
|
||||
|
@ -90,8 +90,8 @@ input[type=button] {
|
|||
|
||||
textarea, input {
|
||||
margin: 0;
|
||||
padding: 2px;
|
||||
border: 1px solid #999;
|
||||
padding: 1.5pt;
|
||||
border: 0.75pt solid #999;
|
||||
background-color: #eee;
|
||||
}
|
||||
|
||||
|
@ -112,13 +112,13 @@ input.coord {
|
|||
table#entity-table {
|
||||
border-collapse: collapse;
|
||||
font-family: Sans-Serif;
|
||||
font-size: 10px;
|
||||
font-size: 7.5pt;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
#entity-table tr {
|
||||
cursor: pointer;
|
||||
border-bottom: 1px solid rgb(63, 63, 63)
|
||||
border-bottom: 0.75pt solid rgb(63, 63, 63)
|
||||
}
|
||||
|
||||
#entity-table tr.selected {
|
||||
|
@ -128,15 +128,15 @@ table#entity-table {
|
|||
#entity-table th {
|
||||
background-color: #333;
|
||||
color: #fff;
|
||||
border: 0px black solid;
|
||||
border: 0pt black solid;
|
||||
text-align: left;
|
||||
word-wrap: nowrap;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
#entity-table td {
|
||||
font-size: 11px;
|
||||
border: 0px black solid;
|
||||
font-size: 8.25pt;
|
||||
border: 0pt black solid;
|
||||
word-wrap: nowrap;
|
||||
white-space: nowrap;
|
||||
text-overflow: ellipsis;
|
||||
|
@ -148,21 +148,21 @@ table#entity-table {
|
|||
}
|
||||
|
||||
th#entity-type {
|
||||
width: 60px;
|
||||
width: 33.75pt;
|
||||
}
|
||||
|
||||
|
||||
|
||||
div.input-area {
|
||||
display: inline-block;
|
||||
font-size: 10px;
|
||||
font-size: 7.5pt;
|
||||
}
|
||||
|
||||
input {
|
||||
}
|
||||
|
||||
#type {
|
||||
font-size: 14px;
|
||||
font-size: 10.5pt;
|
||||
}
|
||||
|
||||
#type label {
|
||||
|
@ -173,22 +173,22 @@ input {
|
|||
background-color: rgb(102, 102, 102);
|
||||
color: rgb(204, 204, 204);
|
||||
border: none;
|
||||
font-size: 10px;
|
||||
font-size: 7.5pt;
|
||||
}
|
||||
|
||||
#properties-list input[type=button] {
|
||||
cursor: pointer;
|
||||
background-color: rgb(51, 102, 102);
|
||||
border-color: #608e96;
|
||||
border-radius: 5px;
|
||||
padding: 5px 10px;
|
||||
border-radius: 3.75pt;
|
||||
padding: 3.75pt 7.5pt;
|
||||
border: 0;
|
||||
color: rgb(204, 204, 204);
|
||||
}
|
||||
|
||||
#properties-list .property {
|
||||
padding: 8px 8px;
|
||||
border-top: 1px solid rgb(63, 63, 63);
|
||||
padding: 6pt 6pt;
|
||||
border-top: 0.75pt solid rgb(63, 63, 63);
|
||||
min-height: 1em;
|
||||
}
|
||||
|
||||
|
@ -203,11 +203,11 @@ table#properties-list {
|
|||
}
|
||||
|
||||
#properties-list > div {
|
||||
margin: 4px 0;
|
||||
margin: 3pt 0;
|
||||
}
|
||||
|
||||
#properties-list {
|
||||
border-bottom: 1px solid #e5e5e5;
|
||||
border-bottom: 0.75pt solid #e5e5e5;
|
||||
}
|
||||
|
||||
#properties-list .label {
|
||||
|
@ -221,11 +221,11 @@ table#properties-list {
|
|||
}
|
||||
|
||||
#properties-list .value > div{
|
||||
padding: 4px 0;
|
||||
padding: 3pt 0;
|
||||
}
|
||||
|
||||
col#col-label {
|
||||
width: 130px;
|
||||
width: 97.5pt;
|
||||
}
|
||||
|
||||
div.outer {
|
||||
|
|
|
@ -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
|
|
@ -43,5 +43,5 @@ void main(void) {
|
|||
gl_FrontColor = vec4(1.0, 1.0, 1.0, step(height, 0.0));
|
||||
|
||||
// pass along the scaled/offset texture coordinates
|
||||
gl_TexCoord[0] = vec4((heightCoord - heightScale.st) * colorScale, 0.0, 1.0);
|
||||
gl_TexCoord[0] = vec4((heightCoord - vec2(0.5, 0.5)) * colorScale + vec2(0.5, 0.5), 0.0, 1.0);
|
||||
}
|
||||
|
|
|
@ -58,7 +58,7 @@ void main(void) {
|
|||
gl_TexCoord[3] = textureSpacePosition * vec4(splatTextureScalesS[3], splatTextureScalesT[3], 0.0, 1.0);
|
||||
|
||||
// compute the alpha values for each texture
|
||||
float value = texture2D(textureMap, (gl_MultiTexCoord0.st - heightScale) * textureScale).r;
|
||||
float value = texture2D(textureMap, (gl_MultiTexCoord0.st - vec2(0.5, 0.5)) * textureScale + vec2(0.5, 0.5)).r;
|
||||
vec4 valueVector = vec4(value, value, value, value);
|
||||
alphaValues = step(textureValueMinima, valueVector) * step(valueVector, textureValueMaxima);
|
||||
}
|
||||
|
|
|
@ -193,7 +193,6 @@ Application::Application(int& argc, char** argv, QElapsedTimer &startup_time) :
|
|||
_justStarted(true),
|
||||
_physicsEngine(glm::vec3(0.0f)),
|
||||
_entities(true, this, this),
|
||||
_entityCollisionSystem(),
|
||||
_entityClipboardRenderer(false, this, this),
|
||||
_entityClipboard(),
|
||||
_viewFrustum(),
|
||||
|
@ -1690,17 +1689,16 @@ void Application::init() {
|
|||
_entities.init();
|
||||
_entities.setViewFrustum(getViewFrustum());
|
||||
|
||||
EntityTree* entityTree = _entities.getTree();
|
||||
|
||||
_entityCollisionSystem.init(&_entityEditSender, entityTree, &_avatarManager);
|
||||
|
||||
entityTree->setSimulation(&_entityCollisionSystem);
|
||||
EntityTree* tree = _entities.getTree();
|
||||
_physicsEngine.setEntityTree(tree);
|
||||
tree->setSimulation(&_physicsEngine);
|
||||
_physicsEngine.init(&_entityEditSender);
|
||||
|
||||
connect(&_entityCollisionSystem, &EntityCollisionSystem::entityCollisionWithEntity,
|
||||
connect(&_physicsEngine, &EntitySimulation::entityCollisionWithEntity,
|
||||
ScriptEngine::getEntityScriptingInterface(), &EntityScriptingInterface::entityCollisionWithEntity);
|
||||
|
||||
// connect the _entityCollisionSystem to our EntityTreeRenderer since that's what handles running entity scripts
|
||||
connect(&_entityCollisionSystem, &EntityCollisionSystem::entityCollisionWithEntity,
|
||||
connect(&_physicsEngine, &EntitySimulation::entityCollisionWithEntity,
|
||||
&_entities, &EntityTreeRenderer::entityCollisionWithEntity);
|
||||
|
||||
// connect the _entities (EntityTreeRenderer) to our script engine's EntityScriptingInterface for firing
|
||||
|
@ -1724,10 +1722,6 @@ void Application::init() {
|
|||
// save settings when avatar changes
|
||||
connect(_myAvatar, &MyAvatar::transformChanged, this, &Application::bumpSettings);
|
||||
|
||||
EntityTree* tree = _entities.getTree();
|
||||
_physicsEngine.setEntityTree(tree);
|
||||
tree->setSimulation(&_physicsEngine);
|
||||
_physicsEngine.init(&_entityEditSender);
|
||||
// make sure our texture cache knows about window size changes
|
||||
DependencyManager::get<TextureCache>()->associateWithWidget(glCanvas.data());
|
||||
|
||||
|
@ -2048,9 +2042,6 @@ void Application::update(float deltaTime) {
|
|||
// NOTE: the _entities.update() call below will wait for lock
|
||||
// and will simulate entity motion (the EntityTree has been given an EntitySimulation).
|
||||
_entities.update(); // update the models...
|
||||
// The _entityCollisionSystem.updateCollisions() call below merely tries for lock,
|
||||
// and on failure it skips collision detection.
|
||||
_entityCollisionSystem.updateCollisions(); // collide the entities...
|
||||
}
|
||||
|
||||
{
|
||||
|
|
|
@ -25,7 +25,6 @@
|
|||
|
||||
#include <AbstractScriptingServicesInterface.h>
|
||||
#include <AbstractViewStateInterface.h>
|
||||
#include <EntityCollisionSystem.h>
|
||||
#include <EntityEditPacketSender.h>
|
||||
#include <EntityTreeRenderer.h>
|
||||
#include <GeometryCache.h>
|
||||
|
@ -466,7 +465,6 @@ private:
|
|||
PhysicsEngine _physicsEngine;
|
||||
|
||||
EntityTreeRenderer _entities;
|
||||
EntityCollisionSystem _entityCollisionSystem;
|
||||
EntityTreeRenderer _entityClipboardRenderer;
|
||||
EntityTree _entityClipboard;
|
||||
|
||||
|
|
|
@ -1425,8 +1425,30 @@ public:
|
|||
char material;
|
||||
|
||||
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) {
|
||||
point = glm::mix(first.point, second.point, t);
|
||||
normal = glm::normalize(glm::mix(first.normal, second.normal, t));
|
||||
color = qRgb(glm::mix(qRed(first.color), qRed(second.color), t), glm::mix(qGreen(first.color), qGreen(second.color), t),
|
||||
glm::mix(qBlue(first.color), qBlue(second.color), t));
|
||||
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 {
|
||||
|
@ -1480,7 +1502,6 @@ public:
|
|||
void swap(IndexVector& other) { QVector<NormalIndex>::swap(other); qSwap(position, other.position); }
|
||||
|
||||
const NormalIndex& get(int y) const;
|
||||
const NormalIndex& getClosest(int y) const;
|
||||
};
|
||||
|
||||
const NormalIndex& IndexVector::get(int y) const {
|
||||
|
@ -1489,54 +1510,82 @@ const NormalIndex& IndexVector::get(int y) const {
|
|||
return (relative >= 0 && relative < size()) ? at(relative) : invalidIndex;
|
||||
}
|
||||
|
||||
const NormalIndex& IndexVector::getClosest(int y) const {
|
||||
static NormalIndex invalidIndex = { { -1, -1, -1, -1 } };
|
||||
int relative = y - position;
|
||||
if (relative < 0 || relative >= size()) {
|
||||
return 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;
|
||||
}
|
||||
const NormalIndex& first = at(relative);
|
||||
if (first.isValid()) {
|
||||
return first;
|
||||
}
|
||||
for (int distance = 1; relative - distance >= 0 || relative + distance < size(); distance++) {
|
||||
int previous = relative - distance;
|
||||
if (previous >= 0) {
|
||||
const NormalIndex& previousIndex = at(previous);
|
||||
if (previousIndex.isValid()) {
|
||||
return previousIndex;
|
||||
}
|
||||
}
|
||||
int next = relative + distance;
|
||||
if (next < size()) {
|
||||
const NormalIndex& nextIndex = at(next);
|
||||
if (nextIndex.isValid()) {
|
||||
return nextIndex;
|
||||
}
|
||||
}
|
||||
}
|
||||
return invalidIndex;
|
||||
return glm::cross(vertices.at(i2.indices[0]).vertex - v0, vertices.at(i3.indices[0]).vertex - v0);
|
||||
}
|
||||
|
||||
static inline void appendIndices(QVector<int>& indices, QMultiHash<VoxelCoord, int>& quadIndices,
|
||||
const QVector<VoxelPoint>& vertices, float step, int i0, int i1, int i2, int i3) {
|
||||
int newIndices[] = { i0, i1, i2, i3 };
|
||||
glm::vec3 minima(FLT_MAX, FLT_MAX, FLT_MAX), maxima(-FLT_MAX, -FLT_MAX, -FLT_MAX);
|
||||
int indexIndex = indices.size();
|
||||
for (unsigned int i = 0; i < sizeof(newIndices) / sizeof(newIndices[0]); i++) {
|
||||
int index = newIndices[i];
|
||||
indices.append(index);
|
||||
const glm::vec3& vertex = vertices.at(index).vertex;
|
||||
minima = glm::min(vertex, minima);
|
||||
maxima = glm::max(vertex, maxima);
|
||||
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);
|
||||
}
|
||||
for (int z = (int)minima.z, endZ = (int)glm::ceil(maxima.z); z < endZ; z++) {
|
||||
for (int y = (int)minima.x, endY = (int)glm::ceil(maxima.y); y < endY; y++) {
|
||||
for (int x = (int)minima.x, endX = (int)glm::ceil(maxima.x); x < endX; x++) {
|
||||
quadIndices.insert(qRgb(x, y, z), indexIndex);
|
||||
}
|
||||
}
|
||||
|
||||
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,
|
||||
|
@ -1617,7 +1666,7 @@ void HeightfieldNodeRenderer::render(const HeightfieldNodePointer& node, const g
|
|||
|
||||
glGenTextures(1, &_colorTextureID);
|
||||
glBindTexture(GL_TEXTURE_2D, _colorTextureID);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
|
||||
if (node->getColor()) {
|
||||
|
@ -1625,6 +1674,7 @@ void HeightfieldNodeRenderer::render(const HeightfieldNodePointer& node, const g
|
|||
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB8, node->getColor()->getWidth(),
|
||||
contents.size() / (node->getColor()->getWidth() * DataBlock::COLOR_BYTES),
|
||||
0, GL_RGB, GL_UNSIGNED_BYTE, contents.constData());
|
||||
glGenerateMipmap(GL_TEXTURE_2D);
|
||||
|
||||
} else {
|
||||
const quint8 WHITE_COLOR[] = { 255, 255, 255 };
|
||||
|
@ -1705,7 +1755,7 @@ void HeightfieldNodeRenderer::render(const HeightfieldNodePointer& node, const g
|
|||
}
|
||||
|
||||
const int EDGES_PER_CUBE = 12;
|
||||
EdgeCrossing crossings[EDGES_PER_CUBE];
|
||||
EdgeCrossing crossings[EDGES_PER_CUBE * 2];
|
||||
|
||||
// as we scan down the cube generating vertices between grid points, we remember the indices of the last
|
||||
// (element, line, section--x, y, z) so that we can connect generated vertices as quads
|
||||
|
@ -1736,13 +1786,6 @@ void HeightfieldNodeRenderer::render(const HeightfieldNodePointer& node, const g
|
|||
lineSrc[stackWidth].getExtents(minimumY, maximumY);
|
||||
}
|
||||
if (maximumY >= minimumY) {
|
||||
int position = minimumY;
|
||||
int count = maximumY - minimumY + 1;
|
||||
NormalIndex lastIndexY = { { -1, -1, -1, -1 } };
|
||||
indicesX.position = position;
|
||||
indicesX.resize(count);
|
||||
indicesZ[x].position = position;
|
||||
indicesZ[x].resize(count);
|
||||
float heightfieldHeight = *heightLineSrc * voxelScale;
|
||||
float nextHeightfieldHeightX = heightLineSrc[1] * voxelScale;
|
||||
float nextHeightfieldHeightZ = heightLineSrc[width] * voxelScale;
|
||||
|
@ -1753,6 +1796,7 @@ 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 NEXT_CORNERS[] = { 1, 3, 0, 2 };
|
||||
int corners = NO_CORNERS;
|
||||
if (heightfieldHeight != 0.0f) {
|
||||
corners |= UPPER_LEFT_CORNER;
|
||||
|
@ -1767,37 +1811,38 @@ void HeightfieldNodeRenderer::render(const HeightfieldNodePointer& node, const g
|
|||
corners |= LOWER_RIGHT_CORNER;
|
||||
}
|
||||
bool stitchable = x != 0 && z != 0 && !(corners == NO_CORNERS || corners == ALL_CORNERS);
|
||||
VoxelPoint cornerPoints[4];
|
||||
EdgeCrossing cornerCrossings[CORNER_COUNT];
|
||||
int clampedX = qMax(x - 1, 0), clampedZ = qMax(z - 1, 0);
|
||||
int cornerMinimumY = INT_MAX, cornerMaximumY = -1;
|
||||
if (stitchable) {
|
||||
for (unsigned int i = 0; i < sizeof(cornerPoints) / sizeof(cornerPoints[0]); i++) {
|
||||
for (int i = 0; i < CORNER_COUNT; i++) {
|
||||
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;
|
||||
VoxelPoint& point = cornerPoints[i];
|
||||
int clampedOffsetX = clampedX + offsetX, clampedOffsetZ = clampedZ + offsetZ;
|
||||
point.vertex = glm::vec3(clampedOffsetX, *height * voxelScale, clampedOffsetZ) * step;
|
||||
float heightValue = *height * voxelScale;
|
||||
int y = (int)heightValue;
|
||||
cornerMinimumY = qMin(cornerMinimumY, y);
|
||||
cornerMaximumY = qMax(cornerMaximumY, y);
|
||||
EdgeCrossing& crossing = cornerCrossings[i];
|
||||
crossing.point = glm::vec3(offsetX, heightValue, offsetZ);
|
||||
int left = height[-1];
|
||||
int right = height[1];
|
||||
int down = height[-width];
|
||||
int up = height[width];
|
||||
glm::vec3 normal = glm::normalize(glm::vec3((left == 0 || right == 0) ? 0.0f : left - right,
|
||||
crossing.normal = glm::normalize(glm::vec3((left == 0 || right == 0) ? 0.0f : left - right,
|
||||
2.0f / voxelScale, (up == 0 || down == 0) ? 0.0f : down - up));
|
||||
point.normal[0] = normal.x * numeric_limits<qint8>::max();
|
||||
point.normal[1] = normal.y * numeric_limits<qint8>::max();
|
||||
point.normal[2] = normal.z * numeric_limits<qint8>::max();
|
||||
int clampedOffsetX = clampedX + offsetX, clampedOffsetZ = clampedZ + offsetZ;
|
||||
if (colorSrc) {
|
||||
const uchar* color = colorSrc + ((int)(clampedOffsetZ * colorStepZ) * colorWidth +
|
||||
(int)(clampedOffsetX * colorStepX)) * DataBlock::COLOR_BYTES;
|
||||
point.color[0] = color[0];
|
||||
point.color[1] = color[1];
|
||||
point.color[2] = color[2];
|
||||
|
||||
crossing.color = qRgb(color[0], color[1], color[2]);
|
||||
|
||||
} else {
|
||||
point.color[0] = point.color[1] = point.color[2] = numeric_limits<quint8>::max();
|
||||
crossing.color = qRgb(numeric_limits<quint8>::max(), numeric_limits<quint8>::max(),
|
||||
numeric_limits<quint8>::max());
|
||||
}
|
||||
int material = 0;
|
||||
if (materialSrc) {
|
||||
|
@ -1812,14 +1857,29 @@ void HeightfieldNodeRenderer::render(const HeightfieldNodePointer& node, const g
|
|||
material = mapping;
|
||||
}
|
||||
}
|
||||
point.materials[0] = material;
|
||||
point.materials[1] = point.materials[2] = point.materials[3] = 0;
|
||||
point.materialWeights[0] = numeric_limits<quint8>::max();
|
||||
point.materialWeights[1] = point.materialWeights[2] = point.materialWeights[3] = 0;
|
||||
crossing.material = material;
|
||||
}
|
||||
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;
|
||||
NormalIndex lastIndexY = { { -1, -1, -1, -1 } };
|
||||
indicesX.position = position;
|
||||
indicesX.resize(count);
|
||||
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) {
|
||||
|
@ -1875,91 +1935,174 @@ void HeightfieldNodeRenderer::render(const HeightfieldNodePointer& node, const g
|
|||
if (alphaTotal == 0 || alphaTotal == possibleTotal) {
|
||||
continue; // no corners set/all corners set
|
||||
}
|
||||
// we first look for crossings with the heightfield corner vertices; these take priority
|
||||
int crossingCount = 0;
|
||||
if (y >= cornerMinimumY && y <= cornerMaximumY) {
|
||||
// first look for set corners, which override any interpolated values
|
||||
int crossedCorners = NO_CORNERS;
|
||||
for (int i = 0; i < CORNER_COUNT; i++) {
|
||||
if (!(corners & (1 << i))) {
|
||||
continue;
|
||||
}
|
||||
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_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_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 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_LEFT_CORNER | UPPER_RIGHT_CORNER | LOWER_RIGHT_CORNER:
|
||||
crossings[crossingCount++] = cornerCrossings[3];
|
||||
crossings[crossingCount - 1].point.y -= y;
|
||||
break;
|
||||
|
||||
case NO_CORNERS:
|
||||
for (int i = 0; i < CORNER_COUNT; i++) {
|
||||
if (!(corners & (1 << i))) {
|
||||
continue;
|
||||
}
|
||||
int nextIndex = NEXT_CORNERS[i];
|
||||
if (!(corners & (1 << nextIndex))) {
|
||||
continue;
|
||||
}
|
||||
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 - cornerCrossing.point.y) / divisor;
|
||||
float t2 = (y + 1 - cornerCrossing.point.y) / divisor;
|
||||
if (t1 >= 0.0f && t1 <= 1.0f) {
|
||||
crossings[crossingCount++].mix(cornerCrossing, nextCornerCrossing, t1);
|
||||
crossings[crossingCount - 1].point.y -= y;
|
||||
}
|
||||
if (t2 >= 0.0f && t2 <= 1.0f) {
|
||||
crossings[crossingCount++].mix(cornerCrossing, nextCornerCrossing, t2);
|
||||
crossings[crossingCount - 1].point.y -= y;
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// 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
|
||||
int crossingCount = 0;
|
||||
const StackArray::Entry& nextEntryY = lineSrc->getEntry(y + 1, heightfieldHeight);
|
||||
if (middleX) {
|
||||
const StackArray::Entry& nextEntryX = lineSrc[1].getEntry(y, nextHeightfieldHeightX);
|
||||
const StackArray::Entry& nextEntryXY = lineSrc[1].getEntry(y + 1, nextHeightfieldHeightX);
|
||||
if (alpha0 != alpha1) {
|
||||
EdgeCrossing& crossing = crossings[crossingCount++];
|
||||
crossing.point = glm::vec3(entry.getHermiteX(crossing.normal), 0.0f, 0.0f);
|
||||
crossing.setColorMaterial(alpha0 == 0 ? nextEntryX : entry);
|
||||
if (crossingCount == 0) {
|
||||
StackArray::Entry nextEntryY = getEntry(lineSrc, stackWidth, y + 1,
|
||||
heightfieldHeight, cornerCrossings, 0);
|
||||
if (middleX) {
|
||||
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);
|
||||
crossing.setColorMaterial(alpha0 == 0 ? nextEntryX : entry);
|
||||
}
|
||||
if (alpha1 != alpha3) {
|
||||
EdgeCrossing& crossing = crossings[crossingCount++];
|
||||
crossing.point = glm::vec3(1.0f, nextEntryX.getHermiteY(crossing.normal), 0.0f);
|
||||
crossing.setColorMaterial(alpha1 == 0 ? nextEntryXY : nextEntryX);
|
||||
}
|
||||
if (alpha2 != alpha3) {
|
||||
EdgeCrossing& crossing = crossings[crossingCount++];
|
||||
crossing.point = glm::vec3(nextEntryY.getHermiteX(crossing.normal), 1.0f, 0.0f);
|
||||
crossing.setColorMaterial(alpha2 == 0 ? nextEntryXY : nextEntryY);
|
||||
}
|
||||
if (middleZ) {
|
||||
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));
|
||||
crossing.setColorMaterial(alpha1 == 0 ? nextEntryXZ : nextEntryX);
|
||||
}
|
||||
if (alpha3 != alpha7) {
|
||||
EdgeCrossing& crossing = crossings[crossingCount++];
|
||||
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);
|
||||
}
|
||||
if (alpha4 != alpha5) {
|
||||
EdgeCrossing& crossing = crossings[crossingCount++];
|
||||
crossing.point = glm::vec3(nextEntryZ.getHermiteX(crossing.normal), 0.0f, 1.0f);
|
||||
crossing.setColorMaterial(alpha4 == 0 ? nextEntryXZ : nextEntryZ);
|
||||
}
|
||||
if (alpha5 != alpha7) {
|
||||
EdgeCrossing& crossing = crossings[crossingCount++];
|
||||
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++];
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (alpha1 != alpha3) {
|
||||
if (alpha0 != alpha2) {
|
||||
EdgeCrossing& crossing = crossings[crossingCount++];
|
||||
crossing.point = glm::vec3(1.0f, nextEntryX.getHermiteY(crossing.normal), 0.0f);
|
||||
crossing.setColorMaterial(alpha1 == 0 ? nextEntryXY : nextEntryX);
|
||||
}
|
||||
if (alpha2 != alpha3) {
|
||||
EdgeCrossing& crossing = crossings[crossingCount++];
|
||||
crossing.point = glm::vec3(nextEntryY.getHermiteX(crossing.normal), 1.0f, 0.0f);
|
||||
crossing.setColorMaterial(alpha2 == 0 ? nextEntryXY : nextEntryY);
|
||||
crossing.point = glm::vec3(0.0f, entry.getHermiteY(crossing.normal), 0.0f);
|
||||
crossing.setColorMaterial(alpha0 == 0 ? nextEntryY : entry);
|
||||
}
|
||||
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);
|
||||
if (alpha1 != alpha5) {
|
||||
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(1.0f, 0.0f, nextEntryX.getHermiteZ(crossing.normal));
|
||||
crossing.setColorMaterial(alpha1 == 0 ? nextEntryXZ : nextEntryX);
|
||||
crossing.point = glm::vec3(0.0f, 0.0f, entry.getHermiteZ(crossing.normal));
|
||||
crossing.setColorMaterial(alpha0 == 0 ? nextEntryZ : entry);
|
||||
}
|
||||
if (alpha3 != alpha7) {
|
||||
if (alpha2 != alpha6) {
|
||||
EdgeCrossing& crossing = crossings[crossingCount++];
|
||||
const StackArray::Entry& nextEntryXY = lineSrc[1].getEntry(y + 1, nextHeightfieldHeightX);
|
||||
crossing.point = glm::vec3(1.0f, 1.0f, nextEntryXY.getHermiteZ(crossing.normal));
|
||||
crossing.setColorMaterial(alpha3 == 0 ? nextEntryXYZ : nextEntryXY);
|
||||
crossing.point = glm::vec3(0.0f, 1.0f, nextEntryY.getHermiteZ(crossing.normal));
|
||||
crossing.setColorMaterial(alpha2 == 0 ? nextEntryYZ : nextEntryY);
|
||||
}
|
||||
if (alpha4 != alpha5) {
|
||||
if (alpha4 != alpha6) {
|
||||
EdgeCrossing& crossing = crossings[crossingCount++];
|
||||
crossing.point = glm::vec3(nextEntryZ.getHermiteX(crossing.normal), 0.0f, 1.0f);
|
||||
crossing.setColorMaterial(alpha4 == 0 ? nextEntryXZ : nextEntryZ);
|
||||
}
|
||||
if (alpha5 != alpha7) {
|
||||
EdgeCrossing& crossing = crossings[crossingCount++];
|
||||
const StackArray::Entry& nextEntryXZ = lineSrc[stackWidth + 1].getEntry(
|
||||
y, nextHeightfieldHeightXZ);
|
||||
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);
|
||||
crossing.point = glm::vec3(nextEntryYZ.getHermiteX(crossing.normal), 1.0f, 1.0f);
|
||||
crossing.setColorMaterial(alpha6 == 0 ? nextEntryXYZ : nextEntryYZ);
|
||||
crossing.point = glm::vec3(0.0f, nextEntryZ.getHermiteY(crossing.normal), 1.0f);
|
||||
crossing.setColorMaterial(alpha4 == 0 ? nextEntryYZ : nextEntryZ);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (alpha0 != alpha2) {
|
||||
EdgeCrossing& crossing = crossings[crossingCount++];
|
||||
crossing.point = glm::vec3(0.0f, entry.getHermiteY(crossing.normal), 0.0f);
|
||||
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);
|
||||
if (alpha0 != alpha4) {
|
||||
EdgeCrossing& crossing = crossings[crossingCount++];
|
||||
crossing.point = glm::vec3(0.0f, 0.0f, entry.getHermiteZ(crossing.normal));
|
||||
crossing.setColorMaterial(alpha0 == 0 ? nextEntryZ : entry);
|
||||
}
|
||||
if (alpha2 != alpha6) {
|
||||
EdgeCrossing& crossing = crossings[crossingCount++];
|
||||
crossing.point = glm::vec3(0.0f, 1.0f, nextEntryY.getHermiteZ(crossing.normal));
|
||||
crossing.setColorMaterial(alpha2 == 0 ? nextEntryYZ : nextEntryY);
|
||||
}
|
||||
if (alpha4 != alpha6) {
|
||||
EdgeCrossing& crossing = crossings[crossingCount++];
|
||||
crossing.point = glm::vec3(0.0f, nextEntryZ.getHermiteY(crossing.normal), 1.0f);
|
||||
crossing.setColorMaterial(alpha4 == 0 ? nextEntryYZ : nextEntryZ);
|
||||
}
|
||||
}
|
||||
// determine whether we should ignore this vertex because it will be stitched
|
||||
// make sure we have valid crossings to include
|
||||
int validCrossings = 0;
|
||||
for (int i = 0; i < crossingCount; i++) {
|
||||
if (qAlpha(crossings[i].color) != 0) {
|
||||
|
@ -2107,173 +2250,6 @@ void HeightfieldNodeRenderer::render(const HeightfieldNodePointer& node, const g
|
|||
point.setNormal(normals[i]);
|
||||
vertices.append(point);
|
||||
}
|
||||
|
||||
if (stitchable) {
|
||||
int nextIndex = vertices.size();
|
||||
const NormalIndex& previousIndexX = lastIndicesX.getClosest(y);
|
||||
const NormalIndex& previousIndexZ = lastIndicesZ[x].getClosest(y);
|
||||
switch (corners) {
|
||||
case UPPER_LEFT_CORNER | UPPER_RIGHT_CORNER | LOWER_RIGHT_CORNER: {
|
||||
vertices.append(cornerPoints[0]);
|
||||
vertices.append(cornerPoints[3]);
|
||||
glm::vec3 normal = glm::cross(cornerPoints[0].vertex - cornerPoints[1].vertex,
|
||||
cornerPoints[3].vertex - cornerPoints[1].vertex);
|
||||
int firstIndex = index.getClosestIndex(normal, vertices);
|
||||
appendIndices(indices, quadIndices, vertices, step, firstIndex,
|
||||
nextIndex + 1, nextIndex, nextIndex);
|
||||
if (previousIndexX.isValid()) {
|
||||
appendIndices(indices, quadIndices, vertices, step, firstIndex, firstIndex,
|
||||
nextIndex, previousIndexX.getClosestIndex(normal, vertices));
|
||||
}
|
||||
break;
|
||||
}
|
||||
case UPPER_LEFT_CORNER | LOWER_LEFT_CORNER | LOWER_RIGHT_CORNER: {
|
||||
vertices.append(cornerPoints[0]);
|
||||
vertices.append(cornerPoints[3]);
|
||||
glm::vec3 normal = glm::cross(cornerPoints[3].vertex - cornerPoints[2].vertex,
|
||||
cornerPoints[0].vertex - cornerPoints[2].vertex);
|
||||
int firstIndex = index.getClosestIndex(normal, vertices);
|
||||
appendIndices(indices, quadIndices, vertices, step, firstIndex,
|
||||
nextIndex, nextIndex + 1, nextIndex + 1);
|
||||
if (previousIndexZ.isValid()) {
|
||||
appendIndices(indices, quadIndices, vertices, step, firstIndex, firstIndex,
|
||||
previousIndexZ.getClosestIndex(normal, vertices), nextIndex);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case UPPER_RIGHT_CORNER | LOWER_RIGHT_CORNER | LOWER_LEFT_CORNER: {
|
||||
vertices.append(cornerPoints[1]);
|
||||
vertices.append(cornerPoints[2]);
|
||||
vertices.append(cornerPoints[3]);
|
||||
glm::vec3 normal = glm::cross(cornerPoints[3].vertex - cornerPoints[2].vertex,
|
||||
cornerPoints[1].vertex - cornerPoints[2].vertex);
|
||||
int firstIndex = index.getClosestIndex(normal, vertices);
|
||||
appendIndices(indices, quadIndices, vertices, step, firstIndex,
|
||||
nextIndex + 2, nextIndex, nextIndex);
|
||||
appendIndices(indices, quadIndices, vertices, step, firstIndex,
|
||||
nextIndex + 1, nextIndex + 2, nextIndex + 2);
|
||||
if (previousIndexX.isValid()) {
|
||||
appendIndices(indices, quadIndices, vertices, step, firstIndex, firstIndex,
|
||||
previousIndexX.getClosestIndex(normal, vertices), nextIndex + 1);
|
||||
}
|
||||
if (previousIndexZ.isValid()) {
|
||||
appendIndices(indices, quadIndices, vertices, step, firstIndex, firstIndex,
|
||||
nextIndex, previousIndexZ.getClosestIndex(normal, vertices));
|
||||
}
|
||||
break;
|
||||
}
|
||||
case UPPER_LEFT_CORNER | UPPER_RIGHT_CORNER | LOWER_LEFT_CORNER: {
|
||||
vertices.append(cornerPoints[0]);
|
||||
vertices.append(cornerPoints[1]);
|
||||
vertices.append(cornerPoints[2]);
|
||||
glm::vec3 normal = glm::cross(cornerPoints[2].vertex - cornerPoints[0].vertex,
|
||||
cornerPoints[1].vertex - cornerPoints[0].vertex);
|
||||
int firstIndex = index.getClosestIndex(normal, vertices);
|
||||
appendIndices(indices, quadIndices, vertices, step, firstIndex,
|
||||
nextIndex + 1, nextIndex, nextIndex);
|
||||
appendIndices(indices, quadIndices, vertices, step, firstIndex,
|
||||
nextIndex, nextIndex + 2, nextIndex + 2);
|
||||
break;
|
||||
}
|
||||
case UPPER_LEFT_CORNER | UPPER_RIGHT_CORNER: {
|
||||
vertices.append(cornerPoints[0]);
|
||||
vertices.append(cornerPoints[1]);
|
||||
const glm::vec3& first = vertices.at(index.indices[0]).vertex;
|
||||
glm::vec3 normal = glm::cross(cornerPoints[1].vertex - first,
|
||||
cornerPoints[0].vertex - first);
|
||||
int firstIndex = index.getClosestIndex(normal, vertices);
|
||||
appendIndices(indices, quadIndices, vertices, step, firstIndex,
|
||||
nextIndex + 1, nextIndex, nextIndex);
|
||||
if (previousIndexX.isValid()) {
|
||||
appendIndices(indices, quadIndices, vertices, step, firstIndex, firstIndex,
|
||||
nextIndex, previousIndexX.getClosestIndex(normal, vertices));
|
||||
}
|
||||
break;
|
||||
}
|
||||
case UPPER_RIGHT_CORNER | LOWER_RIGHT_CORNER: {
|
||||
vertices.append(cornerPoints[1]);
|
||||
vertices.append(cornerPoints[3]);
|
||||
const glm::vec3& first = vertices.at(index.indices[0]).vertex;
|
||||
glm::vec3 normal = glm::cross(cornerPoints[3].vertex - first,
|
||||
cornerPoints[1].vertex - first);
|
||||
int firstIndex = index.getClosestIndex(normal, vertices);
|
||||
appendIndices(indices, quadIndices, vertices, step, firstIndex,
|
||||
nextIndex + 1, nextIndex, nextIndex);
|
||||
if (previousIndexZ.isValid()) {
|
||||
appendIndices(indices, quadIndices, vertices, step, firstIndex,
|
||||
firstIndex, nextIndex, previousIndexZ.getClosestIndex(normal, vertices));
|
||||
}
|
||||
break;
|
||||
}
|
||||
case LOWER_RIGHT_CORNER | LOWER_LEFT_CORNER: {
|
||||
vertices.append(cornerPoints[3]);
|
||||
vertices.append(cornerPoints[2]);
|
||||
const glm::vec3& first = vertices.at(index.indices[0]).vertex;
|
||||
glm::vec3 normal = glm::cross(cornerPoints[2].vertex - first,
|
||||
cornerPoints[3].vertex - first);
|
||||
int firstIndex = index.getClosestIndex(normal, vertices);
|
||||
appendIndices(indices, quadIndices, vertices, step, firstIndex,
|
||||
nextIndex + 1, nextIndex, nextIndex);
|
||||
if (previousIndexX.isValid()) {
|
||||
appendIndices(indices, quadIndices, vertices, step, firstIndex, firstIndex,
|
||||
previousIndexX.getClosestIndex(normal, vertices), nextIndex + 1);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case LOWER_LEFT_CORNER | UPPER_LEFT_CORNER: {
|
||||
vertices.append(cornerPoints[2]);
|
||||
vertices.append(cornerPoints[0]);
|
||||
const glm::vec3& first = vertices.at(index.indices[0]).vertex;
|
||||
glm::vec3 normal = glm::cross(cornerPoints[0].vertex - first,
|
||||
cornerPoints[2].vertex - first);
|
||||
int firstIndex = index.getClosestIndex(normal, vertices);
|
||||
appendIndices(indices, quadIndices, vertices, step, firstIndex,
|
||||
nextIndex + 1, nextIndex, nextIndex);
|
||||
if (previousIndexZ.isValid()) {
|
||||
appendIndices(indices, quadIndices, vertices, step, firstIndex, firstIndex,
|
||||
previousIndexZ.getClosestIndex(normal, vertices), nextIndex + 1);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case UPPER_LEFT_CORNER: {
|
||||
vertices.append(cornerPoints[0]);
|
||||
glm::vec3 normal = glm::cross(cornerPoints[0].vertex -
|
||||
vertices.at(index.indices[0]).vertex, glm::vec3(1.0f, 0.0f, 0.0f));
|
||||
int firstIndex = index.getClosestIndex(normal, vertices);
|
||||
if (previousIndexX.isValid()) {
|
||||
appendIndices(indices, quadIndices, vertices, step, firstIndex, firstIndex,
|
||||
nextIndex, previousIndexX.getClosestIndex(normal, vertices));
|
||||
}
|
||||
if (previousIndexZ.isValid()) {
|
||||
appendIndices(indices, quadIndices, vertices, step, firstIndex, firstIndex,
|
||||
previousIndexZ.getClosestIndex(normal, vertices), nextIndex);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case UPPER_RIGHT_CORNER: {
|
||||
vertices.append(cornerPoints[1]);
|
||||
glm::vec3 normal = glm::cross(cornerPoints[1].vertex -
|
||||
vertices.at(index.indices[0]).vertex, glm::vec3(1.0f, 0.0f, 0.0f));
|
||||
int firstIndex = index.getClosestIndex(normal, vertices);
|
||||
if (previousIndexZ.isValid()) {
|
||||
appendIndices(indices, quadIndices, vertices, step, firstIndex, firstIndex,
|
||||
nextIndex, previousIndexZ.getClosestIndex(normal, vertices));
|
||||
}
|
||||
break;
|
||||
}
|
||||
case LOWER_LEFT_CORNER: {
|
||||
vertices.append(cornerPoints[2]);
|
||||
glm::vec3 normal = glm::cross(cornerPoints[2].vertex -
|
||||
vertices.at(index.indices[0]).vertex, glm::vec3(1.0f, 0.0f, 0.0f));
|
||||
int firstIndex = index.getClosestIndex(normal, vertices);
|
||||
if (previousIndexX.isValid()) {
|
||||
appendIndices(indices, quadIndices, vertices, step, firstIndex, firstIndex,
|
||||
previousIndexX.getClosestIndex(normal, vertices), nextIndex);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// the first x, y, and z are repeated for the boundary edge; past that, we consider generating
|
||||
|
@ -2291,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));
|
||||
|
@ -2323,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));
|
||||
|
@ -2352,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));
|
||||
|
@ -2407,7 +2374,14 @@ void HeightfieldNodeRenderer::render(const HeightfieldNodePointer& node, const g
|
|||
baseBatch.heightTextureID = _heightTextureID;
|
||||
baseBatch.heightScale = glm::vec4(1.0f / width, 1.0f / height, (innerWidth - 1) / -2.0f, (innerHeight - 1) / -2.0f);
|
||||
baseBatch.colorTextureID = _colorTextureID;
|
||||
baseBatch.colorScale = glm::vec2((float)width / innerWidth, (float)height / innerHeight);
|
||||
float widthMultiplier = 1.0f / (0.5f - 1.5f / width);
|
||||
float heightMultiplier = 1.0f / (0.5f - 1.5f / height);
|
||||
if (node->getColor()) {
|
||||
int colorWidth = node->getColor()->getWidth();
|
||||
int colorHeight = node->getColor()->getContents().size() / (colorWidth * DataBlock::COLOR_BYTES);
|
||||
baseBatch.colorScale = glm::vec2((0.5f - 0.5f / colorWidth) * widthMultiplier,
|
||||
(0.5f - 0.5f / colorHeight) * heightMultiplier);
|
||||
}
|
||||
Application::getInstance()->getMetavoxels()->addHeightfieldBaseBatch(baseBatch);
|
||||
|
||||
if (!(cursor || _networkTextures.isEmpty())) {
|
||||
|
@ -2422,7 +2396,12 @@ void HeightfieldNodeRenderer::render(const HeightfieldNodePointer& node, const g
|
|||
splatBatch.heightTextureID = _heightTextureID;
|
||||
splatBatch.heightScale = glm::vec4(1.0f / width, 1.0f / height, 0.0f, 0.0f);
|
||||
splatBatch.materialTextureID = _materialTextureID;
|
||||
splatBatch.textureScale = glm::vec2((float)width / innerWidth, (float)height / innerHeight);
|
||||
if (node->getMaterial()) {
|
||||
int materialWidth = node->getMaterial()->getWidth();
|
||||
int materialHeight = node->getMaterial()->getContents().size() / materialWidth;
|
||||
splatBatch.textureScale = glm::vec2((0.5f - 0.5f / materialWidth) * widthMultiplier,
|
||||
(0.5f - 0.5f / materialHeight) * heightMultiplier);
|
||||
}
|
||||
splatBatch.splatTextureOffset = glm::vec2(
|
||||
glm::dot(translation, rotation * glm::vec3(1.0f, 0.0f, 0.0f)) / scale.x,
|
||||
glm::dot(translation, rotation * glm::vec3(0.0f, 0.0f, 1.0f)) / scale.z);
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -1,302 +0,0 @@
|
|||
//
|
||||
// EntityCollisionSystem.cpp
|
||||
// libraries/entities/src
|
||||
//
|
||||
// Created by Brad Hefta-Gaub on 9/23/14.
|
||||
// Copyright 2013-2014 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 <algorithm>
|
||||
#include <AbstractAudioInterface.h>
|
||||
#include <AvatarData.h>
|
||||
#include <CollisionInfo.h>
|
||||
#include <HeadData.h>
|
||||
#include <HandData.h>
|
||||
#include <PerfStat.h>
|
||||
#include <SphereShape.h>
|
||||
|
||||
#include "EntityCollisionSystem.h"
|
||||
#include "EntityEditPacketSender.h"
|
||||
#include "EntityItem.h"
|
||||
#include "EntityTreeElement.h"
|
||||
#include "EntityTree.h"
|
||||
|
||||
const int MAX_COLLISIONS_PER_Entity = 16;
|
||||
|
||||
EntityCollisionSystem::EntityCollisionSystem()
|
||||
: SimpleEntitySimulation(),
|
||||
_packetSender(NULL),
|
||||
_avatars(NULL),
|
||||
_collisions(MAX_COLLISIONS_PER_Entity) {
|
||||
}
|
||||
|
||||
void EntityCollisionSystem::init(EntityEditPacketSender* packetSender,
|
||||
EntityTree* entities, AvatarHashMap* avatars) {
|
||||
assert(entities);
|
||||
setEntityTree(entities);
|
||||
_packetSender = packetSender;
|
||||
_avatars = avatars;
|
||||
}
|
||||
|
||||
EntityCollisionSystem::~EntityCollisionSystem() {
|
||||
}
|
||||
|
||||
void EntityCollisionSystem::updateCollisions() {
|
||||
PerformanceTimer perfTimer("collisions");
|
||||
assert(_entityTree);
|
||||
// update all Entities
|
||||
if (_entityTree->tryLockForWrite()) {
|
||||
foreach (EntityItem* entity, _movingEntities) {
|
||||
checkEntity(entity);
|
||||
}
|
||||
_entityTree->unlock();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void EntityCollisionSystem::checkEntity(EntityItem* entity) {
|
||||
updateCollisionWithEntities(entity);
|
||||
updateCollisionWithAvatars(entity);
|
||||
}
|
||||
|
||||
void EntityCollisionSystem::emitGlobalEntityCollisionWithEntity(EntityItem* entityA,
|
||||
EntityItem* entityB, const Collision& collision) {
|
||||
|
||||
EntityItemID idA = entityA->getEntityItemID();
|
||||
EntityItemID idB = entityB->getEntityItemID();
|
||||
emit entityCollisionWithEntity(idA, idB, collision);
|
||||
}
|
||||
|
||||
void EntityCollisionSystem::updateCollisionWithEntities(EntityItem* entityA) {
|
||||
|
||||
if (entityA->getIgnoreForCollisions()) {
|
||||
return; // bail early if this entity is to be ignored...
|
||||
}
|
||||
|
||||
// don't collide entities with unknown IDs,
|
||||
if (!entityA->isKnownID()) {
|
||||
return;
|
||||
}
|
||||
|
||||
glm::vec3 penetration;
|
||||
EntityItem* entityB = NULL;
|
||||
|
||||
const int MAX_COLLISIONS_PER_ENTITY = 32;
|
||||
CollisionList collisions(MAX_COLLISIONS_PER_ENTITY);
|
||||
bool shapeCollisionsAccurate = false;
|
||||
|
||||
bool shapeCollisions = _entityTree->findShapeCollisions(&entityA->getCollisionShapeInMeters(),
|
||||
collisions, Octree::NoLock, &shapeCollisionsAccurate);
|
||||
|
||||
if (shapeCollisions) {
|
||||
for(int i = 0; i < collisions.size(); i++) {
|
||||
|
||||
CollisionInfo* collision = collisions[i];
|
||||
penetration = collision->_penetration;
|
||||
entityB = static_cast<EntityItem*>(collision->_extraData);
|
||||
|
||||
// The collision _extraData should be a valid entity, but if for some reason
|
||||
// it's NULL then continue with a warning.
|
||||
if (!entityB) {
|
||||
qDebug() << "UNEXPECTED - we have a collision with missing _extraData. Something went wrong down below!";
|
||||
continue; // skip this loop pass if the entity is NULL
|
||||
}
|
||||
|
||||
// don't collide entities with unknown IDs,
|
||||
if (!entityB->isKnownID()) {
|
||||
continue; // skip this loop pass if the entity has an unknown ID
|
||||
}
|
||||
|
||||
// NOTE: 'penetration' is the depth that 'entityA' overlaps 'entityB'. It points from A into B.
|
||||
glm::vec3 penetrationInTreeUnits = penetration / (float)(TREE_SCALE);
|
||||
|
||||
// Even if the Entities overlap... when the Entities are already moving appart
|
||||
// we don't want to count this as a collision.
|
||||
glm::vec3 relativeVelocity = entityA->getVelocity() - entityB->getVelocity();
|
||||
|
||||
bool fullyEnclosedCollision = glm::length(penetrationInTreeUnits) > entityA->getLargestDimension();
|
||||
|
||||
bool wantToMoveA = entityA->getCollisionsWillMove();
|
||||
bool wantToMoveB = entityB->getCollisionsWillMove();
|
||||
bool movingTowardEachOther = glm::dot(relativeVelocity, penetrationInTreeUnits) > 0.0f;
|
||||
|
||||
// only do collisions if the entities are moving toward each other and one or the other
|
||||
// of the entities are movable from collisions
|
||||
bool doCollisions = !fullyEnclosedCollision && movingTowardEachOther && (wantToMoveA || wantToMoveB);
|
||||
|
||||
if (doCollisions) {
|
||||
|
||||
quint64 now = usecTimestampNow();
|
||||
|
||||
glm::vec3 axis = glm::normalize(penetration);
|
||||
glm::vec3 axialVelocity = glm::dot(relativeVelocity, axis) * axis;
|
||||
|
||||
float massA = entityA->computeMass();
|
||||
float massB = entityB->computeMass();
|
||||
float totalMass = massA + massB;
|
||||
float massRatioA = (2.0f * massB / totalMass);
|
||||
float massRatioB = (2.0f * massA / totalMass);
|
||||
|
||||
// in the event that one of our entities is non-moving, then fix up these ratios
|
||||
if (wantToMoveA && !wantToMoveB) {
|
||||
massRatioA = 2.0f;
|
||||
massRatioB = 0.0f;
|
||||
}
|
||||
|
||||
if (!wantToMoveA && wantToMoveB) {
|
||||
massRatioA = 0.0f;
|
||||
massRatioB = 2.0f;
|
||||
}
|
||||
|
||||
// unless the entity is configured to not be moved by collision, calculate it's new position
|
||||
// and velocity and apply it
|
||||
if (wantToMoveA) {
|
||||
// handle Entity A
|
||||
glm::vec3 newVelocityA = entityA->getVelocity() - axialVelocity * massRatioA;
|
||||
glm::vec3 newPositionA = entityA->getPosition() - 0.5f * penetrationInTreeUnits;
|
||||
|
||||
EntityItemProperties propertiesA = entityA->getProperties();
|
||||
EntityItemID idA(entityA->getID());
|
||||
propertiesA.setVelocity(newVelocityA * (float)TREE_SCALE);
|
||||
propertiesA.setPosition(newPositionA * (float)TREE_SCALE);
|
||||
propertiesA.setLastEdited(now);
|
||||
|
||||
// NOTE: EntityTree::updateEntity() will cause the entity to get sorted correctly in the EntitySimulation,
|
||||
// thereby waking up static non-moving entities.
|
||||
_entityTree->updateEntity(entityA, propertiesA);
|
||||
_packetSender->queueEditEntityMessage(PacketTypeEntityAddOrEdit, idA, propertiesA);
|
||||
}
|
||||
|
||||
// unless the entity is configured to not be moved by collision, calculate it's new position
|
||||
// and velocity and apply it
|
||||
if (wantToMoveB) {
|
||||
glm::vec3 newVelocityB = entityB->getVelocity() + axialVelocity * massRatioB;
|
||||
glm::vec3 newPositionB = entityB->getPosition() + 0.5f * penetrationInTreeUnits;
|
||||
|
||||
EntityItemProperties propertiesB = entityB->getProperties();
|
||||
|
||||
EntityItemID idB(entityB->getID());
|
||||
propertiesB.setVelocity(newVelocityB * (float)TREE_SCALE);
|
||||
propertiesB.setPosition(newPositionB * (float)TREE_SCALE);
|
||||
propertiesB.setLastEdited(now);
|
||||
|
||||
// NOTE: EntityTree::updateEntity() will cause the entity to get sorted correctly in the EntitySimulation,
|
||||
// thereby waking up static non-moving entities.
|
||||
_entityTree->updateEntity(entityB, propertiesB);
|
||||
_packetSender->queueEditEntityMessage(PacketTypeEntityAddOrEdit, idB, propertiesB);
|
||||
}
|
||||
|
||||
// NOTE: Do this after updating the entities so that the callback can delete the entities if they want to
|
||||
Collision collision;
|
||||
collision.penetration = penetration;
|
||||
collision.contactPoint = (0.5f * (float)TREE_SCALE) * (entityA->getPosition() + entityB->getPosition());
|
||||
emitGlobalEntityCollisionWithEntity(entityA, entityB, collision);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void EntityCollisionSystem::updateCollisionWithAvatars(EntityItem* entity) {
|
||||
|
||||
// Entities that are in hand, don't collide with avatars
|
||||
if (!_avatars) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (entity->getIgnoreForCollisions() || !entity->getCollisionsWillMove()) {
|
||||
return; // bail early if this entity is to be ignored or wont move
|
||||
}
|
||||
|
||||
glm::vec3 center = entity->getPosition() * (float)(TREE_SCALE);
|
||||
float radius = entity->getRadius() * (float)(TREE_SCALE);
|
||||
const float ELASTICITY = 0.9f;
|
||||
const float DAMPING = 0.1f;
|
||||
glm::vec3 penetration;
|
||||
|
||||
_collisions.clear();
|
||||
foreach (const AvatarSharedPointer& avatarPointer, _avatars->getAvatarHash()) {
|
||||
AvatarData* avatar = avatarPointer.data();
|
||||
|
||||
float totalRadius = avatar->getBoundingRadius() + radius;
|
||||
glm::vec3 relativePosition = center - avatar->getPosition();
|
||||
if (glm::dot(relativePosition, relativePosition) > (totalRadius * totalRadius)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (avatar->findSphereCollisions(center, radius, _collisions)) {
|
||||
int numCollisions = _collisions.size();
|
||||
for (int i = 0; i < numCollisions; ++i) {
|
||||
CollisionInfo* collision = _collisions.getCollision(i);
|
||||
collision->_damping = DAMPING;
|
||||
collision->_elasticity = ELASTICITY;
|
||||
|
||||
collision->_addedVelocity /= (float)(TREE_SCALE);
|
||||
glm::vec3 relativeVelocity = collision->_addedVelocity - entity->getVelocity();
|
||||
|
||||
if (glm::dot(relativeVelocity, collision->_penetration) <= 0.0f) {
|
||||
// only collide when Entity and collision point are moving toward each other
|
||||
// (doing this prevents some "collision snagging" when Entity penetrates the object)
|
||||
collision->_penetration /= (float)(TREE_SCALE);
|
||||
applyHardCollision(entity, *collision);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void EntityCollisionSystem::applyHardCollision(EntityItem* entity, const CollisionInfo& collisionInfo) {
|
||||
|
||||
// don't collide entities with unknown IDs,
|
||||
if (!entity->isKnownID()) {
|
||||
return;
|
||||
}
|
||||
|
||||
// HALTING_* params are determined using expected acceleration of gravity over some timescale.
|
||||
// This is a HACK for entities that bounce in a 1.0 gravitational field and should eventually be made more universal.
|
||||
const float HALTING_ENTITY_PERIOD = 0.0167f; // ~1/60th of a second
|
||||
const float HALTING_ENTITY_SPEED = 9.8 * HALTING_ENTITY_PERIOD / (float)(TREE_SCALE);
|
||||
|
||||
//
|
||||
// Update the entity in response to a hard collision. Position will be reset exactly
|
||||
// to outside the colliding surface. Velocity will be modified according to elasticity.
|
||||
//
|
||||
// if elasticity = 0.0, collision is inelastic (vel normal to collision is lost)
|
||||
// if elasticity = 1.0, collision is 100% elastic.
|
||||
//
|
||||
glm::vec3 position = entity->getPosition();
|
||||
glm::vec3 velocity = entity->getVelocity();
|
||||
|
||||
const float EPSILON = 0.0f;
|
||||
glm::vec3 relativeVelocity = collisionInfo._addedVelocity - velocity;
|
||||
float velocityDotPenetration = glm::dot(relativeVelocity, collisionInfo._penetration);
|
||||
if (velocityDotPenetration < EPSILON) {
|
||||
// entity is moving into collision surface
|
||||
//
|
||||
// TODO: do something smarter here by comparing the mass of the entity vs that of the other thing
|
||||
// (other's mass could be stored in the Collision Info). The smaller mass should surrender more
|
||||
// position offset and should slave more to the other's velocity in the static-friction case.
|
||||
position -= collisionInfo._penetration;
|
||||
|
||||
if (glm::length(relativeVelocity) < HALTING_ENTITY_SPEED) {
|
||||
// static friction kicks in and entities moves with colliding object
|
||||
velocity = collisionInfo._addedVelocity;
|
||||
} else {
|
||||
glm::vec3 direction = glm::normalize(collisionInfo._penetration);
|
||||
velocity += glm::dot(relativeVelocity, direction) * (1.0f + collisionInfo._elasticity) * direction; // dynamic reflection
|
||||
velocity += glm::clamp(collisionInfo._damping, 0.0f, 1.0f) * (relativeVelocity - glm::dot(relativeVelocity, direction) * direction); // dynamic friction
|
||||
}
|
||||
}
|
||||
|
||||
EntityItemProperties properties = entity->getProperties();
|
||||
EntityItemID entityItemID(entity->getID());
|
||||
|
||||
properties.setPosition(position * (float)TREE_SCALE);
|
||||
properties.setVelocity(velocity * (float)TREE_SCALE);
|
||||
properties.setLastEdited(usecTimestampNow());
|
||||
|
||||
_entityTree->updateEntity(entity, properties);
|
||||
_packetSender->queueEditEntityMessage(PacketTypeEntityAddOrEdit, entityItemID, properties);
|
||||
}
|
|
@ -1,65 +0,0 @@
|
|||
//
|
||||
// EntityCollisionSystem.h
|
||||
// libraries/entities/src
|
||||
//
|
||||
// Created by Brad Hefta-Gaub on 9/23/14.
|
||||
// Copyright 2013-2014 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_EntityCollisionSystem_h
|
||||
#define hifi_EntityCollisionSystem_h
|
||||
|
||||
#include <glm/glm.hpp>
|
||||
#include <stdint.h>
|
||||
|
||||
#include <QtScript/QScriptEngine>
|
||||
#include <QtCore/QObject>
|
||||
|
||||
#include <AvatarHashMap.h>
|
||||
#include <CollisionInfo.h>
|
||||
#include <OctreePacketData.h>
|
||||
#include <SharedUtil.h>
|
||||
|
||||
#include "EntityItem.h"
|
||||
#include "SimpleEntitySimulation.h"
|
||||
|
||||
class AbstractAudioInterface;
|
||||
class AvatarData;
|
||||
class EntityEditPacketSender;
|
||||
class EntityTree;
|
||||
|
||||
class EntityCollisionSystem : public QObject, public SimpleEntitySimulation {
|
||||
Q_OBJECT
|
||||
public:
|
||||
EntityCollisionSystem();
|
||||
|
||||
void init(EntityEditPacketSender* packetSender, EntityTree* entities, AvatarHashMap* _avatars = NULL);
|
||||
|
||||
~EntityCollisionSystem();
|
||||
|
||||
void updateCollisions();
|
||||
|
||||
void checkEntity(EntityItem* Entity);
|
||||
void updateCollisionWithEntities(EntityItem* Entity);
|
||||
void updateCollisionWithAvatars(EntityItem* Entity);
|
||||
void queueEntityPropertiesUpdate(EntityItem* Entity);
|
||||
|
||||
signals:
|
||||
void entityCollisionWithEntity(const EntityItemID& idA, const EntityItemID& idB, const Collision& collision);
|
||||
|
||||
private:
|
||||
void applyHardCollision(EntityItem* entity, const CollisionInfo& collisionInfo);
|
||||
|
||||
static bool updateOperation(OctreeElement* element, void* extraData);
|
||||
void emitGlobalEntityCollisionWithEntity(EntityItem* entityA, EntityItem* entityB, const Collision& penetration);
|
||||
|
||||
EntityEditPacketSender* _packetSender;
|
||||
AbstractAudioInterface* _audio;
|
||||
AvatarHashMap* _avatars;
|
||||
CollisionList _collisions;
|
||||
};
|
||||
|
||||
#endif // hifi_EntityCollisionSystem_h
|
|
@ -12,6 +12,7 @@
|
|||
#ifndef hifi_EntitySimulation_h
|
||||
#define hifi_EntitySimulation_h
|
||||
|
||||
#include <QtCore/QObject>
|
||||
#include <QSet>
|
||||
|
||||
#include <PerfStat.h>
|
||||
|
@ -31,7 +32,8 @@ const int DIRTY_SIMULATION_FLAGS =
|
|||
EntityItem::DIRTY_LIFETIME |
|
||||
EntityItem::DIRTY_UPDATEABLE;
|
||||
|
||||
class EntitySimulation {
|
||||
class EntitySimulation : public QObject {
|
||||
Q_OBJECT
|
||||
public:
|
||||
EntitySimulation() : _mutex(QMutex::Recursive), _entityTree(NULL) { }
|
||||
virtual ~EntitySimulation() { setEntityTree(NULL); }
|
||||
|
@ -61,6 +63,9 @@ public:
|
|||
|
||||
EntityTree* getEntityTree() { return _entityTree; }
|
||||
|
||||
signals:
|
||||
void entityCollisionWithEntity(const EntityItemID& idA, const EntityItemID& idB, const Collision& collision);
|
||||
|
||||
protected:
|
||||
|
||||
// These pure virtual methods are protected because they are not to be called will-nilly. The base class
|
||||
|
|
29
libraries/physics/src/ContactInfo.cpp
Normal file
29
libraries/physics/src/ContactInfo.cpp
Normal file
|
@ -0,0 +1,29 @@
|
|||
//
|
||||
// ContactEvent.cpp
|
||||
// libraries/physcis/src
|
||||
//
|
||||
// Created by Andrew Meadows 2015.01.20
|
||||
// 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 "BulletUtil.h"
|
||||
#include "ContactInfo.h"
|
||||
|
||||
void ContactInfo::update(uint32_t currentStep, btManifoldPoint& p, const glm::vec3& worldOffset) {
|
||||
_lastStep = currentStep;
|
||||
++_numSteps;
|
||||
contactPoint = bulletToGLM(p.m_positionWorldOnB) + worldOffset;
|
||||
penetration = bulletToGLM(p.m_distance1 * p.m_normalWorldOnB);
|
||||
// TODO: also report normal
|
||||
//_normal = bulletToGLM(p.m_normalWorldOnB);
|
||||
}
|
||||
|
||||
ContactEventType ContactInfo::computeType(uint32_t thisStep) {
|
||||
if (_lastStep != thisStep) {
|
||||
return CONTACT_EVENT_TYPE_END;
|
||||
}
|
||||
return (_numSteps == 1) ? CONTACT_EVENT_TYPE_START : CONTACT_EVENT_TYPE_CONTINUE;
|
||||
}
|
37
libraries/physics/src/ContactInfo.h
Normal file
37
libraries/physics/src/ContactInfo.h
Normal file
|
@ -0,0 +1,37 @@
|
|||
//
|
||||
// ContactEvent.h
|
||||
// libraries/physcis/src
|
||||
//
|
||||
// Created by Andrew Meadows 2015.01.20
|
||||
// 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_ContactEvent_h
|
||||
#define hifi_ContactEvent_h
|
||||
|
||||
#include <btBulletDynamicsCommon.h>
|
||||
#include <glm/glm.hpp>
|
||||
|
||||
#include "RegisteredMetaTypes.h"
|
||||
|
||||
enum ContactEventType {
|
||||
CONTACT_EVENT_TYPE_START,
|
||||
CONTACT_EVENT_TYPE_CONTINUE,
|
||||
CONTACT_EVENT_TYPE_END
|
||||
};
|
||||
|
||||
class ContactInfo : public Collision
|
||||
{
|
||||
public:
|
||||
void update(uint32_t currentStep, btManifoldPoint& p, const glm::vec3& worldOffset);
|
||||
ContactEventType computeType(uint32_t thisStep);
|
||||
private:
|
||||
uint32_t _lastStep = 0;
|
||||
uint32_t _numSteps = 0;
|
||||
};
|
||||
|
||||
|
||||
#endif // hifi_ContactEvent_h
|
|
@ -33,6 +33,7 @@ void EntityMotionState::enqueueOutgoingEntity(EntityItem* entity) {
|
|||
|
||||
EntityMotionState::EntityMotionState(EntityItem* entity)
|
||||
: _entity(entity) {
|
||||
_type = MOTION_STATE_TYPE_ENTITY;
|
||||
assert(entity != NULL);
|
||||
}
|
||||
|
||||
|
|
|
@ -60,6 +60,8 @@ public:
|
|||
uint32_t getIncomingDirtyFlags() const;
|
||||
void clearIncomingDirtyFlags(uint32_t flags) { _entity->clearDirtyFlags(flags); }
|
||||
|
||||
EntityItem* getEntity() const { return _entity; }
|
||||
|
||||
protected:
|
||||
EntityItem* _entity;
|
||||
};
|
||||
|
|
|
@ -13,10 +13,10 @@
|
|||
#include "PhysicsEngine.h"
|
||||
|
||||
KinematicController::KinematicController() {
|
||||
_lastFrame = PhysicsEngine::getFrameCount();
|
||||
_lastSubstep = PhysicsEngine::getNumSubsteps();
|
||||
}
|
||||
|
||||
void KinematicController::start() {
|
||||
_enabled = true;
|
||||
_lastFrame = PhysicsEngine::getFrameCount();
|
||||
_lastSubstep = PhysicsEngine::getNumSubsteps();
|
||||
}
|
||||
|
|
|
@ -30,7 +30,7 @@ public:
|
|||
|
||||
protected:
|
||||
bool _enabled = false;
|
||||
uint32_t _lastFrame;
|
||||
uint32_t _lastSubstep;
|
||||
};
|
||||
|
||||
#endif // hifi_KinematicController_h
|
||||
|
|
|
@ -108,6 +108,16 @@ bool ObjectMotionState::doesNotNeedToSendUpdate() const {
|
|||
|
||||
bool ObjectMotionState::shouldSendUpdate(uint32_t simulationFrame) {
|
||||
assert(_body);
|
||||
|
||||
// if we've never checked before, our _sentFrame will be 0, and we need to initialize our state
|
||||
if (_sentFrame == 0) {
|
||||
_sentPosition = bulletToGLM(_body->getWorldTransform().getOrigin());
|
||||
_sentVelocity = bulletToGLM(_body->getLinearVelocity());
|
||||
_sentAngularVelocity = bulletToGLM(_body->getAngularVelocity());
|
||||
_sentFrame = simulationFrame;
|
||||
return false;
|
||||
}
|
||||
|
||||
float dt = (float)(simulationFrame - _sentFrame) * PHYSICS_ENGINE_FIXED_SUBSTEP;
|
||||
_sentFrame = simulationFrame;
|
||||
bool isActive = _body->isActive();
|
||||
|
@ -170,3 +180,16 @@ void ObjectMotionState::removeKinematicController() {
|
|||
_kinematicController = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
void ObjectMotionState::setRigidBody(btRigidBody* body) {
|
||||
// give the body a (void*) back-pointer to this ObjectMotionState
|
||||
if (_body != body) {
|
||||
if (_body) {
|
||||
_body->setUserPointer(NULL);
|
||||
}
|
||||
_body = body;
|
||||
if (_body) {
|
||||
_body->setUserPointer(this);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -17,6 +17,7 @@
|
|||
|
||||
#include <EntityItem.h>
|
||||
|
||||
#include "ContactInfo.h"
|
||||
#include "ShapeInfo.h"
|
||||
|
||||
enum MotionType {
|
||||
|
@ -25,6 +26,12 @@ enum MotionType {
|
|||
MOTION_TYPE_KINEMATIC // keyframed motion
|
||||
};
|
||||
|
||||
enum MotionStateType {
|
||||
MOTION_STATE_TYPE_UNKNOWN,
|
||||
MOTION_STATE_TYPE_ENTITY,
|
||||
MOTION_STATE_TYPE_AVATAR
|
||||
};
|
||||
|
||||
// The update flags trigger two varieties of updates: "hard" which require the body to be pulled
|
||||
// and re-added to the physics engine and "easy" which just updates the body properties.
|
||||
const uint32_t HARD_DIRTY_PHYSICS_FLAGS = (uint32_t)(EntityItem::DIRTY_MOTION_TYPE | EntityItem::DIRTY_SHAPE);
|
||||
|
@ -58,6 +65,7 @@ public:
|
|||
virtual void updateObjectEasy(uint32_t flags, uint32_t frame) = 0;
|
||||
virtual void updateObjectVelocities() = 0;
|
||||
|
||||
MotionStateType getType() const { return _type; }
|
||||
virtual MotionType getMotionType() const { return _motionType; }
|
||||
|
||||
virtual void computeShapeInfo(ShapeInfo& info) = 0;
|
||||
|
@ -88,9 +96,15 @@ public:
|
|||
virtual void addKinematicController() = 0;
|
||||
virtual void removeKinematicController();
|
||||
|
||||
btRigidBody* getRigidBody() const { return _body; }
|
||||
|
||||
friend class PhysicsEngine;
|
||||
protected:
|
||||
// TODO: move these materials properties to EntityItem
|
||||
void setRigidBody(btRigidBody* body);
|
||||
|
||||
MotionStateType _type = MOTION_STATE_TYPE_UNKNOWN;
|
||||
|
||||
// TODO: move these materials properties outside of ObjectMotionState
|
||||
float _friction;
|
||||
float _restitution;
|
||||
float _linearDamping;
|
||||
|
@ -98,7 +112,6 @@ protected:
|
|||
|
||||
MotionType _motionType;
|
||||
|
||||
// _body has NO setters -- it is only changed by PhysicsEngine
|
||||
btRigidBody* _body;
|
||||
|
||||
bool _sentMoving; // true if last update was moving
|
||||
|
|
|
@ -13,21 +13,15 @@
|
|||
#include "ShapeInfoUtil.h"
|
||||
#include "ThreadSafeDynamicsWorld.h"
|
||||
|
||||
static uint32_t _frameCount;
|
||||
static uint32_t _numSubsteps;
|
||||
|
||||
// static
|
||||
uint32_t PhysicsEngine::getFrameCount() {
|
||||
return _frameCount;
|
||||
uint32_t PhysicsEngine::getNumSubsteps() {
|
||||
return _numSubsteps;
|
||||
}
|
||||
|
||||
PhysicsEngine::PhysicsEngine(const glm::vec3& offset)
|
||||
: _collisionConfig(NULL),
|
||||
_collisionDispatcher(NULL),
|
||||
_broadphaseFilter(NULL),
|
||||
_constraintSolver(NULL),
|
||||
_dynamicsWorld(NULL),
|
||||
_originOffset(offset),
|
||||
_entityPacketSender(NULL) {
|
||||
: _originOffset(offset) {
|
||||
}
|
||||
|
||||
PhysicsEngine::~PhysicsEngine() {
|
||||
|
@ -47,8 +41,8 @@ void PhysicsEngine::updateEntitiesInternal(const quint64& now) {
|
|||
ObjectMotionState* state = *stateItr;
|
||||
if (state->doesNotNeedToSendUpdate()) {
|
||||
stateItr = _outgoingPackets.erase(stateItr);
|
||||
} else if (state->shouldSendUpdate(_frameCount)) {
|
||||
state->sendUpdate(_entityPacketSender, _frameCount);
|
||||
} else if (state->shouldSendUpdate(_numSubsteps)) {
|
||||
state->sendUpdate(_entityPacketSender, _numSubsteps);
|
||||
++stateItr;
|
||||
} else {
|
||||
++stateItr;
|
||||
|
@ -135,7 +129,7 @@ void PhysicsEngine::relayIncomingChangesToSimulation() {
|
|||
ObjectMotionState* motionState = *stateItr;
|
||||
uint32_t flags = motionState->getIncomingDirtyFlags() & DIRTY_PHYSICS_FLAGS;
|
||||
|
||||
btRigidBody* body = motionState->_body;
|
||||
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
|
||||
|
@ -144,7 +138,7 @@ void PhysicsEngine::relayIncomingChangesToSimulation() {
|
|||
} 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, _frameCount);
|
||||
motionState->updateObjectEasy(flags, _numSubsteps);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -163,6 +157,20 @@ void PhysicsEngine::relayIncomingChangesToSimulation() {
|
|||
_incomingChanges.clear();
|
||||
}
|
||||
|
||||
void PhysicsEngine::removeContacts(ObjectMotionState* motionState) {
|
||||
// trigger events for new/existing/old contacts
|
||||
ContactMap::iterator contactItr = _contactMap.begin();
|
||||
while (contactItr != _contactMap.end()) {
|
||||
if (contactItr->first._a == motionState || contactItr->first._b == motionState) {
|
||||
ContactMap::iterator iterToDelete = contactItr;
|
||||
++contactItr;
|
||||
_contactMap.erase(iterToDelete);
|
||||
} else {
|
||||
++contactItr;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// virtual
|
||||
void PhysicsEngine::init(EntityEditPacketSender* packetSender) {
|
||||
// _entityTree should be set prior to the init() call
|
||||
|
@ -178,22 +186,6 @@ void PhysicsEngine::init(EntityEditPacketSender* packetSender) {
|
|||
// default gravity of the world is zero, so each object must specify its own gravity
|
||||
// TODO: set up gravity zones
|
||||
_dynamicsWorld->setGravity(btVector3(0.0f, 0.0f, 0.0f));
|
||||
|
||||
// GROUND HACK: add a big planar floor (and walls for testing) to catch falling objects
|
||||
btTransform groundTransform;
|
||||
groundTransform.setIdentity();
|
||||
for (int i = 0; i < 3; ++i) {
|
||||
btVector3 normal(0.0f, 0.0f, 0.0f);
|
||||
normal[i] = 1.0f;
|
||||
btCollisionShape* plane = new btStaticPlaneShape(normal, 0.0f);
|
||||
|
||||
btCollisionObject* groundObject = new btCollisionObject();
|
||||
groundObject->setCollisionFlags(btCollisionObject::CF_STATIC_OBJECT);
|
||||
groundObject->setCollisionShape(plane);
|
||||
|
||||
groundObject->setWorldTransform(groundTransform);
|
||||
_dynamicsWorld->addCollisionObject(groundObject);
|
||||
}
|
||||
}
|
||||
|
||||
assert(packetSender);
|
||||
|
@ -219,23 +211,103 @@ void PhysicsEngine::stepSimulation() {
|
|||
float timeStep = btMin(dt, MAX_TIMESTEP);
|
||||
|
||||
// This is step (2).
|
||||
int numSubSteps = _dynamicsWorld->stepSimulation(timeStep, MAX_NUM_SUBSTEPS, PHYSICS_ENGINE_FIXED_SUBSTEP);
|
||||
_frameCount += (uint32_t)numSubSteps;
|
||||
int numSubsteps = _dynamicsWorld->stepSimulation(timeStep, MAX_NUM_SUBSTEPS, PHYSICS_ENGINE_FIXED_SUBSTEP);
|
||||
_numSubsteps += (uint32_t)numSubsteps;
|
||||
unlock();
|
||||
|
||||
// This is step (3) which is done outside of stepSimulation() so we can lock _entityTree.
|
||||
if (numSubsteps > 0) {
|
||||
// This is step (3) which is done outside of stepSimulation() so we can lock _entityTree.
|
||||
//
|
||||
// Unfortunately we have to unlock the simulation (above) before we try to lock the _entityTree
|
||||
// to avoid deadlock -- the _entityTree may try to lock its EntitySimulation (from which this
|
||||
// PhysicsEngine derives) when updating/adding/deleting entities so we need to wait for our own
|
||||
// lock on the tree before we re-lock ourselves.
|
||||
//
|
||||
// TODO: untangle these lock sequences.
|
||||
_entityTree->lockForWrite();
|
||||
lock();
|
||||
_dynamicsWorld->synchronizeMotionStates();
|
||||
unlock();
|
||||
_entityTree->unlock();
|
||||
|
||||
computeCollisionEvents();
|
||||
}
|
||||
}
|
||||
|
||||
void PhysicsEngine::computeCollisionEvents() {
|
||||
// update all contacts every frame
|
||||
int numManifolds = _collisionDispatcher->getNumManifolds();
|
||||
for (int i = 0; i < numManifolds; ++i) {
|
||||
btPersistentManifold* contactManifold = _collisionDispatcher->getManifoldByIndexInternal(i);
|
||||
if (contactManifold->getNumContacts() > 0) {
|
||||
// TODO: require scripts to register interest in callbacks for specific objects
|
||||
// so we can filter out most collision events right here.
|
||||
const btCollisionObject* objectA = static_cast<const btCollisionObject*>(contactManifold->getBody0());
|
||||
const btCollisionObject* objectB = static_cast<const btCollisionObject*>(contactManifold->getBody1());
|
||||
|
||||
if (!(objectA->isActive() || objectB->isActive())) {
|
||||
// both objects are inactive so stop tracking this contact,
|
||||
// which will eventually trigger a CONTACT_EVENT_TYPE_END
|
||||
continue;
|
||||
}
|
||||
|
||||
void* a = objectA->getUserPointer();
|
||||
void* b = objectB->getUserPointer();
|
||||
if (a || b) {
|
||||
// the manifold has up to 4 distinct points, but only extract info from the first
|
||||
_contactMap[ContactKey(a, b)].update(_numContactFrames, contactManifold->getContactPoint(0), _originOffset);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// We harvest collision callbacks every few frames, which contributes the following effects:
|
||||
//
|
||||
// Unfortunately we have to unlock the simulation (above) before we try to lock the _entityTree
|
||||
// to avoid deadlock -- the _entityTree may try to lock its EntitySimulation (from which this
|
||||
// PhysicsEngine derives) when updating/adding/deleting entities so we need to wait for our own
|
||||
// lock on the tree before we re-lock ourselves.
|
||||
// (1) There is a maximum collision callback rate per pair: substep_rate / SUBSTEPS_PER_COLLIION_FRAME
|
||||
// (2) END/START cycles shorter than SUBSTEPS_PER_COLLIION_FRAME will be filtered out
|
||||
// (3) There is variable lag between when the contact actually starts and when it is reported,
|
||||
// up to SUBSTEPS_PER_COLLIION_FRAME * time_per_substep
|
||||
//
|
||||
// TODO: untangle these lock sequences.
|
||||
_entityTree->lockForWrite();
|
||||
lock();
|
||||
_dynamicsWorld->synchronizeMotionStates();
|
||||
unlock();
|
||||
_entityTree->unlock();
|
||||
const uint32_t SUBSTEPS_PER_COLLISION_FRAME = 2;
|
||||
if (_numSubsteps - _numContactFrames * SUBSTEPS_PER_COLLISION_FRAME < SUBSTEPS_PER_COLLISION_FRAME) {
|
||||
// we don't harvest collision callbacks every frame
|
||||
// this sets a maximum callback-per-contact rate
|
||||
// and also filters out END/START events that happen on shorter timescales
|
||||
return;
|
||||
}
|
||||
|
||||
++_numContactFrames;
|
||||
// scan known contacts and trigger events
|
||||
ContactMap::iterator contactItr = _contactMap.begin();
|
||||
while (contactItr != _contactMap.end()) {
|
||||
ObjectMotionState* A = static_cast<ObjectMotionState*>(contactItr->first._a);
|
||||
ObjectMotionState* B = static_cast<ObjectMotionState*>(contactItr->first._b);
|
||||
|
||||
// TODO: make triggering these events clean and efficient. The code at this context shouldn't
|
||||
// have to figure out what kind of object (entity, avatar, etc) these are in order to properly
|
||||
// emit a collision event.
|
||||
if (A && A->getType() == MOTION_STATE_TYPE_ENTITY) {
|
||||
EntityItemID idA = static_cast<EntityMotionState*>(A)->getEntity()->getEntityItemID();
|
||||
EntityItemID idB;
|
||||
if (B && B->getType() == MOTION_STATE_TYPE_ENTITY) {
|
||||
idB = static_cast<EntityMotionState*>(B)->getEntity()->getEntityItemID();
|
||||
}
|
||||
emit entityCollisionWithEntity(idA, idB, contactItr->second);
|
||||
} else if (B && B->getType() == MOTION_STATE_TYPE_ENTITY) {
|
||||
EntityItemID idA;
|
||||
EntityItemID idB = static_cast<EntityMotionState*>(B)->getEntity()->getEntityItemID();
|
||||
emit entityCollisionWithEntity(idA, idB, contactItr->second);
|
||||
}
|
||||
|
||||
// TODO: enable scripts to filter based on contact event type
|
||||
ContactEventType type = contactItr->second.computeType(_numContactFrames);
|
||||
if (type == CONTACT_EVENT_TYPE_END) {
|
||||
ContactMap::iterator iterToDelete = contactItr;
|
||||
++contactItr;
|
||||
_contactMap.erase(iterToDelete);
|
||||
} else {
|
||||
++contactItr;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Bullet collision flags are as follows:
|
||||
|
@ -259,7 +331,7 @@ void PhysicsEngine::addObject(const ShapeInfo& shapeInfo, btCollisionShape* shap
|
|||
body = new btRigidBody(mass, motionState, shape, inertia);
|
||||
body->setCollisionFlags(btCollisionObject::CF_KINEMATIC_OBJECT);
|
||||
body->updateInertiaTensor();
|
||||
motionState->_body = body;
|
||||
motionState->setRigidBody(body);
|
||||
motionState->addKinematicController();
|
||||
const float KINEMATIC_LINEAR_VELOCITY_THRESHOLD = 0.01f; // 1 cm/sec
|
||||
const float KINEMATIC_ANGULAR_VELOCITY_THRESHOLD = 0.01f; // ~1 deg/sec
|
||||
|
@ -271,7 +343,7 @@ void PhysicsEngine::addObject(const ShapeInfo& shapeInfo, btCollisionShape* shap
|
|||
shape->calculateLocalInertia(mass, inertia);
|
||||
body = new btRigidBody(mass, motionState, shape, inertia);
|
||||
body->updateInertiaTensor();
|
||||
motionState->_body = body;
|
||||
motionState->setRigidBody(body);
|
||||
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
|
||||
|
@ -285,7 +357,7 @@ void PhysicsEngine::addObject(const ShapeInfo& shapeInfo, btCollisionShape* shap
|
|||
body = new btRigidBody(mass, motionState, shape, inertia);
|
||||
body->setCollisionFlags(btCollisionObject::CF_STATIC_OBJECT);
|
||||
body->updateInertiaTensor();
|
||||
motionState->_body = body;
|
||||
motionState->setRigidBody(body);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
@ -298,7 +370,7 @@ void PhysicsEngine::addObject(const ShapeInfo& shapeInfo, btCollisionShape* shap
|
|||
|
||||
bool PhysicsEngine::removeObject(ObjectMotionState* motionState) {
|
||||
assert(motionState);
|
||||
btRigidBody* body = motionState->_body;
|
||||
btRigidBody* body = motionState->getRigidBody();
|
||||
if (body) {
|
||||
const btCollisionShape* shape = body->getCollisionShape();
|
||||
ShapeInfo shapeInfo;
|
||||
|
@ -306,8 +378,10 @@ bool PhysicsEngine::removeObject(ObjectMotionState* motionState) {
|
|||
_dynamicsWorld->removeRigidBody(body);
|
||||
_shapeManager.releaseShape(shapeInfo);
|
||||
delete body;
|
||||
motionState->_body = NULL;
|
||||
motionState->setRigidBody(NULL);
|
||||
motionState->removeKinematicController();
|
||||
|
||||
removeContacts(motionState);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
|
@ -349,7 +423,7 @@ void PhysicsEngine::updateObjectHard(btRigidBody* body, ObjectMotionState* motio
|
|||
}
|
||||
bool easyUpdate = flags & EASY_DIRTY_PHYSICS_FLAGS;
|
||||
if (easyUpdate) {
|
||||
motionState->updateObjectEasy(flags, _frameCount);
|
||||
motionState->updateObjectEasy(flags, _numSubsteps);
|
||||
}
|
||||
|
||||
// update the motion parameters
|
||||
|
|
|
@ -23,6 +23,7 @@ const float PHYSICS_ENGINE_FIXED_SUBSTEP = 1.0f / 60.0f;
|
|||
#include <EntitySimulation.h>
|
||||
|
||||
#include "BulletUtil.h"
|
||||
#include "ContactInfo.h"
|
||||
#include "EntityMotionState.h"
|
||||
#include "ShapeManager.h"
|
||||
#include "ThreadSafeDynamicsWorld.h"
|
||||
|
@ -31,10 +32,26 @@ const float HALF_SIMULATION_EXTENT = 512.0f; // meters
|
|||
|
||||
class ObjectMotionState;
|
||||
|
||||
// simple class for keeping track of contacts
|
||||
class ContactKey {
|
||||
public:
|
||||
ContactKey() = delete;
|
||||
ContactKey(void* a, void* b) : _a(a), _b(b) {}
|
||||
bool operator<(const ContactKey& other) const { return _a < other._a || (_a == other._a && _b < other._b); }
|
||||
bool operator==(const ContactKey& other) const { return _a == other._a && _b == other._b; }
|
||||
void* _a;
|
||||
void* _b;
|
||||
};
|
||||
|
||||
typedef std::map<ContactKey, ContactInfo> ContactMap;
|
||||
typedef std::pair<ContactKey, ContactInfo> ContactMapElement;
|
||||
|
||||
class PhysicsEngine : public EntitySimulation {
|
||||
public:
|
||||
static uint32_t getFrameCount();
|
||||
// TODO: find a good way to make this a non-static method
|
||||
static uint32_t getNumSubsteps();
|
||||
|
||||
PhysicsEngine() = delete; // prevent compiler from creating default ctor
|
||||
PhysicsEngine(const glm::vec3& offset);
|
||||
|
||||
~PhysicsEngine();
|
||||
|
@ -51,6 +68,8 @@ public:
|
|||
|
||||
void stepSimulation();
|
||||
|
||||
void computeCollisionEvents();
|
||||
|
||||
/// \param offset position of simulation origin in domain-frame
|
||||
void setOriginOffset(const glm::vec3& offset) { _originOffset = offset; }
|
||||
|
||||
|
@ -68,22 +87,19 @@ public:
|
|||
/// process queue of changed from external sources
|
||||
void relayIncomingChangesToSimulation();
|
||||
|
||||
/// \return duration of fixed simulation substep
|
||||
float getFixedSubStep() const;
|
||||
|
||||
protected:
|
||||
private:
|
||||
void removeContacts(ObjectMotionState* motionState);
|
||||
void updateObjectHard(btRigidBody* body, ObjectMotionState* motionState, uint32_t flags);
|
||||
void updateObjectEasy(btRigidBody* body, ObjectMotionState* motionState, uint32_t flags);
|
||||
|
||||
btClock _clock;
|
||||
btDefaultCollisionConfiguration* _collisionConfig;
|
||||
btCollisionDispatcher* _collisionDispatcher;
|
||||
btBroadphaseInterface* _broadphaseFilter;
|
||||
btSequentialImpulseConstraintSolver* _constraintSolver;
|
||||
ThreadSafeDynamicsWorld* _dynamicsWorld;
|
||||
btDefaultCollisionConfiguration* _collisionConfig = NULL;
|
||||
btCollisionDispatcher* _collisionDispatcher = NULL;
|
||||
btBroadphaseInterface* _broadphaseFilter = NULL;
|
||||
btSequentialImpulseConstraintSolver* _constraintSolver = NULL;
|
||||
ThreadSafeDynamicsWorld* _dynamicsWorld = NULL;
|
||||
ShapeManager _shapeManager;
|
||||
|
||||
private:
|
||||
glm::vec3 _originOffset;
|
||||
|
||||
// EntitySimulation stuff
|
||||
|
@ -91,7 +107,10 @@ private:
|
|||
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
|
||||
|
||||
EntityEditPacketSender* _entityPacketSender;
|
||||
EntityEditPacketSender* _entityPacketSender = NULL;
|
||||
|
||||
ContactMap _contactMap;
|
||||
uint32_t _numContactFrames = 0;
|
||||
};
|
||||
|
||||
#endif // hifi_PhysicsEngine_h
|
||||
|
|
|
@ -13,9 +13,9 @@
|
|||
#include "SimpleEntityKinematicController.h"
|
||||
|
||||
void SimpleEntityKinematicController:: stepForward() {
|
||||
uint32_t frame = PhysicsEngine::getFrameCount();
|
||||
float dt = (frame - _lastFrame) * PHYSICS_ENGINE_FIXED_SUBSTEP;
|
||||
uint32_t substep = PhysicsEngine::getNumSubsteps();
|
||||
float dt = (substep - _lastSubstep) * PHYSICS_ENGINE_FIXED_SUBSTEP;
|
||||
_entity->simulateSimpleKinematicMotion(dt);
|
||||
_lastFrame = frame;
|
||||
_lastSubstep = substep;
|
||||
}
|
||||
|
||||
|
|
|
@ -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