mirror of
https://github.com/JulianGro/overte.git
synced 2025-05-28 02:51:04 +02:00
Merge branch 'master' of https://github.com/highfidelity/hifi into protocol
This commit is contained in:
commit
33f6933544
56 changed files with 2626 additions and 842 deletions
4
cmake/externals/sixense/CMakeLists.txt
vendored
4
cmake/externals/sixense/CMakeLists.txt
vendored
|
@ -47,13 +47,13 @@ if (WIN32)
|
||||||
elseif(APPLE)
|
elseif(APPLE)
|
||||||
|
|
||||||
# FIXME need to account for different architectures
|
# FIXME need to account for different architectures
|
||||||
set(${EXTERNAL_NAME_UPPER}_LIBRARIES ${SOURCE_DIR}/lib/osx32/libopenvr_api.dylib CACHE TYPE INTERNAL)
|
set(${EXTERNAL_NAME_UPPER}_LIBRARIES ${SOURCE_DIR}/lib/osx_x64/release_dll/libsixense_x64.dylib CACHE TYPE INTERNAL)
|
||||||
add_paths_to_fixup_libs(${SOURCE_DIR}/bin/osx32)
|
add_paths_to_fixup_libs(${SOURCE_DIR}/bin/osx32)
|
||||||
|
|
||||||
elseif(NOT ANDROID)
|
elseif(NOT ANDROID)
|
||||||
|
|
||||||
# FIXME need to account for different architectures
|
# FIXME need to account for different architectures
|
||||||
set(${EXTERNAL_NAME_UPPER}_LIBRARIES ${SOURCE_DIR}/lib/linux32/libopenvr_api.so CACHE TYPE INTERNAL)
|
set(${EXTERNAL_NAME_UPPER}_LIBRARIES ${SOURCE_DIR}/lib/linux_x64/release/libsixense_x64.so CACHE TYPE INTERNAL)
|
||||||
add_paths_to_fixup_libs(${SOURCE_DIR}/bin/linux32)
|
add_paths_to_fixup_libs(${SOURCE_DIR}/bin/linux32)
|
||||||
|
|
||||||
endif()
|
endif()
|
||||||
|
|
|
@ -448,12 +448,31 @@ var toolBar = (function () {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (newPolyVoxButton === toolBar.clicked(clickedOverlay)) {
|
if (newPolyVoxButton === toolBar.clicked(clickedOverlay)) {
|
||||||
createNewEntity({
|
var polyVoxId = createNewEntity({
|
||||||
type: "PolyVox",
|
type: "PolyVox",
|
||||||
dimensions: { x: 10, y: 10, z: 10 },
|
dimensions: { x: 10, y: 10, z: 10 },
|
||||||
voxelVolumeSize: {x:16, y:16, z:16},
|
voxelVolumeSize: {x:16, y:16, z:16},
|
||||||
voxelSurfaceStyle: 1
|
voxelSurfaceStyle: 2
|
||||||
});
|
});
|
||||||
|
for (var x = 1; x <= 14; x++) {
|
||||||
|
Entities.setVoxel(polyVoxId, {x: x, y: 1, z: 1}, 255);
|
||||||
|
Entities.setVoxel(polyVoxId, {x: x, y: 14, z: 1}, 255);
|
||||||
|
Entities.setVoxel(polyVoxId, {x: x, y: 1, z: 14}, 255);
|
||||||
|
Entities.setVoxel(polyVoxId, {x: x, y: 14, z: 14}, 255);
|
||||||
|
}
|
||||||
|
for (var y = 2; y <= 13; y++) {
|
||||||
|
Entities.setVoxel(polyVoxId, {x: 1, y: y, z: 1}, 255);
|
||||||
|
Entities.setVoxel(polyVoxId, {x: 14, y: y, z: 1}, 255);
|
||||||
|
Entities.setVoxel(polyVoxId, {x: 1, y: y, z: 14}, 255);
|
||||||
|
Entities.setVoxel(polyVoxId, {x: 14, y: y, z: 14}, 255);
|
||||||
|
}
|
||||||
|
for (var z = 2; z <= 13; z++) {
|
||||||
|
Entities.setVoxel(polyVoxId, {x: 1, y: 1, z: z}, 255);
|
||||||
|
Entities.setVoxel(polyVoxId, {x: 14, y: 1, z: z}, 255);
|
||||||
|
Entities.setVoxel(polyVoxId, {x: 1, y: 14, z: z}, 255);
|
||||||
|
Entities.setVoxel(polyVoxId, {x: 14, y: 14, z: z}, 255);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
|
@ -10,7 +10,7 @@
|
||||||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||||
//
|
//
|
||||||
|
|
||||||
Overlay2D = function(properties, overlay) { // overlay is an optionnal variable
|
Overlay2D = function(properties, overlay) { // overlay is an optional variable
|
||||||
if (!(typeof(properties) === 'undefined')) {
|
if (!(typeof(properties) === 'undefined')) {
|
||||||
if(typeof(overlay) === 'undefined') {
|
if(typeof(overlay) === 'undefined') {
|
||||||
overlay = Overlays.addOverlay("image", properties);
|
overlay = Overlays.addOverlay("image", properties);
|
||||||
|
|
103
examples/toys/magBalls/ballController.js
Normal file
103
examples/toys/magBalls/ballController.js
Normal file
|
@ -0,0 +1,103 @@
|
||||||
|
Script.include("handController.js");
|
||||||
|
Script.include("highlighter.js");
|
||||||
|
|
||||||
|
BallController = function(side, magBalls) {
|
||||||
|
HandController.call(this, side);
|
||||||
|
this.magBalls = magBalls;
|
||||||
|
this.highlighter = new Highlighter();
|
||||||
|
this.highlighter.setSize(BALL_SIZE);
|
||||||
|
this.ghostEdges = {};
|
||||||
|
}
|
||||||
|
|
||||||
|
BallController.prototype = Object.create( HandController.prototype );
|
||||||
|
|
||||||
|
BallController.prototype.onUpdate = function(deltaTime) {
|
||||||
|
HandController.prototype.onUpdate.call(this, deltaTime);
|
||||||
|
|
||||||
|
if (!this.selected) {
|
||||||
|
// Find the highlight target and set it.
|
||||||
|
var target = this.magBalls.findNearestNode(this.tipPosition, BALL_SELECTION_RADIUS);
|
||||||
|
this.highlighter.highlight(target);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.highlighter.highlight(null);
|
||||||
|
Entities.editEntity(this.selected, { position: this.tipPosition });
|
||||||
|
var targetBalls = this.magBalls.findPotentialEdges(this.selected);
|
||||||
|
for (var ballId in targetBalls) {
|
||||||
|
if (!this.ghostEdges[ballId]) {
|
||||||
|
// create the ovleray
|
||||||
|
this.ghostEdges[ballId] = Overlays.addOverlay("line3d", {
|
||||||
|
start: this.magBalls.getNodePosition(ballId),
|
||||||
|
end: this.tipPosition,
|
||||||
|
color: COLORS.RED,
|
||||||
|
alpha: 1,
|
||||||
|
lineWidth: 5,
|
||||||
|
visible: true,
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
Overlays.editOverlay(this.ghostEdges[ballId], {
|
||||||
|
end: this.tipPosition,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for (var ballId in this.ghostEdges) {
|
||||||
|
if (!targetBalls[ballId]) {
|
||||||
|
Overlays.deleteOverlay(this.ghostEdges[ballId]);
|
||||||
|
delete this.ghostEdges[ballId];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
BallController.prototype.onClick = function() {
|
||||||
|
this.selected = this.magBalls.grabBall(this.tipPosition, BALL_SELECTION_RADIUS);
|
||||||
|
this.highlighter.highlight(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
BallController.prototype.onRelease = function() {
|
||||||
|
this.clearGhostEdges();
|
||||||
|
this.magBalls.releaseBall(this.selected);
|
||||||
|
this.selected = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
BallController.prototype.clearGhostEdges = function() {
|
||||||
|
for(var ballId in this.ghostEdges) {
|
||||||
|
Overlays.deleteOverlay(this.ghostEdges[ballId]);
|
||||||
|
delete this.ghostEdges[ballId];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
BallController.prototype.onCleanup = function() {
|
||||||
|
HandController.prototype.onCleanup.call(this);
|
||||||
|
this.clearGhostEdges();
|
||||||
|
}
|
||||||
|
|
||||||
|
BallController.prototype.onAltClick = function() {
|
||||||
|
return;
|
||||||
|
var target = this.magBalls.findNearestNode(this.tipPosition, BALL_SELECTION_RADIUS);
|
||||||
|
if (!target) {
|
||||||
|
logDebug(target);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// FIXME move to delete shape
|
||||||
|
var toDelete = {};
|
||||||
|
var deleteQueue = [ target ];
|
||||||
|
while (deleteQueue.length) {
|
||||||
|
var curNode = deleteQueue.shift();
|
||||||
|
if (toDelete[curNode]) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
toDelete[curNode] = true;
|
||||||
|
for (var nodeId in this.magBalls.getConnectedNodes(curNode)) {
|
||||||
|
deleteQueue.push(nodeId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for (var nodeId in toDelete) {
|
||||||
|
this.magBalls.destroyNode(nodeId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
BallController.prototype.onAltRelease = function() {
|
||||||
|
}
|
140
examples/toys/magBalls/constants.js
Normal file
140
examples/toys/magBalls/constants.js
Normal file
|
@ -0,0 +1,140 @@
|
||||||
|
//
|
||||||
|
// Created by Bradley Austin Davis on 2015/08/27
|
||||||
|
// 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
|
||||||
|
//
|
||||||
|
|
||||||
|
HIFI_PUBLIC_BUCKET = "http://s3.amazonaws.com/hifi-public/";
|
||||||
|
STICK_URL = HIFI_PUBLIC_BUCKET + "models/props/geo_stick.fbx";
|
||||||
|
|
||||||
|
// FIXME make this editable through some script UI, so the user can customize the size of the structure built
|
||||||
|
SCALE = 0.5;
|
||||||
|
BALL_SIZE = 0.08 * SCALE;
|
||||||
|
STICK_LENGTH = 0.24 * SCALE;
|
||||||
|
|
||||||
|
DEBUG_MAGSTICKS = true;
|
||||||
|
|
||||||
|
CUSTOM_DATA_NAME = "magBalls";
|
||||||
|
BALL_NAME = "MagBall";
|
||||||
|
EDGE_NAME = "MagStick";
|
||||||
|
|
||||||
|
ZERO_VECTOR = { x: 0, y: 0, z: 0 };
|
||||||
|
|
||||||
|
COLORS = {
|
||||||
|
WHITE: {
|
||||||
|
red: 255,
|
||||||
|
green: 255,
|
||||||
|
blue: 255,
|
||||||
|
},
|
||||||
|
BLACK: {
|
||||||
|
red: 0,
|
||||||
|
green: 0,
|
||||||
|
blue: 0,
|
||||||
|
},
|
||||||
|
GREY: {
|
||||||
|
red: 128,
|
||||||
|
green: 128,
|
||||||
|
blue: 128,
|
||||||
|
},
|
||||||
|
RED: {
|
||||||
|
red: 255,
|
||||||
|
green: 0,
|
||||||
|
blue: 0
|
||||||
|
},
|
||||||
|
BLUE: {
|
||||||
|
red: 0,
|
||||||
|
green: 0,
|
||||||
|
blue: 255
|
||||||
|
},
|
||||||
|
GREEN: {
|
||||||
|
red: 0,
|
||||||
|
green: 255,
|
||||||
|
blue: 0
|
||||||
|
},
|
||||||
|
CYAN: {
|
||||||
|
red: 0,
|
||||||
|
green: 255,
|
||||||
|
blue: 255
|
||||||
|
},
|
||||||
|
YELLOW: {
|
||||||
|
red: 255,
|
||||||
|
green: 255,
|
||||||
|
blue: 0
|
||||||
|
},
|
||||||
|
MAGENTA: {
|
||||||
|
red: 255,
|
||||||
|
green: 0,
|
||||||
|
blue: 255
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
BALL_RADIUS = BALL_SIZE / 2.0;
|
||||||
|
|
||||||
|
BALL_SELECTION_RADIUS = BALL_RADIUS * 1.5;
|
||||||
|
|
||||||
|
BALL_DIMENSIONS = {
|
||||||
|
x: BALL_SIZE,
|
||||||
|
y: BALL_SIZE,
|
||||||
|
z: BALL_SIZE
|
||||||
|
};
|
||||||
|
|
||||||
|
BALL_COLOR = {
|
||||||
|
red: 128,
|
||||||
|
green: 128,
|
||||||
|
blue: 128
|
||||||
|
};
|
||||||
|
|
||||||
|
STICK_DIMENSIONS = {
|
||||||
|
x: STICK_LENGTH / 6,
|
||||||
|
y: STICK_LENGTH / 6,
|
||||||
|
z: STICK_LENGTH
|
||||||
|
};
|
||||||
|
|
||||||
|
BALL_DISTANCE = STICK_LENGTH + BALL_SIZE;
|
||||||
|
|
||||||
|
BALL_PROTOTYPE = {
|
||||||
|
type: "Sphere",
|
||||||
|
name: BALL_NAME,
|
||||||
|
dimensions: BALL_DIMENSIONS,
|
||||||
|
color: BALL_COLOR,
|
||||||
|
ignoreCollisions: true,
|
||||||
|
collisionsWillMove: false
|
||||||
|
};
|
||||||
|
|
||||||
|
// 2 millimeters
|
||||||
|
BALL_EPSILON = (.002) / BALL_DISTANCE;
|
||||||
|
|
||||||
|
LINE_DIMENSIONS = {
|
||||||
|
x: 5,
|
||||||
|
y: 5,
|
||||||
|
z: 5
|
||||||
|
}
|
||||||
|
|
||||||
|
LINE_PROTOTYPE = {
|
||||||
|
type: "Line",
|
||||||
|
name: EDGE_NAME,
|
||||||
|
color: COLORS.CYAN,
|
||||||
|
dimensions: LINE_DIMENSIONS,
|
||||||
|
lineWidth: 5,
|
||||||
|
visible: true,
|
||||||
|
ignoreCollisions: true,
|
||||||
|
collisionsWillMove: false,
|
||||||
|
}
|
||||||
|
|
||||||
|
EDGE_PROTOTYPE = LINE_PROTOTYPE;
|
||||||
|
|
||||||
|
// var EDGE_PROTOTYPE = {
|
||||||
|
// type: "Sphere",
|
||||||
|
// name: EDGE_NAME,
|
||||||
|
// color: { red: 0, green: 255, blue: 255 },
|
||||||
|
// //dimensions: STICK_DIMENSIONS,
|
||||||
|
// dimensions: { x: 0.02, y: 0.02, z: 0.02 },
|
||||||
|
// rotation: rotation,
|
||||||
|
// visible: true,
|
||||||
|
// ignoreCollisions: true,
|
||||||
|
// collisionsWillMove: false
|
||||||
|
// }
|
||||||
|
|
||||||
|
|
95
examples/toys/magBalls/debugUtils.js
Normal file
95
examples/toys/magBalls/debugUtils.js
Normal file
|
@ -0,0 +1,95 @@
|
||||||
|
findMatchingNode = function(position, nodePositions) {
|
||||||
|
for (var nodeId in nodePositions) {
|
||||||
|
var nodePos = nodePositions[nodeId];
|
||||||
|
var distance = Vec3.distance(position, nodePos);
|
||||||
|
if (distance < 0.03) {
|
||||||
|
return nodeId;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
repairConnections = function() {
|
||||||
|
var ids = Entities.findEntities(MyAvatar.position, 50);
|
||||||
|
|
||||||
|
// Find all the balls and record their positions
|
||||||
|
var nodePositions = {};
|
||||||
|
for (var i in ids) {
|
||||||
|
var id = ids[i];
|
||||||
|
var properties = Entities.getEntityProperties(id);
|
||||||
|
if (properties.name == BALL_NAME) {
|
||||||
|
nodePositions[id] = properties.position;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Now check all the edges to see if they're valid (point to balls)
|
||||||
|
// and ensure that the balls point back to them
|
||||||
|
var ballsToEdges = {};
|
||||||
|
for (var i in ids) {
|
||||||
|
var id = ids[i];
|
||||||
|
var properties = Entities.getEntityProperties(id);
|
||||||
|
if (properties.name == EDGE_NAME) {
|
||||||
|
var startPos = properties.position;
|
||||||
|
var endPos = Vec3.sum(startPos, properties.linePoints[1]);
|
||||||
|
var magBallData = getMagBallsData(id);
|
||||||
|
var update = false;
|
||||||
|
if (!magBallData.start) {
|
||||||
|
var startNode = findMatchingNode(startPos, nodePositions);
|
||||||
|
if (startNode) {
|
||||||
|
logDebug("Found start node " + startNode)
|
||||||
|
magBallData.start = startNode;
|
||||||
|
update = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!magBallData.end) {
|
||||||
|
var endNode = findMatchingNode(endPos, nodePositions);
|
||||||
|
if (endNode) {
|
||||||
|
logDebug("Found end node " + endNode)
|
||||||
|
magBallData.end = endNode;
|
||||||
|
update = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!magBallData.start || !magBallData.end) {
|
||||||
|
logDebug("Didn't find both ends");
|
||||||
|
Entities.deleteEntity(id);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (!ballsToEdges[magBallData.start]) {
|
||||||
|
ballsToEdges[magBallData.start] = [ id ];
|
||||||
|
} else {
|
||||||
|
ballsToEdges[magBallData.start].push(id);
|
||||||
|
}
|
||||||
|
if (!ballsToEdges[magBallData.end]) {
|
||||||
|
ballsToEdges[magBallData.end] = [ id ];
|
||||||
|
} else {
|
||||||
|
ballsToEdges[magBallData.end].push(id);
|
||||||
|
}
|
||||||
|
if (update) {
|
||||||
|
logDebug("Updating incomplete edge " + id);
|
||||||
|
magBallData.length = BALL_DISTANCE;
|
||||||
|
setMagBallsData(id, magBallData);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for (var nodeId in ballsToEdges) {
|
||||||
|
var magBallData = getMagBallsData(nodeId);
|
||||||
|
var edges = magBallData.edges || [];
|
||||||
|
var edgeHash = {};
|
||||||
|
for (var i in edges) {
|
||||||
|
edgeHash[edges[i]] = true;
|
||||||
|
}
|
||||||
|
var update = false;
|
||||||
|
for (var i in ballsToEdges[nodeId]) {
|
||||||
|
var edgeId = ballsToEdges[nodeId][i];
|
||||||
|
if (!edgeHash[edgeId]) {
|
||||||
|
update = true;
|
||||||
|
edgeHash[edgeId] = true;
|
||||||
|
edges.push(edgeId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (update) {
|
||||||
|
logDebug("Fixing node with missing edge data");
|
||||||
|
magBallData.edges = edges;
|
||||||
|
setMagBallsData(nodeId, magBallData);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
45
examples/toys/magBalls/edgeSpring.js
Normal file
45
examples/toys/magBalls/edgeSpring.js
Normal file
|
@ -0,0 +1,45 @@
|
||||||
|
|
||||||
|
EdgeSpring = function(edgeId, graph) {
|
||||||
|
this.edgeId = edgeId;
|
||||||
|
this.graph = graph;
|
||||||
|
|
||||||
|
var magBallsData = getMagBallsData(this.edgeId);
|
||||||
|
this.start = magBallsData.start;
|
||||||
|
this.end = magBallsData.end;
|
||||||
|
this.desiredLength = magBallsData.length || BALL_DISTANCE;
|
||||||
|
}
|
||||||
|
|
||||||
|
EdgeSpring.prototype.adjust = function(results) {
|
||||||
|
var startPos = this.getAdjustedPosition(this.start, results);
|
||||||
|
var endPos = this.getAdjustedPosition(this.end, results);
|
||||||
|
var vector = Vec3.subtract(endPos, startPos);
|
||||||
|
var length = Vec3.length(vector);
|
||||||
|
var variance = this.getVariance(length);
|
||||||
|
|
||||||
|
if (Math.abs(variance) <= this.MAX_VARIANCE) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// adjust by halves until we fall below our variance
|
||||||
|
var adjustmentVector = Vec3.multiply(variance / 4, vector);
|
||||||
|
|
||||||
|
var newStartPos = Vec3.sum(Vec3.multiply(-1, adjustmentVector), startPos);
|
||||||
|
var newEndPos = Vec3.sum(adjustmentVector, endPos);
|
||||||
|
results[this.start] = newStartPos;
|
||||||
|
results[this.end] = newEndPos;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
EdgeSpring.prototype.MAX_VARIANCE = 0.005;
|
||||||
|
|
||||||
|
EdgeSpring.prototype.getAdjustedPosition = function(nodeId, results) {
|
||||||
|
if (results[nodeId]) {
|
||||||
|
return results[nodeId];
|
||||||
|
}
|
||||||
|
return this.graph.getNodePosition(nodeId);
|
||||||
|
}
|
||||||
|
|
||||||
|
EdgeSpring.prototype.getVariance = function(length) {
|
||||||
|
var difference = this.desiredLength - length;
|
||||||
|
return difference / this.desiredLength;
|
||||||
|
}
|
281
examples/toys/magBalls/graph.js
Normal file
281
examples/toys/magBalls/graph.js
Normal file
|
@ -0,0 +1,281 @@
|
||||||
|
//
|
||||||
|
// Created by Bradley Austin Davis on 2015/08/29
|
||||||
|
// 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
|
||||||
|
//
|
||||||
|
|
||||||
|
// A collection of nodes and edges connecting them.
|
||||||
|
Graph = function() {
|
||||||
|
/* Structure of nodes tree
|
||||||
|
this.nodes: {
|
||||||
|
nodeId1: {
|
||||||
|
edgeId1: true
|
||||||
|
}
|
||||||
|
nodeId2: {
|
||||||
|
edgeId1: true
|
||||||
|
},
|
||||||
|
// Nodes can many edges
|
||||||
|
nodeId3: {
|
||||||
|
edgeId2: true
|
||||||
|
edgeId3: true
|
||||||
|
edgeId4: true
|
||||||
|
edgeId5: true
|
||||||
|
},
|
||||||
|
// Nodes can have 0 edges
|
||||||
|
nodeId5: {
|
||||||
|
},
|
||||||
|
...
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
this.nodes = {};
|
||||||
|
/* Structure of edge tree
|
||||||
|
this.edges: {
|
||||||
|
edgeId1: {
|
||||||
|
// Every edge should have exactly two
|
||||||
|
nodeId1: true
|
||||||
|
nodeId2: true
|
||||||
|
},
|
||||||
|
edgeId2: {
|
||||||
|
nodeId3: true
|
||||||
|
nodeId4: true
|
||||||
|
},
|
||||||
|
...
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
this.edges = {};
|
||||||
|
}
|
||||||
|
|
||||||
|
Graph.prototype.createNodeEntity = function(properties) {
|
||||||
|
throw "Unimplemented";
|
||||||
|
}
|
||||||
|
|
||||||
|
Graph.prototype.createNode = function(properties) {
|
||||||
|
var nodeId = this.createNodeEntity(properties);
|
||||||
|
this.nodes[nodeId] = {};
|
||||||
|
this.validate();
|
||||||
|
return nodeId;
|
||||||
|
}
|
||||||
|
|
||||||
|
Graph.prototype.createEdgeEntity = function(nodeA, nodeB) {
|
||||||
|
throw "Unimplemented";
|
||||||
|
}
|
||||||
|
|
||||||
|
Graph.prototype.createEdge = function(nodeA, nodeB) {
|
||||||
|
if (nodeA == nodeB) {
|
||||||
|
throw "Error: self connection not supported";
|
||||||
|
}
|
||||||
|
var newEdgeId = this.createEdgeEntity(nodeA, nodeB);
|
||||||
|
|
||||||
|
// Create the bidirectional linkage
|
||||||
|
this.edges[newEdgeId] = {};
|
||||||
|
this.edges[newEdgeId][nodeA] = true;
|
||||||
|
this.edges[newEdgeId][nodeB] = true;
|
||||||
|
this.nodes[nodeA][newEdgeId] = true;
|
||||||
|
this.nodes[nodeB][newEdgeId] = true;
|
||||||
|
|
||||||
|
this.validate();
|
||||||
|
}
|
||||||
|
|
||||||
|
Graph.prototype.getEdges = function(nodeId) {
|
||||||
|
var edges = this.nodes[nodeId];
|
||||||
|
var result = {};
|
||||||
|
for (var edgeId in edges) {
|
||||||
|
for (var otherNodeId in this.edges[edgeId]) {
|
||||||
|
if (otherNodeId != nodeId) {
|
||||||
|
result[edgeId] = otherNodeId;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
Graph.prototype.getConnectedNodes = function(nodeId) {
|
||||||
|
var edges = this.getEdges(nodeId);
|
||||||
|
var result = {};
|
||||||
|
for (var edgeId in edges) {
|
||||||
|
var otherNodeId = edges[edgeId];
|
||||||
|
result[otherNodeId] = edgeId;
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
Graph.prototype.getEdgeLength = function(edgeId) {
|
||||||
|
var nodesInEdge = Object.keys(this.edges[edgeId]);
|
||||||
|
return this.getNodeDistance(nodesInEdge[0], nodesInEdge[1]);
|
||||||
|
}
|
||||||
|
|
||||||
|
Graph.prototype.getNodeDistance = function(a, b) {
|
||||||
|
var apos = this.getNodePosition(a);
|
||||||
|
var bpos = this.getNodePosition(b);
|
||||||
|
return Vec3.distance(apos, bpos);
|
||||||
|
}
|
||||||
|
|
||||||
|
Graph.prototype.getNodePosition = function(node) {
|
||||||
|
var properties = Entities.getEntityProperties(node);
|
||||||
|
return properties.position;
|
||||||
|
}
|
||||||
|
|
||||||
|
Graph.prototype.breakEdges = function(nodeId) {
|
||||||
|
for (var edgeId in this.nodes[nodeId]) {
|
||||||
|
this.destroyEdge(edgeId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Graph.prototype.findNearestNode = function(position, maxDist) {
|
||||||
|
var resultId = null;
|
||||||
|
var resultDist = 0;
|
||||||
|
for (var nodeId in this.nodes) {
|
||||||
|
var nodePosition = this.getNodePosition(nodeId);
|
||||||
|
var curDist = Vec3.distance(nodePosition, position);
|
||||||
|
if (!maxDist || curDist <= maxDist) {
|
||||||
|
if (!resultId || curDist < resultDist) {
|
||||||
|
resultId = nodeId;
|
||||||
|
resultDist = curDist;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return resultId;
|
||||||
|
}
|
||||||
|
|
||||||
|
Graph.prototype.findMatchingNodes = function(selector) {
|
||||||
|
var result = {};
|
||||||
|
for (var nodeId in this.nodes) {
|
||||||
|
if (selector(nodeId)) {
|
||||||
|
result[nodeId] = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
Graph.prototype.destroyEdge = function(edgeId) {
|
||||||
|
logDebug("Deleting edge " + edgeId);
|
||||||
|
for (var nodeId in this.edges[edgeId]) {
|
||||||
|
delete this.nodes[nodeId][edgeId];
|
||||||
|
}
|
||||||
|
delete this.edges[edgeId];
|
||||||
|
Entities.deleteEntity(edgeId);
|
||||||
|
this.validate();
|
||||||
|
}
|
||||||
|
|
||||||
|
Graph.prototype.destroyNode = function(nodeId) {
|
||||||
|
logDebug("Deleting node " + nodeId);
|
||||||
|
this.breakEdges(nodeId);
|
||||||
|
delete this.nodes[nodeId];
|
||||||
|
Entities.deleteEntity(nodeId);
|
||||||
|
this.validate();
|
||||||
|
}
|
||||||
|
|
||||||
|
Graph.prototype.deleteAll = function() {
|
||||||
|
var nodeIds = Object.keys(this.nodes);
|
||||||
|
for (var i in nodeIds) {
|
||||||
|
var nodeId = nodeIds[i];
|
||||||
|
this.destroyNode(nodeId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Graph.prototype.areConnected = function(nodeIdA, nodeIdB) {
|
||||||
|
for (var edgeId in this.nodes[nodeIdA]) {
|
||||||
|
if (this.nodes[nodeIdB][edgeId]) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
forEachValue = function(val, operation) {
|
||||||
|
if( typeof val === 'string' ) {
|
||||||
|
operation(val);
|
||||||
|
} else if (typeof val === 'object') {
|
||||||
|
if (val.constructor === Array) {
|
||||||
|
for (var i in val) {
|
||||||
|
operation(val[i]);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
for (var v in val) {
|
||||||
|
operation(v);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Graph.prototype.findShortestPath = function(start, end, options) {
|
||||||
|
var queue = [ start ];
|
||||||
|
var prev = {};
|
||||||
|
if (options && options.exclude) {
|
||||||
|
forEachValue(options.exclude, function(value) {
|
||||||
|
prev[value] = value;
|
||||||
|
});
|
||||||
|
logDebug("exclude " + prev);
|
||||||
|
}
|
||||||
|
var found = false;
|
||||||
|
while (!found && Object.keys(queue).length) {
|
||||||
|
var current = queue.shift();
|
||||||
|
for (var ballId in this.getConnectedNodes(current)) {
|
||||||
|
if (prev[ballId]) {
|
||||||
|
// already visited node
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
// record optimal path
|
||||||
|
prev[ballId] = current;
|
||||||
|
if (ballId == end) {
|
||||||
|
found = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
queue.push(ballId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!found) {
|
||||||
|
logDebug("Exhausted search");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var result = [ end ];
|
||||||
|
while (result[0] != start) {
|
||||||
|
result.unshift(prev[result[0]]);
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
Graph.prototype.validate = function() {
|
||||||
|
var error = false;
|
||||||
|
for (nodeId in this.nodes) {
|
||||||
|
for (edgeId in this.nodes[nodeId]) {
|
||||||
|
var edge = this.edges[edgeId];
|
||||||
|
if (!edge) {
|
||||||
|
logError("Error: node " + nodeId + " refers to unknown edge " + edgeId);
|
||||||
|
error = true;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (!edge[nodeId]) {
|
||||||
|
logError("Error: node " + nodeId + " refers to edge " + edgeId + " but not vice versa");
|
||||||
|
error = true;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (edgeId in this.edges) {
|
||||||
|
for (nodeId in this.edges[edgeId]) {
|
||||||
|
var node = this.nodes[nodeId];
|
||||||
|
if (!node) {
|
||||||
|
logError("Error: edge " + edgeId + " refers to unknown node " + nodeId);
|
||||||
|
error = true;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (!node[edgeId]) {
|
||||||
|
logError("Error: edge " + edgeId + " refers to node " + nodeId + " but not vice versa");
|
||||||
|
error = true;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (error) {
|
||||||
|
logDebug(JSON.stringify({ edges: this.edges, balls: this.nodes }, null, 2));
|
||||||
|
}
|
||||||
|
return error;
|
||||||
|
}
|
127
examples/toys/magBalls/handController.js
Normal file
127
examples/toys/magBalls/handController.js
Normal file
|
@ -0,0 +1,127 @@
|
||||||
|
//
|
||||||
|
// Created by Bradley Austin Davis on 2015/08/29
|
||||||
|
// 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
|
||||||
|
//
|
||||||
|
LEFT_CONTROLLER = 0;
|
||||||
|
RIGHT_CONTROLLER = 1;
|
||||||
|
|
||||||
|
// FIXME add a customizable wand model and a mechanism to switch between wands
|
||||||
|
HandController = function(side) {
|
||||||
|
this.side = side;
|
||||||
|
this.palm = 2 * side;
|
||||||
|
this.tip = 2 * side + 1;
|
||||||
|
this.action = findAction(side ? "ACTION2" : "ACTION1");
|
||||||
|
this.altAction = findAction(side ? "ACTION1" : "ACTION2");
|
||||||
|
this.active = false;
|
||||||
|
this.tipScale = 1.4;
|
||||||
|
this.pointer = Overlays.addOverlay("sphere", {
|
||||||
|
position: ZERO_VECTOR,
|
||||||
|
size: 0.01,
|
||||||
|
color: COLORS.YELLOW,
|
||||||
|
alpha: 1.0,
|
||||||
|
solid: true,
|
||||||
|
visible: false,
|
||||||
|
});
|
||||||
|
|
||||||
|
// Connect to desired events
|
||||||
|
var _this = this;
|
||||||
|
Controller.actionEvent.connect(function(action, state) {
|
||||||
|
_this.onActionEvent(action, state);
|
||||||
|
});
|
||||||
|
|
||||||
|
Script.update.connect(function(deltaTime) {
|
||||||
|
_this.onUpdate(deltaTime);
|
||||||
|
});
|
||||||
|
|
||||||
|
Script.scriptEnding.connect(function() {
|
||||||
|
_this.onCleanup();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
HandController.prototype.onActionEvent = function(action, state) {
|
||||||
|
var spatialControlCount = Controller.getNumberOfSpatialControls();
|
||||||
|
// If only 2 spacial controls, then we only have one controller active, so use either button
|
||||||
|
// otherwise, only use the specified action
|
||||||
|
|
||||||
|
if (action == this.action) {
|
||||||
|
if (state) {
|
||||||
|
this.onClick();
|
||||||
|
} else {
|
||||||
|
this.onRelease();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (action == this.altAction) {
|
||||||
|
if (state) {
|
||||||
|
this.onAltClick();
|
||||||
|
} else {
|
||||||
|
this.onAltRelease();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
HandController.prototype.setActive = function(active) {
|
||||||
|
if (active == this.active) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
logDebug("Hand controller changing active state: " + active);
|
||||||
|
this.active = active;
|
||||||
|
Overlays.editOverlay(this.pointer, {
|
||||||
|
visible: this.active
|
||||||
|
});
|
||||||
|
Entities.editEntity(this.wand, {
|
||||||
|
visible: this.active
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
HandController.prototype.updateControllerState = function() {
|
||||||
|
// FIXME this returns data if either the left or right controller is not on the base
|
||||||
|
this.palmPos = Controller.getSpatialControlPosition(this.palm);
|
||||||
|
var tipPos = Controller.getSpatialControlPosition(this.tip);
|
||||||
|
this.tipPosition = scaleLine(this.palmPos, tipPos, this.tipScale);
|
||||||
|
// When on the base, hydras report a position of 0
|
||||||
|
this.setActive(Vec3.length(this.palmPos) > 0.001);
|
||||||
|
|
||||||
|
//logDebug(Controller.getTriggerValue(0) + " " + Controller.getTriggerValue(1));
|
||||||
|
|
||||||
|
//if (this.active) {
|
||||||
|
// logDebug("#ctrls " + Controller.getNumberOfSpatialControls() + " Side: " + this.side + " Palm: " + this.palm + " " + vec3toStr(this.palmPos))
|
||||||
|
//}
|
||||||
|
}
|
||||||
|
|
||||||
|
HandController.prototype.onCleanup = function() {
|
||||||
|
Overlays.deleteOverlay(this.pointer);
|
||||||
|
}
|
||||||
|
|
||||||
|
HandController.prototype.onUpdate = function(deltaTime) {
|
||||||
|
this.updateControllerState();
|
||||||
|
if (this.active) {
|
||||||
|
Overlays.editOverlay(this.pointer, {
|
||||||
|
position: this.tipPosition
|
||||||
|
});
|
||||||
|
Entities.editEntity(this.wand, {
|
||||||
|
position: this.tipPosition
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
HandController.prototype.onClick = function() {
|
||||||
|
logDebug("Base hand controller does nothing on click");
|
||||||
|
}
|
||||||
|
|
||||||
|
HandController.prototype.onRelease = function() {
|
||||||
|
logDebug("Base hand controller does nothing on release");
|
||||||
|
}
|
||||||
|
|
||||||
|
HandController.prototype.onAltClick = function() {
|
||||||
|
logDebug("Base hand controller does nothing on alt click");
|
||||||
|
}
|
||||||
|
|
||||||
|
HandController.prototype.onAltRelease = function() {
|
||||||
|
logDebug("Base hand controller does nothing on alt click");
|
||||||
|
}
|
||||||
|
|
||||||
|
|
70
examples/toys/magBalls/highlighter.js
Normal file
70
examples/toys/magBalls/highlighter.js
Normal file
|
@ -0,0 +1,70 @@
|
||||||
|
//
|
||||||
|
// Created by Bradley Austin Davis on 2015/08/29
|
||||||
|
// 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
|
||||||
|
//
|
||||||
|
var SELECTION_OVERLAY = {
|
||||||
|
position: {
|
||||||
|
x: 0,
|
||||||
|
y: 0,
|
||||||
|
z: 0
|
||||||
|
},
|
||||||
|
color: {
|
||||||
|
red: 255,
|
||||||
|
green: 255,
|
||||||
|
blue: 0
|
||||||
|
},
|
||||||
|
alpha: 1,
|
||||||
|
size: 1.0,
|
||||||
|
solid: false,
|
||||||
|
//colorPulse: 1.0,
|
||||||
|
//pulseMin: 0.5,
|
||||||
|
//pulseMax: 1.0,
|
||||||
|
visible: false,
|
||||||
|
lineWidth: 1.0,
|
||||||
|
borderSize: 1.4,
|
||||||
|
};
|
||||||
|
|
||||||
|
Highlighter = function() {
|
||||||
|
this.highlightCube = Overlays.addOverlay("cube", this.SELECTION_OVERLAY);
|
||||||
|
this.hightlighted = null;
|
||||||
|
var _this = this;
|
||||||
|
Script.scriptEnding.connect(function() {
|
||||||
|
_this.onCleanup();
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
Highlighter.prototype.onCleanup = function() {
|
||||||
|
Overlays.deleteOverlay(this.highlightCube);
|
||||||
|
}
|
||||||
|
|
||||||
|
Highlighter.prototype.highlight = function(entityId) {
|
||||||
|
if (entityId != this.hightlighted) {
|
||||||
|
this.hightlighted = entityId;
|
||||||
|
this.updateHighlight();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Highlighter.prototype.setSize = function(newSize) {
|
||||||
|
Overlays.editOverlay(this.highlightCube, {
|
||||||
|
size: newSize
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
Highlighter.prototype.updateHighlight = function() {
|
||||||
|
if (this.hightlighted) {
|
||||||
|
var properties = Entities.getEntityProperties(this.hightlighted);
|
||||||
|
// logDebug("Making highlight " + this.highlightCube + " visible @ " + vec3toStr(properties.position));
|
||||||
|
Overlays.editOverlay(this.highlightCube, {
|
||||||
|
position: properties.position,
|
||||||
|
visible: true
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
// logDebug("Making highlight invisible");
|
||||||
|
Overlays.editOverlay(this.highlightCube, {
|
||||||
|
visible: false
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
293
examples/toys/magBalls/magBalls.js
Normal file
293
examples/toys/magBalls/magBalls.js
Normal file
|
@ -0,0 +1,293 @@
|
||||||
|
//
|
||||||
|
// Created by Bradley Austin Davis on 2015/08/29
|
||||||
|
// 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
|
||||||
|
//
|
||||||
|
|
||||||
|
var UPDATE_INTERVAL = 0.1;
|
||||||
|
|
||||||
|
Script.include("graph.js");
|
||||||
|
Script.include("edgeSpring.js");
|
||||||
|
|
||||||
|
// A collection of balls and edges connecting them.
|
||||||
|
MagBalls = function() {
|
||||||
|
Graph.call(this);
|
||||||
|
|
||||||
|
this.MAX_ADJUST_ITERATIONS = 100;
|
||||||
|
this.lastUpdateAge = 0;
|
||||||
|
this.stable = false;
|
||||||
|
this.adjustIterations = 0;
|
||||||
|
this.selectedNodes = {};
|
||||||
|
this.edgeObjects = {};
|
||||||
|
|
||||||
|
this.refresh();
|
||||||
|
|
||||||
|
var _this = this;
|
||||||
|
Script.update.connect(function(deltaTime) {
|
||||||
|
_this.onUpdate(deltaTime);
|
||||||
|
});
|
||||||
|
|
||||||
|
Script.scriptEnding.connect(function() {
|
||||||
|
_this.onCleanup();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
MagBalls.prototype = Object.create( Graph.prototype );
|
||||||
|
|
||||||
|
MagBalls.prototype.onUpdate = function(deltaTime) {
|
||||||
|
this.lastUpdateAge += deltaTime;
|
||||||
|
if (this.lastUpdateAge > UPDATE_INTERVAL) {
|
||||||
|
this.lastUpdateAge = 0;
|
||||||
|
if (!this.stable) {
|
||||||
|
this.adjustIterations += 1;
|
||||||
|
// logDebug("Update");
|
||||||
|
var adjusted = false;
|
||||||
|
var nodeAdjustResults = {};
|
||||||
|
var fixupEdges = {};
|
||||||
|
|
||||||
|
for(var edgeId in this.edges) {
|
||||||
|
adjusted |= this.edgeObjects[edgeId].adjust(nodeAdjustResults);
|
||||||
|
}
|
||||||
|
for (var nodeId in nodeAdjustResults) {
|
||||||
|
var curPos = this.getNodePosition(nodeId);
|
||||||
|
var newPos = nodeAdjustResults[nodeId];
|
||||||
|
var distance = Vec3.distance(curPos, newPos);
|
||||||
|
for (var edgeId in this.nodes[nodeId]) {
|
||||||
|
fixupEdges[edgeId] = true;
|
||||||
|
}
|
||||||
|
// logDebug("Moving node Id " + nodeId + " " + (distance * 1000).toFixed(3) + " mm");
|
||||||
|
Entities.editEntity(nodeId, { position: newPos, color: COLORS.RED });
|
||||||
|
}
|
||||||
|
|
||||||
|
for (var edgeId in fixupEdges) {
|
||||||
|
this.fixupEdge(edgeId);
|
||||||
|
}
|
||||||
|
|
||||||
|
Script.setTimeout(function(){
|
||||||
|
for (var nodeId in nodeAdjustResults) {
|
||||||
|
Entities.editEntity(nodeId, { color: BALL_COLOR });
|
||||||
|
}
|
||||||
|
}, ((UPDATE_INTERVAL * 1000) / 2));
|
||||||
|
|
||||||
|
if (!adjusted || this.adjustIterations > this.MAX_ADJUST_ITERATIONS) {
|
||||||
|
this.adjustIterations = 0;
|
||||||
|
this.stable = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
MagBalls.prototype.createNodeEntity = function(customProperies) {
|
||||||
|
var nodeId = Entities.addEntity(mergeObjects(BALL_PROTOTYPE, customProperies));
|
||||||
|
return nodeId;
|
||||||
|
}
|
||||||
|
|
||||||
|
MagBalls.prototype.createEdgeEntity = function(nodeIdA, nodeIdB) {
|
||||||
|
var apos = this.getNodePosition(nodeIdA);
|
||||||
|
var bpos = this.getNodePosition(nodeIdB);
|
||||||
|
var edgeId = Entities.addEntity(mergeObjects(EDGE_PROTOTYPE, {
|
||||||
|
position: apos,
|
||||||
|
linePoints: [ ZERO_VECTOR, Vec3.subtract(bpos, apos) ],
|
||||||
|
userData: JSON.stringify({
|
||||||
|
magBalls: {
|
||||||
|
start: nodeIdA,
|
||||||
|
end: nodeIdB,
|
||||||
|
length: BALL_DISTANCE
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}));
|
||||||
|
this.edgeObjects[edgeId] = new EdgeSpring(edgeId, this);
|
||||||
|
return edgeId;
|
||||||
|
}
|
||||||
|
|
||||||
|
MagBalls.prototype.findPotentialEdges = function(nodeId) {
|
||||||
|
var variances = {};
|
||||||
|
for (var otherNodeId in this.nodes) {
|
||||||
|
// can't self connect
|
||||||
|
if (otherNodeId == nodeId) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// can't doubly connect
|
||||||
|
if (this.areConnected(otherNodeId, nodeId)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check distance to attempt
|
||||||
|
var distance = this.getNodeDistance(nodeId, otherNodeId);
|
||||||
|
var variance = this.getVariance(distance);
|
||||||
|
if (Math.abs(variance) > 0.25) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
variances[otherNodeId] = variance;
|
||||||
|
}
|
||||||
|
return variances;
|
||||||
|
}
|
||||||
|
|
||||||
|
MagBalls.prototype.grabBall = function(position, maxDist) {
|
||||||
|
var selected = this.findNearestNode(position, maxDist);
|
||||||
|
if (!selected) {
|
||||||
|
selected = this.createNode({ position: position });
|
||||||
|
}
|
||||||
|
if (selected) {
|
||||||
|
this.stable = true;
|
||||||
|
this.breakEdges(selected);
|
||||||
|
this.selectedNodes[selected] = true;
|
||||||
|
}
|
||||||
|
return selected;
|
||||||
|
}
|
||||||
|
|
||||||
|
MagBalls.prototype.releaseBall = function(releasedBall) {
|
||||||
|
delete this.selectedNodes[releasedBall];
|
||||||
|
logDebug("Released ball: " + releasedBall);
|
||||||
|
|
||||||
|
this.stable = false;
|
||||||
|
|
||||||
|
var releasePosition = this.getNodePosition(releasedBall);
|
||||||
|
|
||||||
|
// iterate through the other balls and ensure we don't intersect with
|
||||||
|
// any of them. If we do, just delete this ball and return.
|
||||||
|
// FIXME (play a pop sound)
|
||||||
|
for (var nodeId in this.nodes) {
|
||||||
|
if (nodeId == releasedBall) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
var distance = this.getNodeDistance(releasedBall, nodeId);
|
||||||
|
if (distance < BALL_SIZE) {
|
||||||
|
this.destroyNode(releasedBall);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var targets = this.findPotentialEdges(releasedBall);
|
||||||
|
if (!targets || !Object.keys(targets).length) {
|
||||||
|
// this.destroyNode(releasedBall);
|
||||||
|
}
|
||||||
|
for (var otherBallId in targets) {
|
||||||
|
this.createEdge(otherBallId, releasedBall);
|
||||||
|
}
|
||||||
|
this.validate();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
MagBalls.prototype.getVariance = function(distance) {
|
||||||
|
// FIXME different balls or edges might have different ideas of variance...
|
||||||
|
// let something else handle this
|
||||||
|
var offset = (BALL_DISTANCE - distance);
|
||||||
|
var variance = offset / BALL_DISTANCE
|
||||||
|
return variance;
|
||||||
|
}
|
||||||
|
|
||||||
|
// remove unconnected balls
|
||||||
|
MagBalls.prototype.clean = function() {
|
||||||
|
// do nothing unless there are at least 2 balls and one edge
|
||||||
|
if (Object.keys(this.nodes).length < 2 || !Object.keys(this.edges).length) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
var disconnectedNodes = {};
|
||||||
|
for (var nodeId in this.nodes) {
|
||||||
|
if (!Object.keys(this.nodes[nodeId]).length) {
|
||||||
|
disconnectedNodes[nodeId] = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for (var nodeId in disconnectedNodes) {
|
||||||
|
this.destroyNode(nodeId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// remove all balls
|
||||||
|
MagBalls.prototype.clear = function() {
|
||||||
|
if (DEBUG_MAGSTICKS) {
|
||||||
|
this.deleteAll();
|
||||||
|
var ids = Entities.findEntities(MyAvatar.position, 50);
|
||||||
|
var result = [];
|
||||||
|
ids.forEach(function(id) {
|
||||||
|
var properties = Entities.getEntityProperties(id);
|
||||||
|
if (properties.name == BALL_NAME || properties.name == EDGE_NAME) {
|
||||||
|
Entities.deleteEntity(id);
|
||||||
|
}
|
||||||
|
}, this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
MagBalls.prototype.destroyEdge = function(edgeId) {
|
||||||
|
Graph.prototype.destroyEdge.call(this, edgeId);
|
||||||
|
delete this.edgeObjects[edgeId];
|
||||||
|
}
|
||||||
|
|
||||||
|
MagBalls.prototype.destroyNode = function(nodeId) {
|
||||||
|
Graph.prototype.destroyNode.call(this, nodeId);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Scan the entity tree and load all the objects in range
|
||||||
|
MagBalls.prototype.refresh = function() {
|
||||||
|
var ids = Entities.findEntities(MyAvatar.position, 50);
|
||||||
|
for (var i in ids) {
|
||||||
|
var id = ids[i];
|
||||||
|
var properties = Entities.getEntityProperties(id);
|
||||||
|
if (properties.name == BALL_NAME) {
|
||||||
|
this.nodes[id] = {};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var deleteEdges = [];
|
||||||
|
for (var i in ids) {
|
||||||
|
var id = ids[i];
|
||||||
|
var properties = Entities.getEntityProperties(id);
|
||||||
|
if (properties.name == EDGE_NAME) {
|
||||||
|
var edgeId = id;
|
||||||
|
this.edges[edgeId] = {};
|
||||||
|
var magBallData = getMagBallsData(id);
|
||||||
|
if (!magBallData.start || !magBallData.end) {
|
||||||
|
logWarn("Edge information is missing for " + id);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (!this.nodes[magBallData.start] || !this.nodes[magBallData.end]) {
|
||||||
|
logWarn("Edge " + id + " refers to unknown nodes: " + JSON.stringify(magBallData));
|
||||||
|
Entities.editEntity(id, { color: COLORS.RED });
|
||||||
|
deleteEdges.push(id);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
this.nodes[magBallData.start][edgeId] = true;
|
||||||
|
this.nodes[magBallData.end][edgeId] = true;
|
||||||
|
this.edges[edgeId][magBallData.start] = true;
|
||||||
|
this.edges[edgeId][magBallData.end] = true;
|
||||||
|
this.edgeObjects[id] = new EdgeSpring(id, this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (deleteEdges.length) {
|
||||||
|
Script.setTimeout(function() {
|
||||||
|
for (var i in deleteEdges) {
|
||||||
|
var edgeId = deleteEdges[i];
|
||||||
|
logDebug("deleting invalid edge " + edgeId);
|
||||||
|
Entities.deleteEntity(edgeId);
|
||||||
|
}
|
||||||
|
}, 1000);
|
||||||
|
}
|
||||||
|
|
||||||
|
var edgeCount = Object.keys(this.edges).length;
|
||||||
|
var nodeCount = Object.keys(this.nodes).length;
|
||||||
|
logDebug("Found " + nodeCount + " nodes and " + edgeCount + " edges ");
|
||||||
|
this.validate();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
MagBalls.prototype.findEdgeParams = function(startBall, endBall) {
|
||||||
|
var startBallPos = this.getNodePosition(startBall);
|
||||||
|
var endBallPos = this.getNodePosition(endBall);
|
||||||
|
var vector = Vec3.subtract(endBallPos, startBallPos);
|
||||||
|
return {
|
||||||
|
position: startBallPos,
|
||||||
|
linePoints: [ ZERO_VECTOR, vector ]
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
MagBalls.prototype.fixupEdge = function(edgeId) {
|
||||||
|
var ballsInEdge = Object.keys(this.edges[edgeId]);
|
||||||
|
Entities.editEntity(edgeId, this.findEdgeParams(ballsInEdge[0], ballsInEdge[1]));
|
||||||
|
}
|
||||||
|
|
25
examples/toys/magBalls/magBallsMain.js
Normal file
25
examples/toys/magBalls/magBallsMain.js
Normal file
|
@ -0,0 +1,25 @@
|
||||||
|
//
|
||||||
|
// Created by Bradley Austin Davis on 2015/08/25
|
||||||
|
// 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
|
||||||
|
//
|
||||||
|
|
||||||
|
Script.include("constants.js");
|
||||||
|
Script.include("utils.js");
|
||||||
|
Script.include("magBalls.js");
|
||||||
|
|
||||||
|
Script.include("ballController.js");
|
||||||
|
|
||||||
|
var magBalls = new MagBalls();
|
||||||
|
|
||||||
|
// Clear any previous balls
|
||||||
|
// magBalls.clear();
|
||||||
|
|
||||||
|
MenuController = function(side) {
|
||||||
|
HandController.call(this, side);
|
||||||
|
}
|
||||||
|
|
||||||
|
// FIXME resolve some of the issues with dual controllers before allowing both controllers active
|
||||||
|
var handControllers = [new BallController(LEFT_CONTROLLER, magBalls)]; //, new HandController(RIGHT) ];
|
66
examples/toys/magBalls/menuController.js
Normal file
66
examples/toys/magBalls/menuController.js
Normal file
|
@ -0,0 +1,66 @@
|
||||||
|
Script.include("handController.js");
|
||||||
|
|
||||||
|
MenuController = function(side, magBalls) {
|
||||||
|
HandController.call(this, side);
|
||||||
|
}
|
||||||
|
|
||||||
|
MenuController.prototype = Object.create( HandController.prototype );
|
||||||
|
|
||||||
|
MenuController.prototype.onUpdate = function(deltaTime) {
|
||||||
|
HandController.prototype.onUpdate.call(this, deltaTime);
|
||||||
|
if (!this.selected) {
|
||||||
|
// Find the highlight target and set it.
|
||||||
|
var target = this.magBalls.findNearestNode(this.tipPosition, BALL_SELECTION_RADIUS);
|
||||||
|
this.highlighter.highlight(target);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.highlighter.highlight(null);
|
||||||
|
Entities.editEntity(this.selected, { position: this.tipPosition });
|
||||||
|
var targetBalls = this.magBalls.findPotentialEdges(this.selected);
|
||||||
|
for (var ballId in targetBalls) {
|
||||||
|
if (!this.ghostEdges[ballId]) {
|
||||||
|
// create the ovleray
|
||||||
|
this.ghostEdges[ballId] = Overlays.addOverlay("line3d", {
|
||||||
|
start: this.magBalls.getNodePosition(ballId),
|
||||||
|
end: this.tipPosition,
|
||||||
|
color: COLORS.RED,
|
||||||
|
alpha: 1,
|
||||||
|
lineWidth: 5,
|
||||||
|
visible: true,
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
Overlays.editOverlay(this.ghostEdges[ballId], {
|
||||||
|
end: this.tipPosition,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for (var ballId in this.ghostEdges) {
|
||||||
|
if (!targetBalls[ballId]) {
|
||||||
|
Overlays.deleteOverlay(this.ghostEdges[ballId]);
|
||||||
|
delete this.ghostEdges[ballId];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
MenuController.prototype.onClick = function() {
|
||||||
|
this.selected = this.magBalls.grabBall(this.tipPosition, BALL_SELECTION_RADIUS);
|
||||||
|
this.highlighter.highlight(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
MenuController.prototype.onRelease = function() {
|
||||||
|
this.clearGhostEdges();
|
||||||
|
this.magBalls.releaseBall(this.selected);
|
||||||
|
this.selected = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
MenuController.prototype.clearGhostEdges = function() {
|
||||||
|
for(var ballId in this.ghostEdges) {
|
||||||
|
Overlays.deleteOverlay(this.ghostEdges[ballId]);
|
||||||
|
delete this.ghostEdges[ballId];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
MenuController.prototype.onCleanup = function() {
|
||||||
|
HandController.prototype.onCleanup.call(this);
|
||||||
|
this.clearGhostEdges();
|
||||||
|
}
|
106
examples/toys/magBalls/utils.js
Normal file
106
examples/toys/magBalls/utils.js
Normal file
|
@ -0,0 +1,106 @@
|
||||||
|
//
|
||||||
|
// Created by Bradley Austin Davis on 2015/08/29
|
||||||
|
// 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
|
||||||
|
//
|
||||||
|
|
||||||
|
vec3toStr = function (v, digits) {
|
||||||
|
if (!digits) { digits = 3; }
|
||||||
|
return "{ " + v.x.toFixed(digits) + ", " + v.y.toFixed(digits) + ", " + v.z.toFixed(digits)+ " }";
|
||||||
|
}
|
||||||
|
|
||||||
|
scaleLine = function (start, end, scale) {
|
||||||
|
var v = Vec3.subtract(end, start);
|
||||||
|
var length = Vec3.length(v);
|
||||||
|
v = Vec3.multiply(scale, v);
|
||||||
|
return Vec3.sum(start, v);
|
||||||
|
}
|
||||||
|
|
||||||
|
findAction = function(name) {
|
||||||
|
var actions = Controller.getAllActions();
|
||||||
|
for (var i = 0; i < actions.length; i++) {
|
||||||
|
if (actions[i].actionName == name) {
|
||||||
|
return i;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
addLine = function(origin, vector, color) {
|
||||||
|
if (!color) {
|
||||||
|
color = COLORS.WHITE
|
||||||
|
}
|
||||||
|
return Entities.addEntity(mergeObjects(LINE_PROTOTYPE, {
|
||||||
|
position: origin,
|
||||||
|
linePoints: [
|
||||||
|
ZERO_VECTOR,
|
||||||
|
vector,
|
||||||
|
],
|
||||||
|
color: color
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
// FIXME fetch from a subkey of user data to support non-destructive modifications
|
||||||
|
setEntityUserData = function(id, data) {
|
||||||
|
var json = JSON.stringify(data)
|
||||||
|
Entities.editEntity(id, { userData: json });
|
||||||
|
}
|
||||||
|
|
||||||
|
// FIXME do non-destructive modification of the existing user data
|
||||||
|
getEntityUserData = function(id) {
|
||||||
|
var results = null;
|
||||||
|
var properties = Entities.getEntityProperties(id);
|
||||||
|
if (properties.userData) {
|
||||||
|
results = JSON.parse(properties.userData);
|
||||||
|
}
|
||||||
|
return results ? results : {};
|
||||||
|
}
|
||||||
|
|
||||||
|
// Non-destructively modify the user data of an entity.
|
||||||
|
setEntityCustomData = function(customKey, id, data) {
|
||||||
|
var userData = getEntityUserData(id);
|
||||||
|
userData[customKey] = data;
|
||||||
|
setEntityUserData(id, userData);
|
||||||
|
}
|
||||||
|
|
||||||
|
getEntityCustomData = function(customKey, id, defaultValue) {
|
||||||
|
var userData = getEntityUserData(id);
|
||||||
|
return userData[customKey] ? userData[customKey] : defaultValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
getMagBallsData = function(id) {
|
||||||
|
return getEntityCustomData(CUSTOM_DATA_NAME, id, {});
|
||||||
|
}
|
||||||
|
|
||||||
|
setMagBallsData = function(id, value) {
|
||||||
|
setEntityCustomData(CUSTOM_DATA_NAME, id, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
mergeObjects = function(proto, custom) {
|
||||||
|
var result = {};
|
||||||
|
for (var attrname in proto) {
|
||||||
|
result[attrname] = proto[attrname];
|
||||||
|
}
|
||||||
|
for (var attrname in custom) {
|
||||||
|
result[attrname] = custom[attrname];
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
logWarn = function(str) {
|
||||||
|
print(str);
|
||||||
|
}
|
||||||
|
|
||||||
|
logError = function(str) {
|
||||||
|
print(str);
|
||||||
|
}
|
||||||
|
|
||||||
|
logInfo = function(str) {
|
||||||
|
print(str);
|
||||||
|
}
|
||||||
|
|
||||||
|
logDebug = function(str) {
|
||||||
|
print(str);
|
||||||
|
}
|
|
@ -1,10 +1,206 @@
|
||||||
var controlHeld = false;
|
var controlHeld = false;
|
||||||
var shiftHeld = false;
|
var shiftHeld = false;
|
||||||
|
|
||||||
|
Script.include([
|
||||||
|
"libraries/toolBars.js",
|
||||||
|
]);
|
||||||
|
|
||||||
|
var isActive = false;
|
||||||
|
var toolIconUrl = "http://headache.hungry.com/~seth/hifi/";
|
||||||
|
var toolHeight = 50;
|
||||||
|
var toolWidth = 50;
|
||||||
|
|
||||||
|
var addingVoxels = false;
|
||||||
|
var deletingVoxels = false;
|
||||||
|
|
||||||
|
offAlpha = 0.5;
|
||||||
|
onAlpha = 0.9;
|
||||||
|
|
||||||
function floorVector(v) {
|
function floorVector(v) {
|
||||||
return {x: Math.floor(v.x), y: Math.floor(v.y), z: Math.floor(v.z)};
|
return {x: Math.floor(v.x), y: Math.floor(v.y), z: Math.floor(v.z)};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function vectorToString(v){
|
||||||
|
return "{" + v.x + ", " + v.x + ", " + v.x + "}";
|
||||||
|
}
|
||||||
|
|
||||||
|
var toolBar = (function () {
|
||||||
|
var that = {},
|
||||||
|
toolBar,
|
||||||
|
activeButton,
|
||||||
|
addVoxelButton,
|
||||||
|
deleteVoxelButton,
|
||||||
|
addTerrainButton;
|
||||||
|
|
||||||
|
function initialize() {
|
||||||
|
toolBar = new ToolBar(0, 0, ToolBar.VERTICAL, "highfidelity.voxel.toolbar", function (windowDimensions, toolbar) {
|
||||||
|
return {
|
||||||
|
x: windowDimensions.x - 8*2 - toolbar.width * 2,
|
||||||
|
y: (windowDimensions.y - toolbar.height) / 2
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
activeButton = toolBar.addTool({
|
||||||
|
imageURL: "http://s3.amazonaws.com/hifi-public/images/tools/polyvox.svg",
|
||||||
|
width: toolWidth,
|
||||||
|
height: toolHeight,
|
||||||
|
alpha: onAlpha,
|
||||||
|
visible: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
addVoxelButton = toolBar.addTool({
|
||||||
|
imageURL: toolIconUrl + "voxel-add.svg",
|
||||||
|
subImage: { x: 0, y: Tool.IMAGE_WIDTH, width: Tool.IMAGE_WIDTH, height: Tool.IMAGE_HEIGHT },
|
||||||
|
width: toolWidth,
|
||||||
|
height: toolHeight,
|
||||||
|
alpha: offAlpha,
|
||||||
|
visible: false
|
||||||
|
});
|
||||||
|
|
||||||
|
deleteVoxelButton = toolBar.addTool({
|
||||||
|
imageURL: toolIconUrl + "voxel-delete.svg",
|
||||||
|
subImage: { x: 0, y: Tool.IMAGE_WIDTH, width: Tool.IMAGE_WIDTH, height: Tool.IMAGE_HEIGHT },
|
||||||
|
width: toolWidth,
|
||||||
|
height: toolHeight,
|
||||||
|
alpha: offAlpha,
|
||||||
|
visible: false
|
||||||
|
});
|
||||||
|
|
||||||
|
addTerrainButton = toolBar.addTool({
|
||||||
|
imageURL: toolIconUrl + "voxel-terrain.svg",
|
||||||
|
subImage: { x: 0, y: Tool.IMAGE_WIDTH, width: Tool.IMAGE_WIDTH, height: Tool.IMAGE_HEIGHT },
|
||||||
|
width: toolWidth,
|
||||||
|
height: toolHeight,
|
||||||
|
alpha: onAlpha,
|
||||||
|
visible: false
|
||||||
|
});
|
||||||
|
|
||||||
|
that.setActive(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
that.setActive = function(active) {
|
||||||
|
if (active != isActive) {
|
||||||
|
isActive = active;
|
||||||
|
that.showTools(isActive);
|
||||||
|
}
|
||||||
|
toolBar.selectTool(activeButton, isActive);
|
||||||
|
};
|
||||||
|
|
||||||
|
// Sets visibility of tool buttons, excluding the power button
|
||||||
|
that.showTools = function(doShow) {
|
||||||
|
toolBar.showTool(addVoxelButton, doShow);
|
||||||
|
toolBar.showTool(deleteVoxelButton, doShow);
|
||||||
|
toolBar.showTool(addTerrainButton, doShow);
|
||||||
|
};
|
||||||
|
|
||||||
|
that.mousePressEvent = function (event) {
|
||||||
|
var clickedOverlay = Overlays.getOverlayAtPoint({ x: event.x, y: event.y });
|
||||||
|
|
||||||
|
if (activeButton === toolBar.clicked(clickedOverlay)) {
|
||||||
|
that.setActive(!isActive);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (addVoxelButton === toolBar.clicked(clickedOverlay)) {
|
||||||
|
if (addingVoxels) {
|
||||||
|
addingVoxels = false;
|
||||||
|
deletingVoxels = false;
|
||||||
|
toolBar.setAlpha(offAlpha, addVoxelButton);
|
||||||
|
toolBar.setAlpha(offAlpha, deleteVoxelButton);
|
||||||
|
toolBar.selectTool(addVoxelButton, false);
|
||||||
|
toolBar.selectTool(deleteVoxelButton, false);
|
||||||
|
} else {
|
||||||
|
addingVoxels = true;
|
||||||
|
deletingVoxels = false;
|
||||||
|
toolBar.setAlpha(onAlpha, addVoxelButton);
|
||||||
|
toolBar.setAlpha(offAlpha, deleteVoxelButton);
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (deleteVoxelButton === toolBar.clicked(clickedOverlay)) {
|
||||||
|
if (deletingVoxels) {
|
||||||
|
deletingVoxels = false;
|
||||||
|
addingVoxels = false;
|
||||||
|
toolBar.setAlpha(offAlpha, addVoxelButton);
|
||||||
|
toolBar.setAlpha(offAlpha, deleteVoxelButton);
|
||||||
|
} else {
|
||||||
|
deletingVoxels = true;
|
||||||
|
addingVoxels = false;
|
||||||
|
toolBar.setAlpha(offAlpha, addVoxelButton);
|
||||||
|
toolBar.setAlpha(onAlpha, deleteVoxelButton);
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (addTerrainButton === toolBar.clicked(clickedOverlay)) {
|
||||||
|
addTerrainBlock();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Window.domainChanged.connect(function() {
|
||||||
|
that.setActive(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
that.cleanup = function () {
|
||||||
|
toolBar.cleanup();
|
||||||
|
// Overlays.deleteOverlay(activeButton);
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
initialize();
|
||||||
|
return that;
|
||||||
|
}());
|
||||||
|
|
||||||
|
|
||||||
|
function addTerrainBlock() {
|
||||||
|
|
||||||
|
var myPosDiv16 = Vec3.multiply(Vec3.sum(MyAvatar.position, {x:8, x:8, z:8}), 1.0 / 16.0);
|
||||||
|
var myPosDiv16Floored = floorVector(myPosDiv16);
|
||||||
|
var baseLocation = Vec3.multiply(myPosDiv16Floored, 16.0);
|
||||||
|
|
||||||
|
if (baseLocation.y + 8 > MyAvatar.position.y) {
|
||||||
|
baseLocation.y -= 16;
|
||||||
|
}
|
||||||
|
|
||||||
|
print("myPosDiv16 is " + vectorToString(myPosDiv16));
|
||||||
|
print("MyPosDiv16Floored is " + vectorToString(myPosDiv16Floored));
|
||||||
|
print("baseLocation is " + vectorToString(baseLocation));
|
||||||
|
|
||||||
|
alreadyThere = Entities.findEntities(baseLocation, 1.0);
|
||||||
|
for (var i = 0; i < alreadyThere.length; i++) {
|
||||||
|
var id = alreadyThere[i];
|
||||||
|
var properties = Entities.getEntityProperties(id);
|
||||||
|
if (properties.name == "terrain") {
|
||||||
|
print("already terrain there");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var polyVoxId = Entities.addEntity({
|
||||||
|
type: "PolyVox",
|
||||||
|
name: "terrain",
|
||||||
|
position: baseLocation,
|
||||||
|
dimensions: { x: 16, y: 16, z: 16 },
|
||||||
|
voxelVolumeSize: {x:16, y:16, z:16},
|
||||||
|
voxelSurfaceStyle: 2
|
||||||
|
});
|
||||||
|
Entities.setAllVoxels(polyVoxId, 255);
|
||||||
|
|
||||||
|
for (var y = 8; y < 16; y++) {
|
||||||
|
for (var x = 0; x < 16; x++) {
|
||||||
|
for (var z = 0; z < 16; z++) {
|
||||||
|
Entities.setVoxel(polyVoxId, {x: x, y: y, z: z}, 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
function attemptVoxelChange(pickRayDir, intersection) {
|
function attemptVoxelChange(pickRayDir, intersection) {
|
||||||
|
|
||||||
var properties = Entities.getEntityProperties(intersection.entityID);
|
var properties = Entities.getEntityProperties(intersection.entityID);
|
||||||
|
@ -12,25 +208,30 @@ function attemptVoxelChange(pickRayDir, intersection) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (addingVoxels == false && deletingVoxels == false) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
var voxelPosition = Entities.worldCoordsToVoxelCoords(intersection.entityID, intersection.intersection);
|
var voxelPosition = Entities.worldCoordsToVoxelCoords(intersection.entityID, intersection.intersection);
|
||||||
voxelPosition = Vec3.subtract(voxelPosition, {x: 0.5, y: 0.5, z: 0.5});
|
|
||||||
var pickRayDirInVoxelSpace = Entities.localCoordsToVoxelCoords(intersection.entityID, pickRayDir);
|
var pickRayDirInVoxelSpace = Entities.localCoordsToVoxelCoords(intersection.entityID, pickRayDir);
|
||||||
pickRayDirInVoxelSpace = Vec3.normalize(pickRayDirInVoxelSpace);
|
pickRayDirInVoxelSpace = Vec3.normalize(pickRayDirInVoxelSpace);
|
||||||
|
|
||||||
|
var doAdd = addingVoxels;
|
||||||
|
var doDelete = deletingVoxels;
|
||||||
|
|
||||||
if (controlHeld) {
|
if (controlHeld) {
|
||||||
// hold control to erase a voxel
|
doAdd = deletingVoxels;
|
||||||
|
doDelete = addingVoxels;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (doDelete) {
|
||||||
var toErasePosition = Vec3.sum(voxelPosition, Vec3.multiply(pickRayDirInVoxelSpace, 0.1));
|
var toErasePosition = Vec3.sum(voxelPosition, Vec3.multiply(pickRayDirInVoxelSpace, 0.1));
|
||||||
return Entities.setVoxel(intersection.entityID, floorVector(toErasePosition), 0);
|
return Entities.setVoxel(intersection.entityID, floorVector(toErasePosition), 0);
|
||||||
} else if (shiftHeld) {
|
}
|
||||||
// hold shift to set all voxels to 255
|
if (doAdd) {
|
||||||
return Entities.setAllVoxels(intersection.entityID, 255);
|
|
||||||
} else {
|
|
||||||
// no modifier key to add a voxel
|
|
||||||
var toDrawPosition = Vec3.subtract(voxelPosition, Vec3.multiply(pickRayDirInVoxelSpace, 0.1));
|
var toDrawPosition = Vec3.subtract(voxelPosition, Vec3.multiply(pickRayDirInVoxelSpace, 0.1));
|
||||||
return Entities.setVoxel(intersection.entityID, floorVector(toDrawPosition), 255);
|
return Entities.setVoxel(intersection.entityID, floorVector(toDrawPosition), 255);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Entities.setVoxelSphere(id, intersection.intersection, radius, 0)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function mousePressEvent(event) {
|
function mousePressEvent(event) {
|
||||||
|
@ -38,6 +239,10 @@ function mousePressEvent(event) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (toolBar.mousePressEvent(event)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
var pickRay = Camera.computePickRay(event.x, event.y);
|
var pickRay = Camera.computePickRay(event.x, event.y);
|
||||||
var intersection = Entities.findRayIntersection(pickRay, true); // accurate picking
|
var intersection = Entities.findRayIntersection(pickRay, true); // accurate picking
|
||||||
|
|
||||||
|
@ -76,6 +281,15 @@ function keyReleaseEvent(event) {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function cleanup() {
|
||||||
|
for (var i = 0; i < overlays.length; i++) {
|
||||||
|
Overlays.deleteOverlay(overlays[i]);
|
||||||
|
}
|
||||||
|
toolBar.cleanup();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
Controller.mousePressEvent.connect(mousePressEvent);
|
Controller.mousePressEvent.connect(mousePressEvent);
|
||||||
Controller.keyPressEvent.connect(keyPressEvent);
|
Controller.keyPressEvent.connect(keyPressEvent);
|
||||||
Controller.keyReleaseEvent.connect(keyReleaseEvent);
|
Controller.keyReleaseEvent.connect(keyReleaseEvent);
|
||||||
|
Script.scriptEnding.connect(cleanup);
|
||||||
|
|
|
@ -2821,7 +2821,8 @@ void Application::update(float deltaTime) {
|
||||||
|
|
||||||
_entities.getTree()->lockForWrite();
|
_entities.getTree()->lockForWrite();
|
||||||
_entitySimulation.lock();
|
_entitySimulation.lock();
|
||||||
_physicsEngine.changeObjects(_entitySimulation.getObjectsToChange());
|
VectorOfMotionStates stillNeedChange = _physicsEngine.changeObjects(_entitySimulation.getObjectsToChange());
|
||||||
|
_entitySimulation.setObjectsToChange(stillNeedChange);
|
||||||
_entitySimulation.unlock();
|
_entitySimulation.unlock();
|
||||||
_entities.getTree()->unlock();
|
_entities.getTree()->unlock();
|
||||||
|
|
||||||
|
|
|
@ -456,8 +456,6 @@ Menu::Menu() {
|
||||||
SLOT(toggleConnexion(bool)));
|
SLOT(toggleConnexion(bool)));
|
||||||
|
|
||||||
MenuWrapper* handOptionsMenu = developerMenu->addMenu("Hands");
|
MenuWrapper* handOptionsMenu = developerMenu->addMenu("Hands");
|
||||||
addCheckableActionToQMenuAndActionHash(handOptionsMenu, MenuOption::AlignForearmsWithWrists, 0, false);
|
|
||||||
addCheckableActionToQMenuAndActionHash(handOptionsMenu, MenuOption::AlternateIK, 0, false);
|
|
||||||
addCheckableActionToQMenuAndActionHash(handOptionsMenu, MenuOption::DisplayHands, 0, true);
|
addCheckableActionToQMenuAndActionHash(handOptionsMenu, MenuOption::DisplayHands, 0, true);
|
||||||
addCheckableActionToQMenuAndActionHash(handOptionsMenu, MenuOption::DisplayHandTargets, 0, false);
|
addCheckableActionToQMenuAndActionHash(handOptionsMenu, MenuOption::DisplayHandTargets, 0, false);
|
||||||
addCheckableActionToQMenuAndActionHash(handOptionsMenu, MenuOption::HandMouseInput, 0, true);
|
addCheckableActionToQMenuAndActionHash(handOptionsMenu, MenuOption::HandMouseInput, 0, true);
|
||||||
|
|
|
@ -131,8 +131,6 @@ namespace MenuOption {
|
||||||
const QString AboutApp = "About Interface";
|
const QString AboutApp = "About Interface";
|
||||||
const QString AddRemoveFriends = "Add/Remove Friends...";
|
const QString AddRemoveFriends = "Add/Remove Friends...";
|
||||||
const QString AddressBar = "Show Address Bar";
|
const QString AddressBar = "Show Address Bar";
|
||||||
const QString AlignForearmsWithWrists = "Align Forearms with Wrists";
|
|
||||||
const QString AlternateIK = "Alternate IK";
|
|
||||||
const QString Animations = "Animations...";
|
const QString Animations = "Animations...";
|
||||||
const QString Atmosphere = "Atmosphere";
|
const QString Atmosphere = "Atmosphere";
|
||||||
const QString Attachments = "Attachments...";
|
const QString Attachments = "Attachments...";
|
||||||
|
|
|
@ -28,15 +28,20 @@ AvatarMotionState::~AvatarMotionState() {
|
||||||
}
|
}
|
||||||
|
|
||||||
// virtual
|
// virtual
|
||||||
uint32_t AvatarMotionState::getAndClearIncomingDirtyFlags() {
|
uint32_t AvatarMotionState::getIncomingDirtyFlags() {
|
||||||
uint32_t dirtyFlags = 0;
|
uint32_t dirtyFlags = 0;
|
||||||
if (_body && _avatar) {
|
if (_body && _avatar) {
|
||||||
dirtyFlags = _dirtyFlags;
|
dirtyFlags = _dirtyFlags;
|
||||||
_dirtyFlags = 0;
|
|
||||||
}
|
}
|
||||||
return dirtyFlags;
|
return dirtyFlags;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void AvatarMotionState::clearIncomingDirtyFlags() {
|
||||||
|
if (_body && _avatar) {
|
||||||
|
_dirtyFlags = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
MotionType AvatarMotionState::computeObjectMotionType() const {
|
MotionType AvatarMotionState::computeObjectMotionType() const {
|
||||||
// TODO?: support non-DYNAMIC motion for avatars? (e.g. when sitting)
|
// TODO?: support non-DYNAMIC motion for avatars? (e.g. when sitting)
|
||||||
return MOTION_TYPE_DYNAMIC;
|
return MOTION_TYPE_DYNAMIC;
|
||||||
|
|
|
@ -25,7 +25,8 @@ public:
|
||||||
|
|
||||||
virtual MotionType getMotionType() const { return _motionType; }
|
virtual MotionType getMotionType() const { return _motionType; }
|
||||||
|
|
||||||
virtual uint32_t getAndClearIncomingDirtyFlags();
|
virtual uint32_t getIncomingDirtyFlags();
|
||||||
|
virtual void clearIncomingDirtyFlags();
|
||||||
|
|
||||||
virtual MotionType computeObjectMotionType() const;
|
virtual MotionType computeObjectMotionType() const;
|
||||||
|
|
||||||
|
@ -65,6 +66,7 @@ public:
|
||||||
friend class AvatarManager;
|
friend class AvatarManager;
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
|
virtual bool isReadyToComputeShape() { return true; }
|
||||||
virtual btCollisionShape* computeNewShape();
|
virtual btCollisionShape* computeNewShape();
|
||||||
virtual void clearObjectBackPointer();
|
virtual void clearObjectBackPointer();
|
||||||
Avatar* _avatar;
|
Avatar* _avatar;
|
||||||
|
|
|
@ -41,7 +41,7 @@ SkeletonModel::~SkeletonModel() {
|
||||||
|
|
||||||
void SkeletonModel::initJointStates(QVector<JointState> states) {
|
void SkeletonModel::initJointStates(QVector<JointState> states) {
|
||||||
const FBXGeometry& geometry = _geometry->getFBXGeometry();
|
const FBXGeometry& geometry = _geometry->getFBXGeometry();
|
||||||
glm::mat4 parentTransform = glm::scale(_scale) * glm::translate(_offset) * geometry.offset;
|
glm::mat4 rootTransform = glm::scale(_scale) * glm::translate(_offset) * geometry.offset;
|
||||||
|
|
||||||
int rootJointIndex = geometry.rootJointIndex;
|
int rootJointIndex = geometry.rootJointIndex;
|
||||||
int leftHandJointIndex = geometry.leftHandJointIndex;
|
int leftHandJointIndex = geometry.leftHandJointIndex;
|
||||||
|
@ -51,14 +51,14 @@ void SkeletonModel::initJointStates(QVector<JointState> states) {
|
||||||
int rightElbowJointIndex = rightHandJointIndex >= 0 ? geometry.joints.at(rightHandJointIndex).parentIndex : -1;
|
int rightElbowJointIndex = rightHandJointIndex >= 0 ? geometry.joints.at(rightHandJointIndex).parentIndex : -1;
|
||||||
int rightShoulderJointIndex = rightElbowJointIndex >= 0 ? geometry.joints.at(rightElbowJointIndex).parentIndex : -1;
|
int rightShoulderJointIndex = rightElbowJointIndex >= 0 ? geometry.joints.at(rightElbowJointIndex).parentIndex : -1;
|
||||||
|
|
||||||
_boundingRadius = _rig->initJointStates(states, parentTransform,
|
_rig->initJointStates(states, rootTransform,
|
||||||
rootJointIndex,
|
rootJointIndex,
|
||||||
leftHandJointIndex,
|
leftHandJointIndex,
|
||||||
leftElbowJointIndex,
|
leftElbowJointIndex,
|
||||||
leftShoulderJointIndex,
|
leftShoulderJointIndex,
|
||||||
rightHandJointIndex,
|
rightHandJointIndex,
|
||||||
rightElbowJointIndex,
|
rightElbowJointIndex,
|
||||||
rightShoulderJointIndex);
|
rightShoulderJointIndex);
|
||||||
|
|
||||||
// Determine the default eye position for avatar scale = 1.0
|
// Determine the default eye position for avatar scale = 1.0
|
||||||
int headJointIndex = _geometry->getFBXGeometry().headJointIndex;
|
int headJointIndex = _geometry->getFBXGeometry().headJointIndex;
|
||||||
|
@ -83,7 +83,7 @@ void SkeletonModel::initJointStates(QVector<JointState> states) {
|
||||||
// of its root joint and we need that done before we try to build shapes hence we
|
// of its root joint and we need that done before we try to build shapes hence we
|
||||||
// recompute all joint transforms at this time.
|
// recompute all joint transforms at this time.
|
||||||
for (int i = 0; i < _rig->getJointStateCount(); i++) {
|
for (int i = 0; i < _rig->getJointStateCount(); i++) {
|
||||||
_rig->updateJointState(i, parentTransform);
|
_rig->updateJointState(i, rootTransform);
|
||||||
}
|
}
|
||||||
|
|
||||||
buildShapes();
|
buildShapes();
|
||||||
|
@ -157,11 +157,11 @@ void SkeletonModel::simulate(float deltaTime, bool fullUpdate) {
|
||||||
setBlendshapeCoefficients(_owningAvatar->getHead()->getBlendshapeCoefficients());
|
setBlendshapeCoefficients(_owningAvatar->getHead()->getBlendshapeCoefficients());
|
||||||
|
|
||||||
Model::simulate(deltaTime, fullUpdate);
|
Model::simulate(deltaTime, fullUpdate);
|
||||||
|
|
||||||
if (!isActive() || !_owningAvatar->isMyAvatar()) {
|
if (!isActive() || !_owningAvatar->isMyAvatar()) {
|
||||||
return; // only simulate for own avatar
|
return; // only simulate for own avatar
|
||||||
}
|
}
|
||||||
|
|
||||||
MyAvatar* myAvatar = static_cast<MyAvatar*>(_owningAvatar);
|
MyAvatar* myAvatar = static_cast<MyAvatar*>(_owningAvatar);
|
||||||
if (myAvatar->isPlaying()) {
|
if (myAvatar->isPlaying()) {
|
||||||
// Don't take inputs if playing back a recording.
|
// Don't take inputs if playing back a recording.
|
||||||
|
@ -248,40 +248,24 @@ void SkeletonModel::applyHandPosition(int jointIndex, const glm::vec3& position)
|
||||||
rotationBetween(handRotation * glm::vec3(-sign, 0.0f, 0.0f), forearmVector),
|
rotationBetween(handRotation * glm::vec3(-sign, 0.0f, 0.0f), forearmVector),
|
||||||
true, PALM_PRIORITY);
|
true, PALM_PRIORITY);
|
||||||
}
|
}
|
||||||
|
|
||||||
void SkeletonModel::applyPalmData(int jointIndex, PalmData& palm) {
|
void SkeletonModel::applyPalmData(int jointIndex, PalmData& palm) {
|
||||||
if (jointIndex == -1 || jointIndex >= _rig->getJointStateCount()) {
|
if (jointIndex == -1 || jointIndex >= _rig->getJointStateCount()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const FBXGeometry& geometry = _geometry->getFBXGeometry();
|
const FBXGeometry& geometry = _geometry->getFBXGeometry();
|
||||||
float sign = (jointIndex == geometry.rightHandJointIndex) ? 1.0f : -1.0f;
|
|
||||||
int parentJointIndex = geometry.joints.at(jointIndex).parentIndex;
|
int parentJointIndex = geometry.joints.at(jointIndex).parentIndex;
|
||||||
if (parentJointIndex == -1) {
|
if (parentJointIndex == -1) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// rotate palm to align with its normal (normal points out of hand's palm)
|
// the palm's position must be transformed into the model-frame
|
||||||
glm::quat inverseRotation = glm::inverse(_rotation);
|
glm::quat inverseRotation = glm::inverse(_rotation);
|
||||||
glm::vec3 palmPosition = inverseRotation * (palm.getPosition() - _translation);
|
glm::vec3 palmPosition = inverseRotation * (palm.getPosition() - _translation);
|
||||||
glm::vec3 palmNormal = inverseRotation * palm.getNormal();
|
|
||||||
glm::vec3 fingerDirection = inverseRotation * palm.getFingerDirection();
|
|
||||||
|
|
||||||
glm::quat palmRotation = rotationBetween(geometry.palmDirection, palmNormal);
|
// the palm's "raw" rotation is already in the model-frame
|
||||||
palmRotation = rotationBetween(palmRotation * glm::vec3(-sign, 0.0f, 0.0f), fingerDirection) * palmRotation;
|
glm::quat palmRotation = palm.getRawRotation();
|
||||||
|
|
||||||
if (Menu::getInstance()->isOptionChecked(MenuOption::AlternateIK)) {
|
inverseKinematics(jointIndex, palmPosition, palmRotation, PALM_PRIORITY);
|
||||||
_rig->setHandPosition(jointIndex, palmPosition, palmRotation, extractUniformScale(_scale), PALM_PRIORITY);
|
|
||||||
} else if (Menu::getInstance()->isOptionChecked(MenuOption::AlignForearmsWithWrists)) {
|
|
||||||
float forearmLength = geometry.joints.at(jointIndex).distanceToParent * extractUniformScale(_scale);
|
|
||||||
glm::vec3 forearm = palmRotation * glm::vec3(sign * forearmLength, 0.0f, 0.0f);
|
|
||||||
setJointPosition(parentJointIndex, palmPosition + forearm,
|
|
||||||
glm::quat(), false, -1, false, glm::vec3(0.0f, -1.0f, 0.0f), PALM_PRIORITY);
|
|
||||||
_rig->setJointRotationInBindFrame(parentJointIndex, palmRotation, PALM_PRIORITY);
|
|
||||||
// lock hand to forearm by slamming its rotation (in parent-frame) to identity
|
|
||||||
_rig->setJointRotationInConstrainedFrame(jointIndex, glm::quat(), PALM_PRIORITY);
|
|
||||||
} else {
|
|
||||||
inverseKinematics(jointIndex, palmPosition, palmRotation, PALM_PRIORITY);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void SkeletonModel::renderJointConstraints(gpu::Batch& batch, int jointIndex) {
|
void SkeletonModel::renderJointConstraints(gpu::Batch& batch, int jointIndex) {
|
||||||
|
@ -301,13 +285,13 @@ void SkeletonModel::renderJointConstraints(gpu::Batch& batch, int jointIndex) {
|
||||||
_rotation :
|
_rotation :
|
||||||
_rotation * _rig->getJointState(joint.parentIndex).getRotation();
|
_rotation * _rig->getJointState(joint.parentIndex).getRotation();
|
||||||
float fanScale = directionSize * 0.75f;
|
float fanScale = directionSize * 0.75f;
|
||||||
|
|
||||||
Transform transform = Transform();
|
Transform transform = Transform();
|
||||||
transform.setTranslation(position);
|
transform.setTranslation(position);
|
||||||
transform.setRotation(parentRotation);
|
transform.setRotation(parentRotation);
|
||||||
transform.setScale(fanScale);
|
transform.setScale(fanScale);
|
||||||
batch.setModelTransform(transform);
|
batch.setModelTransform(transform);
|
||||||
|
|
||||||
const int AXIS_COUNT = 3;
|
const int AXIS_COUNT = 3;
|
||||||
|
|
||||||
auto geometryCache = DependencyManager::get<GeometryCache>();
|
auto geometryCache = DependencyManager::get<GeometryCache>();
|
||||||
|
@ -318,7 +302,7 @@ void SkeletonModel::renderJointConstraints(gpu::Batch& batch, int jointIndex) {
|
||||||
}
|
}
|
||||||
glm::vec3 axis;
|
glm::vec3 axis;
|
||||||
axis[i] = 1.0f;
|
axis[i] = 1.0f;
|
||||||
|
|
||||||
glm::vec3 otherAxis;
|
glm::vec3 otherAxis;
|
||||||
if (i == 0) {
|
if (i == 0) {
|
||||||
otherAxis.y = 1.0f;
|
otherAxis.y = 1.0f;
|
||||||
|
@ -339,18 +323,18 @@ void SkeletonModel::renderJointConstraints(gpu::Batch& batch, int jointIndex) {
|
||||||
// better if the skeleton model cached these buffers for each of the joints they are rendering
|
// better if the skeleton model cached these buffers for each of the joints they are rendering
|
||||||
geometryCache->updateVertices(_triangleFanID, points, color);
|
geometryCache->updateVertices(_triangleFanID, points, color);
|
||||||
geometryCache->renderVertices(batch, gpu::TRIANGLE_FAN, _triangleFanID);
|
geometryCache->renderVertices(batch, gpu::TRIANGLE_FAN, _triangleFanID);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
renderOrientationDirections(batch, jointIndex, position, _rotation * jointState.getRotation(), directionSize);
|
renderOrientationDirections(batch, jointIndex, position, _rotation * jointState.getRotation(), directionSize);
|
||||||
jointIndex = joint.parentIndex;
|
jointIndex = joint.parentIndex;
|
||||||
|
|
||||||
} while (jointIndex != -1 && geometry.joints.at(jointIndex).isFree);
|
} while (jointIndex != -1 && geometry.joints.at(jointIndex).isFree);
|
||||||
}
|
}
|
||||||
|
|
||||||
void SkeletonModel::renderOrientationDirections(gpu::Batch& batch, int jointIndex,
|
void SkeletonModel::renderOrientationDirections(gpu::Batch& batch, int jointIndex,
|
||||||
glm::vec3 position, const glm::quat& orientation, float size) {
|
glm::vec3 position, const glm::quat& orientation, float size) {
|
||||||
|
|
||||||
auto geometryCache = DependencyManager::get<GeometryCache>();
|
auto geometryCache = DependencyManager::get<GeometryCache>();
|
||||||
|
|
||||||
if (!_jointOrientationLines.contains(jointIndex)) {
|
if (!_jointOrientationLines.contains(jointIndex)) {
|
||||||
|
@ -486,7 +470,7 @@ void SkeletonModel::buildShapes() {
|
||||||
if (_geometry == NULL || _rig->jointStatesEmpty()) {
|
if (_geometry == NULL || _rig->jointStatesEmpty()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const FBXGeometry& geometry = _geometry->getFBXGeometry();
|
const FBXGeometry& geometry = _geometry->getFBXGeometry();
|
||||||
if (geometry.joints.isEmpty() || geometry.rootJointIndex == -1) {
|
if (geometry.joints.isEmpty() || geometry.rootJointIndex == -1) {
|
||||||
// rootJointIndex == -1 if the avatar model has no skeleton
|
// rootJointIndex == -1 if the avatar model has no skeleton
|
||||||
|
@ -533,7 +517,6 @@ void SkeletonModel::computeBoundingShape(const FBXGeometry& geometry) {
|
||||||
|
|
||||||
glm::vec3 rootPosition = _rig->getJointState(geometry.rootJointIndex).getPosition();
|
glm::vec3 rootPosition = _rig->getJointState(geometry.rootJointIndex).getPosition();
|
||||||
_boundingCapsuleLocalOffset = 0.5f * (totalExtents.maximum + totalExtents.minimum) - rootPosition;
|
_boundingCapsuleLocalOffset = 0.5f * (totalExtents.maximum + totalExtents.minimum) - rootPosition;
|
||||||
_boundingRadius = 0.5f * glm::length(diagonal);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void SkeletonModel::renderBoundingCollisionShapes(gpu::Batch& batch, float alpha) {
|
void SkeletonModel::renderBoundingCollisionShapes(gpu::Batch& batch, float alpha) {
|
||||||
|
@ -551,7 +534,7 @@ void SkeletonModel::renderBoundingCollisionShapes(gpu::Batch& batch, float alpha
|
||||||
geometryCache->renderSphere(batch, _boundingCapsuleRadius, BALL_SUBDIVISIONS, BALL_SUBDIVISIONS,
|
geometryCache->renderSphere(batch, _boundingCapsuleRadius, BALL_SUBDIVISIONS, BALL_SUBDIVISIONS,
|
||||||
glm::vec4(0.6f, 0.6f, 0.8f, alpha));
|
glm::vec4(0.6f, 0.6f, 0.8f, alpha));
|
||||||
|
|
||||||
// draw a yellow sphere at the capsule bottom point
|
// draw a yellow sphere at the capsule bottom point
|
||||||
glm::vec3 bottomPoint = topPoint - glm::vec3(0.0f, -_boundingCapsuleHeight, 0.0f);
|
glm::vec3 bottomPoint = topPoint - glm::vec3(0.0f, -_boundingCapsuleHeight, 0.0f);
|
||||||
glm::vec3 axis = topPoint - bottomPoint;
|
glm::vec3 axis = topPoint - bottomPoint;
|
||||||
transform.setTranslation(bottomPoint);
|
transform.setTranslation(bottomPoint);
|
||||||
|
|
|
@ -12,7 +12,7 @@
|
||||||
#include "AvatarRig.h"
|
#include "AvatarRig.h"
|
||||||
|
|
||||||
/// Updates the state of the joint at the specified index.
|
/// Updates the state of the joint at the specified index.
|
||||||
void AvatarRig::updateJointState(int index, glm::mat4 parentTransform) {
|
void AvatarRig::updateJointState(int index, glm::mat4 rootTransform) {
|
||||||
if (index < 0 && index >= _jointStates.size()) {
|
if (index < 0 && index >= _jointStates.size()) {
|
||||||
return; // bail
|
return; // bail
|
||||||
}
|
}
|
||||||
|
@ -21,7 +21,7 @@ void AvatarRig::updateJointState(int index, glm::mat4 parentTransform) {
|
||||||
// compute model transforms
|
// compute model transforms
|
||||||
if (index == _rootJointIndex) {
|
if (index == _rootJointIndex) {
|
||||||
// we always zero-out the translation part of an avatar's root join-transform.
|
// we always zero-out the translation part of an avatar's root join-transform.
|
||||||
state.computeTransform(parentTransform);
|
state.computeTransform(rootTransform);
|
||||||
clearJointTransformTranslation(index);
|
clearJointTransformTranslation(index);
|
||||||
} else {
|
} else {
|
||||||
// guard against out-of-bounds access to _jointStates
|
// guard against out-of-bounds access to _jointStates
|
||||||
|
|
|
@ -21,7 +21,7 @@ class AvatarRig : public Rig {
|
||||||
|
|
||||||
public:
|
public:
|
||||||
~AvatarRig() {}
|
~AvatarRig() {}
|
||||||
virtual void updateJointState(int index, glm::mat4 parentTransform);
|
virtual void updateJointState(int index, glm::mat4 rootTransform);
|
||||||
virtual void setHandPosition(int jointIndex, const glm::vec3& position, const glm::quat& rotation,
|
virtual void setHandPosition(int jointIndex, const glm::vec3& position, const glm::quat& rotation,
|
||||||
float scale, float priority);
|
float scale, float priority);
|
||||||
};
|
};
|
||||||
|
|
|
@ -12,13 +12,13 @@
|
||||||
#include "EntityRig.h"
|
#include "EntityRig.h"
|
||||||
|
|
||||||
/// Updates the state of the joint at the specified index.
|
/// Updates the state of the joint at the specified index.
|
||||||
void EntityRig::updateJointState(int index, glm::mat4 parentTransform) {
|
void EntityRig::updateJointState(int index, glm::mat4 rootTransform) {
|
||||||
JointState& state = _jointStates[index];
|
JointState& state = _jointStates[index];
|
||||||
|
|
||||||
// compute model transforms
|
// compute model transforms
|
||||||
int parentIndex = state.getParentIndex();
|
int parentIndex = state.getParentIndex();
|
||||||
if (parentIndex == -1) {
|
if (parentIndex == -1) {
|
||||||
state.computeTransform(parentTransform);
|
state.computeTransform(rootTransform);
|
||||||
} else {
|
} else {
|
||||||
// guard against out-of-bounds access to _jointStates
|
// guard against out-of-bounds access to _jointStates
|
||||||
if (parentIndex >= 0 && parentIndex < _jointStates.size()) {
|
if (parentIndex >= 0 && parentIndex < _jointStates.size()) {
|
||||||
|
|
|
@ -21,7 +21,7 @@ class EntityRig : public Rig {
|
||||||
|
|
||||||
public:
|
public:
|
||||||
~EntityRig() {}
|
~EntityRig() {}
|
||||||
virtual void updateJointState(int index, glm::mat4 parentTransform);
|
virtual void updateJointState(int index, glm::mat4 rootTransform);
|
||||||
virtual void setHandPosition(int jointIndex, const glm::vec3& position, const glm::quat& rotation,
|
virtual void setHandPosition(int jointIndex, const glm::vec3& position, const glm::quat& rotation,
|
||||||
float scale, float priority) {}
|
float scale, float priority) {}
|
||||||
};
|
};
|
||||||
|
|
|
@ -153,6 +153,24 @@ void JointState::setRotationInBindFrame(const glm::quat& rotation, float priorit
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void JointState::setRotationInModelFrame(const glm::quat& rotationInModelFrame, float priority, bool constrain) {
|
||||||
|
// rotation is from bind- to model-frame
|
||||||
|
if (priority >= _animationPriority) {
|
||||||
|
glm::quat parentRotation = computeParentRotation();
|
||||||
|
|
||||||
|
// R = Rp * Rpre * r * Rpost
|
||||||
|
// R' = Rp * Rpre * r' * Rpost
|
||||||
|
// r' = (Rp * Rpre)^ * R' * Rpost^
|
||||||
|
glm::quat targetRotation = glm::inverse(parentRotation * _preRotation) * rotationInModelFrame * glm::inverse(_postRotation);
|
||||||
|
if (constrain && _constraint) {
|
||||||
|
_constraint->softClamp(targetRotation, _rotationInConstrainedFrame, 0.5f);
|
||||||
|
}
|
||||||
|
_rotationInConstrainedFrame = glm::normalize(targetRotation);
|
||||||
|
_transformChanged = true;
|
||||||
|
_animationPriority = priority;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void JointState::clearTransformTranslation() {
|
void JointState::clearTransformTranslation() {
|
||||||
_transform[3][0] = 0.0f;
|
_transform[3][0] = 0.0f;
|
||||||
_transform[3][1] = 0.0f;
|
_transform[3][1] = 0.0f;
|
||||||
|
|
|
@ -82,6 +82,11 @@ public:
|
||||||
/// NOTE: the JointState's model-frame transform/rotation are NOT updated!
|
/// NOTE: the JointState's model-frame transform/rotation are NOT updated!
|
||||||
void setRotationInBindFrame(const glm::quat& rotation, float priority, bool constrain = false);
|
void setRotationInBindFrame(const glm::quat& rotation, float priority, bool constrain = false);
|
||||||
|
|
||||||
|
/// \param rotationInModelRame is in model-frame
|
||||||
|
/// computes and sets new _rotationInConstrainedFrame to match rotationInModelFrame
|
||||||
|
/// NOTE: the JointState's model-frame transform/rotation are NOT updated!
|
||||||
|
void setRotationInModelFrame(const glm::quat& rotationInModelFrame, float priority, bool constrain);
|
||||||
|
|
||||||
void setRotationInConstrainedFrame(glm::quat targetRotation, float priority, bool constrain = false, float mix = 1.0f);
|
void setRotationInConstrainedFrame(glm::quat targetRotation, float priority, bool constrain = false, float mix = 1.0f);
|
||||||
void setVisibleRotationInConstrainedFrame(const glm::quat& targetRotation);
|
void setVisibleRotationInConstrainedFrame(const glm::quat& targetRotation);
|
||||||
const glm::quat& getRotationInConstrainedFrame() const { return _rotationInConstrainedFrame; }
|
const glm::quat& getRotationInConstrainedFrame() const { return _rotationInConstrainedFrame; }
|
||||||
|
|
|
@ -185,14 +185,14 @@ void Rig::deleteAnimations() {
|
||||||
_animationHandles.clear();
|
_animationHandles.clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
float Rig::initJointStates(QVector<JointState> states, glm::mat4 parentTransform,
|
void Rig::initJointStates(QVector<JointState> states, glm::mat4 rootTransform,
|
||||||
int rootJointIndex,
|
int rootJointIndex,
|
||||||
int leftHandJointIndex,
|
int leftHandJointIndex,
|
||||||
int leftElbowJointIndex,
|
int leftElbowJointIndex,
|
||||||
int leftShoulderJointIndex,
|
int leftShoulderJointIndex,
|
||||||
int rightHandJointIndex,
|
int rightHandJointIndex,
|
||||||
int rightElbowJointIndex,
|
int rightElbowJointIndex,
|
||||||
int rightShoulderJointIndex) {
|
int rightShoulderJointIndex) {
|
||||||
_jointStates = states;
|
_jointStates = states;
|
||||||
|
|
||||||
_rootJointIndex = rootJointIndex;
|
_rootJointIndex = rootJointIndex;
|
||||||
|
@ -203,22 +203,15 @@ float Rig::initJointStates(QVector<JointState> states, glm::mat4 parentTransform
|
||||||
_rightElbowJointIndex = rightElbowJointIndex;
|
_rightElbowJointIndex = rightElbowJointIndex;
|
||||||
_rightShoulderJointIndex = rightShoulderJointIndex;
|
_rightShoulderJointIndex = rightShoulderJointIndex;
|
||||||
|
|
||||||
initJointTransforms(parentTransform);
|
initJointTransforms(rootTransform);
|
||||||
|
|
||||||
int numStates = _jointStates.size();
|
int numStates = _jointStates.size();
|
||||||
float radius = 0.0f;
|
|
||||||
for (int i = 0; i < numStates; ++i) {
|
for (int i = 0; i < numStates; ++i) {
|
||||||
float distance = glm::length(_jointStates[i].getPosition());
|
|
||||||
if (distance > radius) {
|
|
||||||
radius = distance;
|
|
||||||
}
|
|
||||||
_jointStates[i].buildConstraint();
|
_jointStates[i].buildConstraint();
|
||||||
}
|
}
|
||||||
for (int i = 0; i < _jointStates.size(); i++) {
|
for (int i = 0; i < _jointStates.size(); i++) {
|
||||||
_jointStates[i].slaveVisibleTransform();
|
_jointStates[i].slaveVisibleTransform();
|
||||||
}
|
}
|
||||||
|
|
||||||
return radius;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// We could build and cache a dictionary, too....
|
// We could build and cache a dictionary, too....
|
||||||
|
@ -233,14 +226,14 @@ int Rig::indexOfJoint(const QString& jointName) {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
void Rig::initJointTransforms(glm::mat4 parentTransform) {
|
void Rig::initJointTransforms(glm::mat4 rootTransform) {
|
||||||
// compute model transforms
|
// compute model transforms
|
||||||
int numStates = _jointStates.size();
|
int numStates = _jointStates.size();
|
||||||
for (int i = 0; i < numStates; ++i) {
|
for (int i = 0; i < numStates; ++i) {
|
||||||
JointState& state = _jointStates[i];
|
JointState& state = _jointStates[i];
|
||||||
int parentIndex = state.getParentIndex();
|
int parentIndex = state.getParentIndex();
|
||||||
if (parentIndex == -1) {
|
if (parentIndex == -1) {
|
||||||
state.initTransform(parentTransform);
|
state.initTransform(rootTransform);
|
||||||
} else {
|
} else {
|
||||||
const JointState& parentState = _jointStates.at(parentIndex);
|
const JointState& parentState = _jointStates.at(parentIndex);
|
||||||
state.initTransform(parentState.getTransform());
|
state.initTransform(parentState.getTransform());
|
||||||
|
@ -448,7 +441,7 @@ void Rig::computeMotionAnimationState(float deltaTime, const glm::vec3& worldPos
|
||||||
_lastPosition = worldPosition;
|
_lastPosition = worldPosition;
|
||||||
}
|
}
|
||||||
|
|
||||||
void Rig::updateAnimations(float deltaTime, glm::mat4 parentTransform) {
|
void Rig::updateAnimations(float deltaTime, glm::mat4 rootTransform) {
|
||||||
|
|
||||||
// First normalize the fades so that they sum to 1.0.
|
// First normalize the fades so that they sum to 1.0.
|
||||||
// update the fade data in each animation (not normalized as they are an independent propert of animation)
|
// update the fade data in each animation (not normalized as they are an independent propert of animation)
|
||||||
|
@ -494,7 +487,7 @@ void Rig::updateAnimations(float deltaTime, glm::mat4 parentTransform) {
|
||||||
}
|
}
|
||||||
|
|
||||||
for (int i = 0; i < _jointStates.size(); i++) {
|
for (int i = 0; i < _jointStates.size(); i++) {
|
||||||
updateJointState(i, parentTransform);
|
updateJointState(i, rootTransform);
|
||||||
}
|
}
|
||||||
for (int i = 0; i < _jointStates.size(); i++) {
|
for (int i = 0; i < _jointStates.size(); i++) {
|
||||||
_jointStates[i].resetTransformChanged();
|
_jointStates[i].resetTransformChanged();
|
||||||
|
@ -503,7 +496,7 @@ void Rig::updateAnimations(float deltaTime, glm::mat4 parentTransform) {
|
||||||
|
|
||||||
bool Rig::setJointPosition(int jointIndex, const glm::vec3& position, const glm::quat& rotation, bool useRotation,
|
bool Rig::setJointPosition(int jointIndex, const glm::vec3& position, const glm::quat& rotation, bool useRotation,
|
||||||
int lastFreeIndex, bool allIntermediatesFree, const glm::vec3& alignment, float priority,
|
int lastFreeIndex, bool allIntermediatesFree, const glm::vec3& alignment, float priority,
|
||||||
const QVector<int>& freeLineage, glm::mat4 parentTransform) {
|
const QVector<int>& freeLineage, glm::mat4 rootTransform) {
|
||||||
if (jointIndex == -1 || _jointStates.isEmpty()) {
|
if (jointIndex == -1 || _jointStates.isEmpty()) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
@ -555,7 +548,7 @@ bool Rig::setJointPosition(int jointIndex, const glm::vec3& position, const glm:
|
||||||
glm::vec3 positionSum;
|
glm::vec3 positionSum;
|
||||||
for (int k = j - 1; k > 0; k--) {
|
for (int k = j - 1; k > 0; k--) {
|
||||||
int index = freeLineage.at(k);
|
int index = freeLineage.at(k);
|
||||||
updateJointState(index, parentTransform);
|
updateJointState(index, rootTransform);
|
||||||
positionSum += extractTranslation(_jointStates.at(index).getTransform());
|
positionSum += extractTranslation(_jointStates.at(index).getTransform());
|
||||||
}
|
}
|
||||||
glm::vec3 projectedCenterOfMass = glm::cross(jointVector,
|
glm::vec3 projectedCenterOfMass = glm::cross(jointVector,
|
||||||
|
@ -578,15 +571,15 @@ bool Rig::setJointPosition(int jointIndex, const glm::vec3& position, const glm:
|
||||||
|
|
||||||
// now update the joint states from the top
|
// now update the joint states from the top
|
||||||
for (int j = freeLineage.size() - 1; j >= 0; j--) {
|
for (int j = freeLineage.size() - 1; j >= 0; j--) {
|
||||||
updateJointState(freeLineage.at(j), parentTransform);
|
updateJointState(freeLineage.at(j), rootTransform);
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
void Rig::inverseKinematics(int endIndex, glm::vec3 targetPosition, const glm::quat& targetRotation, float priority,
|
void Rig::inverseKinematics(int endIndex, glm::vec3 targetPosition, const glm::quat& targetRotation, float priority,
|
||||||
const QVector<int>& freeLineage, glm::mat4 parentTransform) {
|
const QVector<int>& freeLineage, glm::mat4 rootTransform) {
|
||||||
// NOTE: targetRotation is from bind- to model-frame
|
// NOTE: targetRotation is from in model-frame
|
||||||
|
|
||||||
if (endIndex == -1 || _jointStates.isEmpty()) {
|
if (endIndex == -1 || _jointStates.isEmpty()) {
|
||||||
return;
|
return;
|
||||||
|
@ -604,12 +597,27 @@ void Rig::inverseKinematics(int endIndex, glm::vec3 targetPosition, const glm::q
|
||||||
const JointState& state = _jointStates.at(index);
|
const JointState& state = _jointStates.at(index);
|
||||||
int parentIndex = state.getParentIndex();
|
int parentIndex = state.getParentIndex();
|
||||||
if (parentIndex == -1) {
|
if (parentIndex == -1) {
|
||||||
topParentTransform = parentTransform;
|
topParentTransform = rootTransform;
|
||||||
} else {
|
} else {
|
||||||
topParentTransform = _jointStates[parentIndex].getTransform();
|
topParentTransform = _jointStates[parentIndex].getTransform();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// relax toward default rotation
|
||||||
|
// NOTE: ideally this should use dt and a relaxation timescale to compute how much to relax
|
||||||
|
for (int j = 0; j < numFree; j++) {
|
||||||
|
int nextIndex = freeLineage.at(j);
|
||||||
|
JointState& nextState = _jointStates[nextIndex];
|
||||||
|
if (! nextState.getIsFree()) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Apply the zero rotationDelta, but use mixRotationDelta() which blends a bit of the default pose
|
||||||
|
// in the process. This provides stability to the IK solution for most models.
|
||||||
|
float mixFactor = 0.08f;
|
||||||
|
nextState.mixRotationDelta(glm::quat(), mixFactor, priority);
|
||||||
|
}
|
||||||
|
|
||||||
// this is a cyclic coordinate descent algorithm: see
|
// this is a cyclic coordinate descent algorithm: see
|
||||||
// http://www.ryanjuckett.com/programming/animation/21-cyclic-coordinate-descent-in-2d
|
// http://www.ryanjuckett.com/programming/animation/21-cyclic-coordinate-descent-in-2d
|
||||||
|
|
||||||
|
@ -618,7 +626,7 @@ void Rig::inverseKinematics(int endIndex, glm::vec3 targetPosition, const glm::q
|
||||||
glm::vec3 endPosition = endState.getPosition();
|
glm::vec3 endPosition = endState.getPosition();
|
||||||
float distanceToGo = glm::distance(targetPosition, endPosition);
|
float distanceToGo = glm::distance(targetPosition, endPosition);
|
||||||
|
|
||||||
const int MAX_ITERATION_COUNT = 2;
|
const int MAX_ITERATION_COUNT = 3;
|
||||||
const float ACCEPTABLE_IK_ERROR = 0.005f; // 5mm
|
const float ACCEPTABLE_IK_ERROR = 0.005f; // 5mm
|
||||||
int numIterations = 0;
|
int numIterations = 0;
|
||||||
do {
|
do {
|
||||||
|
@ -656,7 +664,7 @@ void Rig::inverseKinematics(int endIndex, glm::vec3 targetPosition, const glm::q
|
||||||
|
|
||||||
float gravityAngle = glm::angle(gravityDelta);
|
float gravityAngle = glm::angle(gravityDelta);
|
||||||
const float MIN_GRAVITY_ANGLE = 0.1f;
|
const float MIN_GRAVITY_ANGLE = 0.1f;
|
||||||
float mixFactor = 0.5f;
|
float mixFactor = 0.1f;
|
||||||
if (gravityAngle < MIN_GRAVITY_ANGLE) {
|
if (gravityAngle < MIN_GRAVITY_ANGLE) {
|
||||||
// the final rotation is a mix of the two
|
// the final rotation is a mix of the two
|
||||||
mixFactor = 0.5f * gravityAngle / MIN_GRAVITY_ANGLE;
|
mixFactor = 0.5f * gravityAngle / MIN_GRAVITY_ANGLE;
|
||||||
|
@ -664,11 +672,10 @@ void Rig::inverseKinematics(int endIndex, glm::vec3 targetPosition, const glm::q
|
||||||
deltaRotation = safeMix(deltaRotation, gravityDelta, mixFactor);
|
deltaRotation = safeMix(deltaRotation, gravityDelta, mixFactor);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Apply the rotation, but use mixRotationDelta() which blends a bit of the default pose
|
// Apply the rotation delta.
|
||||||
// in the process. This provides stability to the IK solution for most models.
|
|
||||||
glm::quat oldNextRotation = nextState.getRotation();
|
glm::quat oldNextRotation = nextState.getRotation();
|
||||||
float mixFactor = 0.03f;
|
float mixFactor = 0.05f;
|
||||||
nextState.mixRotationDelta(deltaRotation, mixFactor, priority);
|
nextState.applyRotationDelta(deltaRotation, mixFactor, priority);
|
||||||
|
|
||||||
// measure the result of the rotation which may have been modified by
|
// measure the result of the rotation which may have been modified by
|
||||||
// blending and constraints
|
// blending and constraints
|
||||||
|
@ -687,10 +694,10 @@ void Rig::inverseKinematics(int endIndex, glm::vec3 targetPosition, const glm::q
|
||||||
// measure our success
|
// measure our success
|
||||||
endPosition = endState.getPosition();
|
endPosition = endState.getPosition();
|
||||||
distanceToGo = glm::distance(targetPosition, endPosition);
|
distanceToGo = glm::distance(targetPosition, endPosition);
|
||||||
} while (numIterations < MAX_ITERATION_COUNT && distanceToGo < ACCEPTABLE_IK_ERROR);
|
} while (numIterations < MAX_ITERATION_COUNT && distanceToGo > ACCEPTABLE_IK_ERROR);
|
||||||
|
|
||||||
// set final rotation of the end joint
|
// set final rotation of the end joint
|
||||||
endState.setRotationInBindFrame(targetRotation, priority, true);
|
endState.setRotationInModelFrame(targetRotation, priority, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool Rig::restoreJointPosition(int jointIndex, float fraction, float priority, const QVector<int>& freeLineage) {
|
bool Rig::restoreJointPosition(int jointIndex, float fraction, float priority, const QVector<int>& freeLineage) {
|
||||||
|
|
|
@ -92,19 +92,19 @@ public:
|
||||||
float priority = 1.0f, bool loop = false, bool hold = false, float firstFrame = 0.0f,
|
float priority = 1.0f, bool loop = false, bool hold = false, float firstFrame = 0.0f,
|
||||||
float lastFrame = FLT_MAX, const QStringList& maskedJoints = QStringList(), bool startAutomatically = false);
|
float lastFrame = FLT_MAX, const QStringList& maskedJoints = QStringList(), bool startAutomatically = false);
|
||||||
|
|
||||||
float initJointStates(QVector<JointState> states, glm::mat4 parentTransform,
|
void initJointStates(QVector<JointState> states, glm::mat4 rootTransform,
|
||||||
int rootJointIndex,
|
int rootJointIndex,
|
||||||
int leftHandJointIndex,
|
int leftHandJointIndex,
|
||||||
int leftElbowJointIndex,
|
int leftElbowJointIndex,
|
||||||
int leftShoulderJointIndex,
|
int leftShoulderJointIndex,
|
||||||
int rightHandJointIndex,
|
int rightHandJointIndex,
|
||||||
int rightElbowJointIndex,
|
int rightElbowJointIndex,
|
||||||
int rightShoulderJointIndex);
|
int rightShoulderJointIndex);
|
||||||
bool jointStatesEmpty() { return _jointStates.isEmpty(); };
|
bool jointStatesEmpty() { return _jointStates.isEmpty(); };
|
||||||
int getJointStateCount() const { return _jointStates.size(); }
|
int getJointStateCount() const { return _jointStates.size(); }
|
||||||
int indexOfJoint(const QString& jointName) ;
|
int indexOfJoint(const QString& jointName) ;
|
||||||
|
|
||||||
void initJointTransforms(glm::mat4 parentTransform);
|
void initJointTransforms(glm::mat4 rootTransform);
|
||||||
void clearJointTransformTranslation(int jointIndex);
|
void clearJointTransformTranslation(int jointIndex);
|
||||||
void reset(const QVector<FBXJoint>& fbxJoints);
|
void reset(const QVector<FBXJoint>& fbxJoints);
|
||||||
bool getJointStateRotation(int index, glm::quat& rotation) const;
|
bool getJointStateRotation(int index, glm::quat& rotation) const;
|
||||||
|
@ -135,12 +135,12 @@ public:
|
||||||
// Start or stop animations as needed.
|
// Start or stop animations as needed.
|
||||||
void computeMotionAnimationState(float deltaTime, const glm::vec3& worldPosition, const glm::vec3& worldVelocity, const glm::quat& worldRotation);
|
void computeMotionAnimationState(float deltaTime, const glm::vec3& worldPosition, const glm::vec3& worldVelocity, const glm::quat& worldRotation);
|
||||||
// Regardless of who started the animations or how many, update the joints.
|
// Regardless of who started the animations or how many, update the joints.
|
||||||
void updateAnimations(float deltaTime, glm::mat4 parentTransform);
|
void updateAnimations(float deltaTime, glm::mat4 rootTransform);
|
||||||
bool setJointPosition(int jointIndex, const glm::vec3& position, const glm::quat& rotation, bool useRotation,
|
bool setJointPosition(int jointIndex, const glm::vec3& position, const glm::quat& rotation, bool useRotation,
|
||||||
int lastFreeIndex, bool allIntermediatesFree, const glm::vec3& alignment, float priority,
|
int lastFreeIndex, bool allIntermediatesFree, const glm::vec3& alignment, float priority,
|
||||||
const QVector<int>& freeLineage, glm::mat4 parentTransform);
|
const QVector<int>& freeLineage, glm::mat4 rootTransform);
|
||||||
void inverseKinematics(int endIndex, glm::vec3 targetPosition, const glm::quat& targetRotation, float priority,
|
void inverseKinematics(int endIndex, glm::vec3 targetPosition, const glm::quat& targetRotation, float priority,
|
||||||
const QVector<int>& freeLineage, glm::mat4 parentTransform);
|
const QVector<int>& freeLineage, glm::mat4 rootTransform);
|
||||||
bool restoreJointPosition(int jointIndex, float fraction, float priority, const QVector<int>& freeLineage);
|
bool restoreJointPosition(int jointIndex, float fraction, float priority, const QVector<int>& freeLineage);
|
||||||
float getLimbLength(int jointIndex, const QVector<int>& freeLineage,
|
float getLimbLength(int jointIndex, const QVector<int>& freeLineage,
|
||||||
const glm::vec3 scale, const QVector<FBXJoint>& fbxJoints) const;
|
const glm::vec3 scale, const QVector<FBXJoint>& fbxJoints) const;
|
||||||
|
@ -152,7 +152,7 @@ public:
|
||||||
glm::quat getJointDefaultRotationInParentFrame(int jointIndex);
|
glm::quat getJointDefaultRotationInParentFrame(int jointIndex);
|
||||||
void updateVisibleJointStates();
|
void updateVisibleJointStates();
|
||||||
|
|
||||||
virtual void updateJointState(int index, glm::mat4 parentTransform) = 0;
|
virtual void updateJointState(int index, glm::mat4 rootTransform) = 0;
|
||||||
|
|
||||||
void setEnableRig(bool isEnabled) { _enableRig = isEnabled; }
|
void setEnableRig(bool isEnabled) { _enableRig = isEnabled; }
|
||||||
|
|
||||||
|
|
|
@ -104,10 +104,10 @@ bool OpenGLDisplayPlugin::eventFilter(QObject* receiver, QEvent* event) {
|
||||||
if (QCoreApplication::sendEvent(QCoreApplication::instance(), event)) {
|
if (QCoreApplication::sendEvent(QCoreApplication::instance(), event)) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
break;
|
||||||
default:
|
default:
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -12,13 +12,18 @@
|
||||||
#ifndef hifi_RenderablePolyVoxEntityItem_h
|
#ifndef hifi_RenderablePolyVoxEntityItem_h
|
||||||
#define hifi_RenderablePolyVoxEntityItem_h
|
#define hifi_RenderablePolyVoxEntityItem_h
|
||||||
|
|
||||||
|
#include <QSemaphore>
|
||||||
|
#include <atomic>
|
||||||
|
|
||||||
#include <PolyVoxCore/SimpleVolume.h>
|
#include <PolyVoxCore/SimpleVolume.h>
|
||||||
|
#include <PolyVoxCore/Raycast.h>
|
||||||
|
|
||||||
#include <TextureCache.h>
|
#include <TextureCache.h>
|
||||||
|
|
||||||
#include "PolyVoxEntityItem.h"
|
#include "PolyVoxEntityItem.h"
|
||||||
#include "RenderableDebugableEntityItem.h"
|
#include "RenderableDebugableEntityItem.h"
|
||||||
#include "RenderableEntityItem.h"
|
#include "RenderableEntityItem.h"
|
||||||
|
#include "gpu/Context.h"
|
||||||
|
|
||||||
class PolyVoxPayload {
|
class PolyVoxPayload {
|
||||||
public:
|
public:
|
||||||
|
@ -56,19 +61,16 @@ public:
|
||||||
virtual uint8_t getVoxel(int x, int y, int z);
|
virtual uint8_t getVoxel(int x, int y, int z);
|
||||||
virtual bool setVoxel(int x, int y, int z, uint8_t toValue);
|
virtual bool setVoxel(int x, int y, int z, uint8_t toValue);
|
||||||
|
|
||||||
bool updateOnCount(int x, int y, int z, uint8_t new_value);
|
|
||||||
|
|
||||||
void render(RenderArgs* args);
|
void render(RenderArgs* args);
|
||||||
virtual bool supportsDetailedRayIntersection() const { return true; }
|
virtual bool supportsDetailedRayIntersection() const { return true; }
|
||||||
virtual bool findDetailedRayIntersection(const glm::vec3& origin, const glm::vec3& direction,
|
virtual bool findDetailedRayIntersection(const glm::vec3& origin, const glm::vec3& direction,
|
||||||
bool& keepSearching, OctreeElement*& element, float& distance, BoxFace& face,
|
bool& keepSearching, OctreeElement*& element, float& distance, BoxFace& face,
|
||||||
void** intersectedObject, bool precisionPicking) const;
|
void** intersectedObject, bool precisionPicking) const;
|
||||||
|
|
||||||
void getModel();
|
|
||||||
|
|
||||||
virtual void setVoxelData(QByteArray voxelData);
|
virtual void setVoxelData(QByteArray voxelData);
|
||||||
|
|
||||||
virtual void setVoxelVolumeSize(glm::vec3 voxelVolumeSize);
|
virtual void setVoxelVolumeSize(glm::vec3 voxelVolumeSize);
|
||||||
|
virtual void setVoxelSurfaceStyle(PolyVoxSurfaceStyle voxelSurfaceStyle);
|
||||||
|
|
||||||
glm::vec3 getSurfacePositionAdjustment() const;
|
glm::vec3 getSurfacePositionAdjustment() const;
|
||||||
glm::mat4 voxelToWorldMatrix() const;
|
glm::mat4 voxelToWorldMatrix() const;
|
||||||
glm::mat4 worldToVoxelMatrix() const;
|
glm::mat4 worldToVoxelMatrix() const;
|
||||||
|
@ -103,33 +105,48 @@ public:
|
||||||
std::shared_ptr<render::Scene> scene,
|
std::shared_ptr<render::Scene> scene,
|
||||||
render::PendingChanges& pendingChanges);
|
render::PendingChanges& pendingChanges);
|
||||||
|
|
||||||
protected:
|
|
||||||
virtual void updateVoxelSurfaceStyle(PolyVoxSurfaceStyle voxelSurfaceStyle);
|
|
||||||
|
|
||||||
private:
|
private:
|
||||||
// The PolyVoxEntityItem class has _voxelData which contains dimensions and compressed voxel data. The dimensions
|
// The PolyVoxEntityItem class has _voxelData which contains dimensions and compressed voxel data. The dimensions
|
||||||
// may not match _voxelVolumeSize.
|
// may not match _voxelVolumeSize.
|
||||||
|
|
||||||
bool setVoxelInternal(int x, int y, int z, uint8_t toValue);
|
model::MeshPointer _mesh;
|
||||||
void compressVolumeData();
|
bool _meshDirty; // does collision-shape need to be recomputed?
|
||||||
void decompressVolumeData();
|
mutable QReadWriteLock _meshLock{QReadWriteLock::Recursive};
|
||||||
void clearEdges();
|
|
||||||
|
|
||||||
PolyVox::SimpleVolume<uint8_t>* _volData = nullptr;
|
|
||||||
model::Geometry _modelGeometry;
|
|
||||||
bool _needsModelReload = true;
|
|
||||||
|
|
||||||
QVector<QVector<glm::vec3>> _points; // XXX
|
|
||||||
|
|
||||||
NetworkTexturePointer _xTexture;
|
NetworkTexturePointer _xTexture;
|
||||||
NetworkTexturePointer _yTexture;
|
NetworkTexturePointer _yTexture;
|
||||||
NetworkTexturePointer _zTexture;
|
NetworkTexturePointer _zTexture;
|
||||||
|
|
||||||
int _onCount = 0; // how many non-zero voxels are in _volData
|
|
||||||
|
|
||||||
const int MATERIAL_GPU_SLOT = 3;
|
const int MATERIAL_GPU_SLOT = 3;
|
||||||
render::ItemID _myItem;
|
render::ItemID _myItem;
|
||||||
static gpu::PipelinePointer _pipeline;
|
static gpu::PipelinePointer _pipeline;
|
||||||
|
|
||||||
|
ShapeInfo _shapeInfo;
|
||||||
|
mutable QReadWriteLock _shapeInfoLock;
|
||||||
|
|
||||||
|
PolyVox::SimpleVolume<uint8_t>* _volData = nullptr;
|
||||||
|
mutable QReadWriteLock _volDataLock{QReadWriteLock::Recursive}; // lock for _volData
|
||||||
|
bool _volDataDirty = false; // does getMesh need to be called?
|
||||||
|
int _onCount; // how many non-zero voxels are in _volData
|
||||||
|
|
||||||
|
bool inUserBounds(const PolyVox::SimpleVolume<uint8_t>* vol, PolyVoxEntityItem::PolyVoxSurfaceStyle surfaceStyle,
|
||||||
|
int x, int y, int z);
|
||||||
|
uint8_t getVoxelInternal(int x, int y, int z);
|
||||||
|
bool setVoxelInternal(int x, int y, int z, uint8_t toValue);
|
||||||
|
bool updateOnCount(int x, int y, int z, uint8_t toValue);
|
||||||
|
PolyVox::RaycastResult doRayCast(glm::vec4 originInVoxel, glm::vec4 farInVoxel, glm::vec4& result) const;
|
||||||
|
|
||||||
|
// these are run off the main thread
|
||||||
|
void decompressVolumeData();
|
||||||
|
void decompressVolumeDataAsync();
|
||||||
|
void compressVolumeDataAndSendEditPacket();
|
||||||
|
void compressVolumeDataAndSendEditPacketAsync();
|
||||||
|
void getMesh();
|
||||||
|
void getMeshAsync();
|
||||||
|
void computeShapeInfoWorker();
|
||||||
|
void computeShapeInfoWorkerAsync();
|
||||||
|
|
||||||
|
QSemaphore _threadRunning{1};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -431,23 +431,10 @@ bool EntityScriptingInterface::setVoxels(QUuid entityID,
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
auto now = usecTimestampNow();
|
|
||||||
|
|
||||||
auto polyVoxEntity = std::dynamic_pointer_cast<PolyVoxEntityItem>(entity);
|
auto polyVoxEntity = std::dynamic_pointer_cast<PolyVoxEntityItem>(entity);
|
||||||
_entityTree->lockForWrite();
|
_entityTree->lockForWrite();
|
||||||
bool result = actor(*polyVoxEntity);
|
bool result = actor(*polyVoxEntity);
|
||||||
entity->setLastEdited(now);
|
|
||||||
entity->setLastBroadcast(now);
|
|
||||||
_entityTree->unlock();
|
_entityTree->unlock();
|
||||||
|
|
||||||
_entityTree->lockForRead();
|
|
||||||
EntityItemProperties properties = entity->getProperties();
|
|
||||||
_entityTree->unlock();
|
|
||||||
|
|
||||||
properties.setVoxelDataDirty();
|
|
||||||
properties.setLastEdited(now);
|
|
||||||
|
|
||||||
queueEntityMessage(PacketType::EntityEdit, entityID, properties);
|
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -131,20 +131,20 @@ void ParticleEffectEntityItem::computeAndUpdateDimensions() {
|
||||||
|
|
||||||
float maxVelocityX = fabsf(_velocity.x) + _velocitySpread.x;
|
float maxVelocityX = fabsf(_velocity.x) + _velocitySpread.x;
|
||||||
float maxAccelerationX = fabsf(_acceleration.x) + _accelerationSpread.x;
|
float maxAccelerationX = fabsf(_acceleration.x) + _accelerationSpread.x;
|
||||||
float maxXDistance = (maxVelocityX * time) + (0.5 * maxAccelerationX * time * time);
|
float maxXDistance = (maxVelocityX * time) + (0.5f * maxAccelerationX * time * time);
|
||||||
|
|
||||||
float maxVelocityY = fabs(_velocity.y) + _velocitySpread.y;
|
float maxVelocityY = fabsf(_velocity.y) + _velocitySpread.y;
|
||||||
float maxAccelerationY = fabsf(_acceleration.y) + _accelerationSpread.y;
|
float maxAccelerationY = fabsf(_acceleration.y) + _accelerationSpread.y;
|
||||||
float maxYDistance = (maxVelocityY * time) + (0.5 * maxAccelerationY * time * time);
|
float maxYDistance = (maxVelocityY * time) + (0.5f * maxAccelerationY * time * time);
|
||||||
|
|
||||||
float maxVelocityZ = fabsf(_velocity.z) + _velocitySpread.z;
|
float maxVelocityZ = fabsf(_velocity.z) + _velocitySpread.z;
|
||||||
float maxAccelerationZ = fabsf(_acceleration.z) + _accelerationSpread.z;
|
float maxAccelerationZ = fabsf(_acceleration.z) + _accelerationSpread.z;
|
||||||
float maxZDistance = (maxVelocityZ * time) + (0.5 * maxAccelerationZ * time * time);
|
float maxZDistance = (maxVelocityZ * time) + (0.5f * maxAccelerationZ * time * time);
|
||||||
|
|
||||||
float maxDistance = std::max(maxXDistance, std::max(maxYDistance, maxZDistance));
|
float maxDistance = std::max(maxXDistance, std::max(maxYDistance, maxZDistance));
|
||||||
|
|
||||||
//times 2 because dimensions are diameters not radii
|
//times 2 because dimensions are diameters not radii
|
||||||
glm::vec3 dims(2.0 * maxDistance);
|
glm::vec3 dims(2.0f * maxDistance);
|
||||||
EntityItem::setDimensions(dims);
|
EntityItem::setDimensions(dims);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -25,7 +25,7 @@ const glm::vec3 PolyVoxEntityItem::DEFAULT_VOXEL_VOLUME_SIZE = glm::vec3(32, 32,
|
||||||
const float PolyVoxEntityItem::MAX_VOXEL_DIMENSION = 128.0f;
|
const float PolyVoxEntityItem::MAX_VOXEL_DIMENSION = 128.0f;
|
||||||
const QByteArray PolyVoxEntityItem::DEFAULT_VOXEL_DATA(PolyVoxEntityItem::makeEmptyVoxelData());
|
const QByteArray PolyVoxEntityItem::DEFAULT_VOXEL_DATA(PolyVoxEntityItem::makeEmptyVoxelData());
|
||||||
const PolyVoxEntityItem::PolyVoxSurfaceStyle PolyVoxEntityItem::DEFAULT_VOXEL_SURFACE_STYLE =
|
const PolyVoxEntityItem::PolyVoxSurfaceStyle PolyVoxEntityItem::DEFAULT_VOXEL_SURFACE_STYLE =
|
||||||
PolyVoxEntityItem::SURFACE_MARCHING_CUBES;
|
PolyVoxEntityItem::SURFACE_EDGED_CUBIC;
|
||||||
const QString PolyVoxEntityItem::DEFAULT_X_TEXTURE_URL = QString("");
|
const QString PolyVoxEntityItem::DEFAULT_X_TEXTURE_URL = QString("");
|
||||||
const QString PolyVoxEntityItem::DEFAULT_Y_TEXTURE_URL = QString("");
|
const QString PolyVoxEntityItem::DEFAULT_Y_TEXTURE_URL = QString("");
|
||||||
const QString PolyVoxEntityItem::DEFAULT_Z_TEXTURE_URL = QString("");
|
const QString PolyVoxEntityItem::DEFAULT_Z_TEXTURE_URL = QString("");
|
||||||
|
@ -52,6 +52,7 @@ PolyVoxEntityItem::PolyVoxEntityItem(const EntityItemID& entityItemID, const Ent
|
||||||
EntityItem(entityItemID),
|
EntityItem(entityItemID),
|
||||||
_voxelVolumeSize(PolyVoxEntityItem::DEFAULT_VOXEL_VOLUME_SIZE),
|
_voxelVolumeSize(PolyVoxEntityItem::DEFAULT_VOXEL_VOLUME_SIZE),
|
||||||
_voxelData(PolyVoxEntityItem::DEFAULT_VOXEL_DATA),
|
_voxelData(PolyVoxEntityItem::DEFAULT_VOXEL_DATA),
|
||||||
|
_voxelDataDirty(true),
|
||||||
_voxelSurfaceStyle(PolyVoxEntityItem::DEFAULT_VOXEL_SURFACE_STYLE),
|
_voxelSurfaceStyle(PolyVoxEntityItem::DEFAULT_VOXEL_SURFACE_STYLE),
|
||||||
_xTextureURL(PolyVoxEntityItem::DEFAULT_X_TEXTURE_URL),
|
_xTextureURL(PolyVoxEntityItem::DEFAULT_X_TEXTURE_URL),
|
||||||
_yTextureURL(PolyVoxEntityItem::DEFAULT_Y_TEXTURE_URL),
|
_yTextureURL(PolyVoxEntityItem::DEFAULT_Y_TEXTURE_URL),
|
||||||
|
@ -66,7 +67,7 @@ void PolyVoxEntityItem::setVoxelVolumeSize(glm::vec3 voxelVolumeSize) {
|
||||||
assert((int)_voxelVolumeSize.y == _voxelVolumeSize.y);
|
assert((int)_voxelVolumeSize.y == _voxelVolumeSize.y);
|
||||||
assert((int)_voxelVolumeSize.z == _voxelVolumeSize.z);
|
assert((int)_voxelVolumeSize.z == _voxelVolumeSize.z);
|
||||||
|
|
||||||
_voxelVolumeSize = voxelVolumeSize;
|
_voxelVolumeSize = glm::vec3(roundf(voxelVolumeSize.x), roundf(voxelVolumeSize.y), roundf(voxelVolumeSize.z));
|
||||||
if (_voxelVolumeSize.x < 1) {
|
if (_voxelVolumeSize.x < 1) {
|
||||||
qDebug() << "PolyVoxEntityItem::setVoxelVolumeSize clamping x of" << _voxelVolumeSize.x << "to 1";
|
qDebug() << "PolyVoxEntityItem::setVoxelVolumeSize clamping x of" << _voxelVolumeSize.x << "to 1";
|
||||||
_voxelVolumeSize.x = 1;
|
_voxelVolumeSize.x = 1;
|
||||||
|
@ -185,9 +186,16 @@ void PolyVoxEntityItem::debugDump() const {
|
||||||
qCDebug(entities) << " getLastEdited:" << debugTime(getLastEdited(), now);
|
qCDebug(entities) << " getLastEdited:" << debugTime(getLastEdited(), now);
|
||||||
}
|
}
|
||||||
|
|
||||||
void PolyVoxEntityItem::setVoxelSurfaceStyle(PolyVoxSurfaceStyle voxelSurfaceStyle) {
|
void PolyVoxEntityItem::setVoxelData(QByteArray voxelData) {
|
||||||
if (voxelSurfaceStyle == _voxelSurfaceStyle) {
|
_voxelDataLock.lockForWrite();
|
||||||
return;
|
_voxelData = voxelData;
|
||||||
}
|
_voxelDataDirty = true;
|
||||||
updateVoxelSurfaceStyle(voxelSurfaceStyle);
|
_voxelDataLock.unlock();
|
||||||
|
}
|
||||||
|
|
||||||
|
const QByteArray PolyVoxEntityItem::getVoxelData() const {
|
||||||
|
_voxelDataLock.lockForRead();
|
||||||
|
auto result = _voxelData;
|
||||||
|
_voxelDataLock.unlock();
|
||||||
|
return result;
|
||||||
}
|
}
|
||||||
|
|
|
@ -52,8 +52,8 @@ class PolyVoxEntityItem : public EntityItem {
|
||||||
virtual void setVoxelVolumeSize(glm::vec3 voxelVolumeSize);
|
virtual void setVoxelVolumeSize(glm::vec3 voxelVolumeSize);
|
||||||
virtual const glm::vec3& getVoxelVolumeSize() const { return _voxelVolumeSize; }
|
virtual const glm::vec3& getVoxelVolumeSize() const { return _voxelVolumeSize; }
|
||||||
|
|
||||||
virtual void setVoxelData(QByteArray voxelData) { _voxelData = voxelData; }
|
virtual void setVoxelData(QByteArray voxelData);
|
||||||
virtual const QByteArray& getVoxelData() const { return _voxelData; }
|
virtual const QByteArray getVoxelData() const;
|
||||||
|
|
||||||
enum PolyVoxSurfaceStyle {
|
enum PolyVoxSurfaceStyle {
|
||||||
SURFACE_MARCHING_CUBES,
|
SURFACE_MARCHING_CUBES,
|
||||||
|
@ -62,7 +62,7 @@ class PolyVoxEntityItem : public EntityItem {
|
||||||
SURFACE_EDGED_MARCHING_CUBES
|
SURFACE_EDGED_MARCHING_CUBES
|
||||||
};
|
};
|
||||||
|
|
||||||
void setVoxelSurfaceStyle(PolyVoxSurfaceStyle voxelSurfaceStyle);
|
virtual void setVoxelSurfaceStyle(PolyVoxSurfaceStyle voxelSurfaceStyle) { _voxelSurfaceStyle = voxelSurfaceStyle; }
|
||||||
// this other version of setVoxelSurfaceStyle is needed for SET_ENTITY_PROPERTY_FROM_PROPERTIES
|
// this other version of setVoxelSurfaceStyle is needed for SET_ENTITY_PROPERTY_FROM_PROPERTIES
|
||||||
void setVoxelSurfaceStyle(uint16_t voxelSurfaceStyle) { setVoxelSurfaceStyle((PolyVoxSurfaceStyle) voxelSurfaceStyle); }
|
void setVoxelSurfaceStyle(uint16_t voxelSurfaceStyle) { setVoxelSurfaceStyle((PolyVoxSurfaceStyle) voxelSurfaceStyle); }
|
||||||
virtual PolyVoxSurfaceStyle getVoxelSurfaceStyle() const { return _voxelSurfaceStyle; }
|
virtual PolyVoxSurfaceStyle getVoxelSurfaceStyle() const { return _voxelSurfaceStyle; }
|
||||||
|
@ -104,12 +104,12 @@ class PolyVoxEntityItem : public EntityItem {
|
||||||
virtual const QString& getZTextureURL() const { return _zTextureURL; }
|
virtual const QString& getZTextureURL() const { return _zTextureURL; }
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
virtual void updateVoxelSurfaceStyle(PolyVoxSurfaceStyle voxelSurfaceStyle) {
|
|
||||||
_voxelSurfaceStyle = voxelSurfaceStyle;
|
|
||||||
}
|
|
||||||
|
|
||||||
glm::vec3 _voxelVolumeSize; // this is always 3 bytes
|
glm::vec3 _voxelVolumeSize; // this is always 3 bytes
|
||||||
|
|
||||||
|
mutable QReadWriteLock _voxelDataLock;
|
||||||
QByteArray _voxelData;
|
QByteArray _voxelData;
|
||||||
|
bool _voxelDataDirty;
|
||||||
|
|
||||||
PolyVoxSurfaceStyle _voxelSurfaceStyle;
|
PolyVoxSurfaceStyle _voxelSurfaceStyle;
|
||||||
|
|
||||||
QString _xTextureURL;
|
QString _xTextureURL;
|
||||||
|
|
|
@ -21,6 +21,10 @@
|
||||||
#include "SixenseManager.h"
|
#include "SixenseManager.h"
|
||||||
#include "UserActivityLogger.h"
|
#include "UserActivityLogger.h"
|
||||||
|
|
||||||
|
#ifdef HAVE_SIXENSE
|
||||||
|
#include "sixense.h"
|
||||||
|
#endif
|
||||||
|
|
||||||
// TODO: This should not be here
|
// TODO: This should not be here
|
||||||
#include <QLoggingCategory>
|
#include <QLoggingCategory>
|
||||||
Q_DECLARE_LOGGING_CATEGORY(inputplugins)
|
Q_DECLARE_LOGGING_CATEGORY(inputplugins)
|
||||||
|
@ -30,8 +34,6 @@ Q_LOGGING_CATEGORY(inputplugins, "hifi.inputplugins")
|
||||||
const unsigned int LEFT_MASK = 0;
|
const unsigned int LEFT_MASK = 0;
|
||||||
const unsigned int RIGHT_MASK = 1U << 1;
|
const unsigned int RIGHT_MASK = 1U << 1;
|
||||||
|
|
||||||
#ifdef HAVE_SIXENSE
|
|
||||||
|
|
||||||
const int CALIBRATION_STATE_IDLE = 0;
|
const int CALIBRATION_STATE_IDLE = 0;
|
||||||
const int CALIBRATION_STATE_X = 1;
|
const int CALIBRATION_STATE_X = 1;
|
||||||
const int CALIBRATION_STATE_Y = 2;
|
const int CALIBRATION_STATE_Y = 2;
|
||||||
|
@ -51,8 +53,6 @@ typedef int (*SixenseTakeIntFunction)(int);
|
||||||
typedef int (*SixenseTakeIntAndSixenseControllerData)(int, sixenseControllerData*);
|
typedef int (*SixenseTakeIntAndSixenseControllerData)(int, sixenseControllerData*);
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#endif
|
|
||||||
|
|
||||||
const QString SixenseManager::NAME = "Sixense";
|
const QString SixenseManager::NAME = "Sixense";
|
||||||
|
|
||||||
const QString MENU_PARENT = "Avatar";
|
const QString MENU_PARENT = "Avatar";
|
||||||
|
@ -66,8 +66,8 @@ SixenseManager& SixenseManager::getInstance() {
|
||||||
}
|
}
|
||||||
|
|
||||||
SixenseManager::SixenseManager() :
|
SixenseManager::SixenseManager() :
|
||||||
InputDevice("Hydra"),
|
InputDevice("Hydra"),
|
||||||
#if defined(HAVE_SIXENSE) && defined(__APPLE__)
|
#ifdef __APPLE__
|
||||||
_sixenseLibrary(NULL),
|
_sixenseLibrary(NULL),
|
||||||
#endif
|
#endif
|
||||||
_hydrasConnected(false)
|
_hydrasConnected(false)
|
||||||
|
@ -213,18 +213,16 @@ void SixenseManager::update(float deltaTime, bool jointsCaptured) {
|
||||||
// NOTE: Sixense API returns pos data in millimeters but we IMMEDIATELY convert to meters.
|
// NOTE: Sixense API returns pos data in millimeters but we IMMEDIATELY convert to meters.
|
||||||
glm::vec3 position(data->pos[0], data->pos[1], data->pos[2]);
|
glm::vec3 position(data->pos[0], data->pos[1], data->pos[2]);
|
||||||
position *= METERS_PER_MILLIMETER;
|
position *= METERS_PER_MILLIMETER;
|
||||||
|
|
||||||
// Check to see if this hand/controller is on the base
|
// Check to see if this hand/controller is on the base
|
||||||
const float CONTROLLER_AT_BASE_DISTANCE = 0.075f;
|
const float CONTROLLER_AT_BASE_DISTANCE = 0.075f;
|
||||||
if (glm::length(position) >= CONTROLLER_AT_BASE_DISTANCE) {
|
if (glm::length(position) >= CONTROLLER_AT_BASE_DISTANCE) {
|
||||||
handleButtonEvent(data->buttons, numActiveControllers - 1);
|
handleButtonEvent(data->buttons, numActiveControllers - 1);
|
||||||
handleAxisEvent(data->joystick_x, data->joystick_y, data->trigger, numActiveControllers - 1);
|
handleAxisEvent(data->joystick_x, data->joystick_y, data->trigger, numActiveControllers - 1);
|
||||||
|
|
||||||
// Rotation of Palm
|
|
||||||
glm::quat rotation(data->rot_quat[3], -data->rot_quat[0], data->rot_quat[1], -data->rot_quat[2]);
|
|
||||||
rotation = glm::angleAxis(PI, glm::vec3(0.0f, 1.0f, 0.0f)) * _orbRotation * rotation;
|
|
||||||
|
|
||||||
if (!jointsCaptured) {
|
if (!jointsCaptured) {
|
||||||
|
// Rotation of Palm
|
||||||
|
glm::quat rotation(data->rot_quat[3], data->rot_quat[0], data->rot_quat[1], data->rot_quat[2]);
|
||||||
handlePoseEvent(position, rotation, numActiveControllers - 1);
|
handlePoseEvent(position, rotation, numActiveControllers - 1);
|
||||||
} else {
|
} else {
|
||||||
_poseStateMap.clear();
|
_poseStateMap.clear();
|
||||||
|
@ -232,7 +230,7 @@ void SixenseManager::update(float deltaTime, bool jointsCaptured) {
|
||||||
} else {
|
} else {
|
||||||
_poseStateMap[(numActiveControllers - 1) == 0 ? LEFT_HAND : RIGHT_HAND] = UserInputMapper::PoseValue();
|
_poseStateMap[(numActiveControllers - 1) == 0 ? LEFT_HAND : RIGHT_HAND] = UserInputMapper::PoseValue();
|
||||||
}
|
}
|
||||||
|
|
||||||
// // Read controller buttons and joystick into the hand
|
// // Read controller buttons and joystick into the hand
|
||||||
// palm->setControllerButtons(data->buttons);
|
// palm->setControllerButtons(data->buttons);
|
||||||
// palm->setTrigger(data->trigger);
|
// palm->setTrigger(data->trigger);
|
||||||
|
@ -242,7 +240,7 @@ void SixenseManager::update(float deltaTime, bool jointsCaptured) {
|
||||||
if (numActiveControllers == 2) {
|
if (numActiveControllers == 2) {
|
||||||
updateCalibration(controllers);
|
updateCalibration(controllers);
|
||||||
}
|
}
|
||||||
|
|
||||||
for (auto axisState : _axisStateMap) {
|
for (auto axisState : _axisStateMap) {
|
||||||
if (fabsf(axisState.second) < CONTROLLER_THRESHOLD) {
|
if (fabsf(axisState.second) < CONTROLLER_THRESHOLD) {
|
||||||
_axisStateMap[axisState.first] = 0.0f;
|
_axisStateMap[axisState.first] = 0.0f;
|
||||||
|
@ -436,16 +434,66 @@ void SixenseManager::handleButtonEvent(unsigned int buttons, int index) {
|
||||||
|
|
||||||
void SixenseManager::handlePoseEvent(glm::vec3 position, glm::quat rotation, int index) {
|
void SixenseManager::handlePoseEvent(glm::vec3 position, glm::quat rotation, int index) {
|
||||||
#ifdef HAVE_SIXENSE
|
#ifdef HAVE_SIXENSE
|
||||||
|
// From ABOVE the sixense coordinate frame looks like this:
|
||||||
|
//
|
||||||
|
// |
|
||||||
|
// USB cables
|
||||||
|
// |
|
||||||
|
// .-. user
|
||||||
|
// (Orb) --neckX---- forward
|
||||||
|
// '-' |
|
||||||
|
// | | user
|
||||||
|
// neckZ y +---- right
|
||||||
|
// | (o)-----x
|
||||||
|
// |
|
||||||
|
// |
|
||||||
|
// z
|
||||||
|
|
||||||
// Transform the measured position into body frame.
|
// Transform the measured position into body frame.
|
||||||
glm::vec3 neck = _neckBase;
|
glm::vec3 neck = _neckBase;
|
||||||
// Set y component of the "neck" to raise the measured position a little bit.
|
// Set y component of the "neck" to raise the measured position a little bit.
|
||||||
neck.y = 0.5f;
|
neck.y = 0.5f;
|
||||||
position = _orbRotation * (position - neck);
|
position = _orbRotation * (position - neck);
|
||||||
|
|
||||||
// adjustment for hydra controllers fit into hands
|
// From ABOVE the hand canonical axes looks like this:
|
||||||
float sign = (index == 0) ? -1.0f : 1.0f;
|
//
|
||||||
rotation *= glm::angleAxis(sign * PI/4.0f, glm::vec3(0.0f, 0.0f, 1.0f));
|
// | | | | y | | | |
|
||||||
|
// | | | | | | | | |
|
||||||
|
// | | | | |
|
||||||
|
// |left | / x----(+) \ |right|
|
||||||
|
// | _/ z \_ |
|
||||||
|
// | | | |
|
||||||
|
// | | | |
|
||||||
|
//
|
||||||
|
|
||||||
|
// To convert sixense's delta-rotation into the hand's frame we will have to transform it like so:
|
||||||
|
//
|
||||||
|
// deltaHand = Qsh^ * deltaSixense * Qsh
|
||||||
|
//
|
||||||
|
// where Qsh = transform from sixense axes to hand axes. By inspection we can determine Qsh:
|
||||||
|
//
|
||||||
|
// Qsh = angleAxis(PI, zAxis) * angleAxis(-PI/2, xAxis)
|
||||||
|
//
|
||||||
|
const glm::vec3 xAxis = glm::vec3(1.0f, 0.0f, 0.0f);
|
||||||
|
const glm::vec3 zAxis = glm::vec3(0.0f, 0.0f, 1.0f);
|
||||||
|
const glm::quat sixenseToHand = glm::angleAxis(PI, zAxis) * glm::angleAxis(-PI/2.0f, xAxis);
|
||||||
|
|
||||||
|
// In addition to Qsh each hand has pre-offset introduced by the shape of the sixense controllers
|
||||||
|
// and how they fit into the hand in their relaxed state. This offset is a quarter turn about
|
||||||
|
// the sixense's z-axis, with its direction different for the two hands:
|
||||||
|
float sign = (index == 0) ? 1.0f : -1.0f;
|
||||||
|
const glm::quat preOffset = glm::angleAxis(sign * PI / 2.0f, zAxis);
|
||||||
|
|
||||||
|
// Finally, there is a post-offset (same for both hands) to get the hand's rest orientation
|
||||||
|
// (fingers forward, palm down) aligned properly in the avatar's model-frame.
|
||||||
|
const glm::quat postOffset = glm::angleAxis(PI / 2.0f, xAxis);
|
||||||
|
|
||||||
|
// The total rotation of the hand uses the formula:
|
||||||
|
//
|
||||||
|
// rotation = postOffset * Qsh^ * (measuredRotation * preOffset) * Qsh
|
||||||
|
//
|
||||||
|
rotation = postOffset * glm::inverse(sixenseToHand) * rotation * preOffset * sixenseToHand;
|
||||||
|
|
||||||
_poseStateMap[makeInput(JointChannel(index)).getChannel()] = UserInputMapper::PoseValue(position, rotation);
|
_poseStateMap[makeInput(JointChannel(index)).getChannel()] = UserInputMapper::PoseValue(position, rotation);
|
||||||
#endif // HAVE_SIXENSE
|
#endif // HAVE_SIXENSE
|
||||||
}
|
}
|
||||||
|
@ -453,7 +501,7 @@ void SixenseManager::handlePoseEvent(glm::vec3 position, glm::quat rotation, int
|
||||||
void SixenseManager::registerToUserInputMapper(UserInputMapper& mapper) {
|
void SixenseManager::registerToUserInputMapper(UserInputMapper& mapper) {
|
||||||
// Grab the current free device ID
|
// Grab the current free device ID
|
||||||
_deviceID = mapper.getFreeDeviceID();
|
_deviceID = mapper.getFreeDeviceID();
|
||||||
|
|
||||||
auto proxy = std::make_shared<UserInputMapper::DeviceProxy>(_name);
|
auto proxy = std::make_shared<UserInputMapper::DeviceProxy>(_name);
|
||||||
proxy->getButton = [this] (const UserInputMapper::Input& input, int timestamp) -> bool { return this->getButton(input.getChannel()); };
|
proxy->getButton = [this] (const UserInputMapper::Input& input, int timestamp) -> bool { return this->getButton(input.getChannel()); };
|
||||||
proxy->getAxis = [this] (const UserInputMapper::Input& input, int timestamp) -> float { return this->getAxis(input.getChannel()); };
|
proxy->getAxis = [this] (const UserInputMapper::Input& input, int timestamp) -> float { return this->getAxis(input.getChannel()); };
|
||||||
|
@ -465,25 +513,25 @@ void SixenseManager::registerToUserInputMapper(UserInputMapper& mapper) {
|
||||||
availableInputs.append(UserInputMapper::InputPair(makeInput(BUTTON_2, 0), "Left Button 2"));
|
availableInputs.append(UserInputMapper::InputPair(makeInput(BUTTON_2, 0), "Left Button 2"));
|
||||||
availableInputs.append(UserInputMapper::InputPair(makeInput(BUTTON_3, 0), "Left Button 3"));
|
availableInputs.append(UserInputMapper::InputPair(makeInput(BUTTON_3, 0), "Left Button 3"));
|
||||||
availableInputs.append(UserInputMapper::InputPair(makeInput(BUTTON_4, 0), "Left Button 4"));
|
availableInputs.append(UserInputMapper::InputPair(makeInput(BUTTON_4, 0), "Left Button 4"));
|
||||||
|
|
||||||
availableInputs.append(UserInputMapper::InputPair(makeInput(BUTTON_FWD, 0), "L1"));
|
availableInputs.append(UserInputMapper::InputPair(makeInput(BUTTON_FWD, 0), "L1"));
|
||||||
availableInputs.append(UserInputMapper::InputPair(makeInput(BACK_TRIGGER, 0), "L2"));
|
availableInputs.append(UserInputMapper::InputPair(makeInput(BACK_TRIGGER, 0), "L2"));
|
||||||
|
|
||||||
availableInputs.append(UserInputMapper::InputPair(makeInput(AXIS_Y_POS, 0), "Left Stick Up"));
|
availableInputs.append(UserInputMapper::InputPair(makeInput(AXIS_Y_POS, 0), "Left Stick Up"));
|
||||||
availableInputs.append(UserInputMapper::InputPair(makeInput(AXIS_Y_NEG, 0), "Left Stick Down"));
|
availableInputs.append(UserInputMapper::InputPair(makeInput(AXIS_Y_NEG, 0), "Left Stick Down"));
|
||||||
availableInputs.append(UserInputMapper::InputPair(makeInput(AXIS_X_POS, 0), "Left Stick Right"));
|
availableInputs.append(UserInputMapper::InputPair(makeInput(AXIS_X_POS, 0), "Left Stick Right"));
|
||||||
availableInputs.append(UserInputMapper::InputPair(makeInput(AXIS_X_NEG, 0), "Left Stick Left"));
|
availableInputs.append(UserInputMapper::InputPair(makeInput(AXIS_X_NEG, 0), "Left Stick Left"));
|
||||||
availableInputs.append(UserInputMapper::InputPair(makeInput(BUTTON_TRIGGER, 0), "Left Trigger Press"));
|
availableInputs.append(UserInputMapper::InputPair(makeInput(BUTTON_TRIGGER, 0), "Left Trigger Press"));
|
||||||
|
|
||||||
availableInputs.append(UserInputMapper::InputPair(makeInput(BUTTON_0, 1), "Right Start"));
|
availableInputs.append(UserInputMapper::InputPair(makeInput(BUTTON_0, 1), "Right Start"));
|
||||||
availableInputs.append(UserInputMapper::InputPair(makeInput(BUTTON_1, 1), "Right Button 1"));
|
availableInputs.append(UserInputMapper::InputPair(makeInput(BUTTON_1, 1), "Right Button 1"));
|
||||||
availableInputs.append(UserInputMapper::InputPair(makeInput(BUTTON_2, 1), "Right Button 2"));
|
availableInputs.append(UserInputMapper::InputPair(makeInput(BUTTON_2, 1), "Right Button 2"));
|
||||||
availableInputs.append(UserInputMapper::InputPair(makeInput(BUTTON_3, 1), "Right Button 3"));
|
availableInputs.append(UserInputMapper::InputPair(makeInput(BUTTON_3, 1), "Right Button 3"));
|
||||||
availableInputs.append(UserInputMapper::InputPair(makeInput(BUTTON_4, 1), "Right Button 4"));
|
availableInputs.append(UserInputMapper::InputPair(makeInput(BUTTON_4, 1), "Right Button 4"));
|
||||||
|
|
||||||
availableInputs.append(UserInputMapper::InputPair(makeInput(BUTTON_FWD, 1), "R1"));
|
availableInputs.append(UserInputMapper::InputPair(makeInput(BUTTON_FWD, 1), "R1"));
|
||||||
availableInputs.append(UserInputMapper::InputPair(makeInput(BACK_TRIGGER, 1), "R2"));
|
availableInputs.append(UserInputMapper::InputPair(makeInput(BACK_TRIGGER, 1), "R2"));
|
||||||
|
|
||||||
availableInputs.append(UserInputMapper::InputPair(makeInput(AXIS_Y_POS, 1), "Right Stick Up"));
|
availableInputs.append(UserInputMapper::InputPair(makeInput(AXIS_Y_POS, 1), "Right Stick Up"));
|
||||||
availableInputs.append(UserInputMapper::InputPair(makeInput(AXIS_Y_NEG, 1), "Right Stick Down"));
|
availableInputs.append(UserInputMapper::InputPair(makeInput(AXIS_Y_NEG, 1), "Right Stick Down"));
|
||||||
availableInputs.append(UserInputMapper::InputPair(makeInput(AXIS_X_POS, 1), "Right Stick Right"));
|
availableInputs.append(UserInputMapper::InputPair(makeInput(AXIS_X_POS, 1), "Right Stick Right"));
|
||||||
|
@ -506,35 +554,35 @@ void SixenseManager::assignDefaultInputMapping(UserInputMapper& mapper) {
|
||||||
const float JOYSTICK_PITCH_SPEED = 0.25f;
|
const float JOYSTICK_PITCH_SPEED = 0.25f;
|
||||||
const float BUTTON_MOVE_SPEED = 1.0f;
|
const float BUTTON_MOVE_SPEED = 1.0f;
|
||||||
const float BOOM_SPEED = 0.1f;
|
const float BOOM_SPEED = 0.1f;
|
||||||
|
|
||||||
// Left Joystick: Movement, strafing
|
// Left Joystick: Movement, strafing
|
||||||
mapper.addInputChannel(UserInputMapper::LONGITUDINAL_FORWARD, makeInput(AXIS_Y_POS, 0), JOYSTICK_MOVE_SPEED);
|
mapper.addInputChannel(UserInputMapper::LONGITUDINAL_FORWARD, makeInput(AXIS_Y_POS, 0), JOYSTICK_MOVE_SPEED);
|
||||||
mapper.addInputChannel(UserInputMapper::LONGITUDINAL_BACKWARD, makeInput(AXIS_Y_NEG, 0), JOYSTICK_MOVE_SPEED);
|
mapper.addInputChannel(UserInputMapper::LONGITUDINAL_BACKWARD, makeInput(AXIS_Y_NEG, 0), JOYSTICK_MOVE_SPEED);
|
||||||
mapper.addInputChannel(UserInputMapper::LATERAL_RIGHT, makeInput(AXIS_X_POS, 0), JOYSTICK_MOVE_SPEED);
|
mapper.addInputChannel(UserInputMapper::LATERAL_RIGHT, makeInput(AXIS_X_POS, 0), JOYSTICK_MOVE_SPEED);
|
||||||
mapper.addInputChannel(UserInputMapper::LATERAL_LEFT, makeInput(AXIS_X_NEG, 0), JOYSTICK_MOVE_SPEED);
|
mapper.addInputChannel(UserInputMapper::LATERAL_LEFT, makeInput(AXIS_X_NEG, 0), JOYSTICK_MOVE_SPEED);
|
||||||
|
|
||||||
// Right Joystick: Camera orientation
|
// Right Joystick: Camera orientation
|
||||||
mapper.addInputChannel(UserInputMapper::YAW_RIGHT, makeInput(AXIS_X_POS, 1), JOYSTICK_YAW_SPEED);
|
mapper.addInputChannel(UserInputMapper::YAW_RIGHT, makeInput(AXIS_X_POS, 1), JOYSTICK_YAW_SPEED);
|
||||||
mapper.addInputChannel(UserInputMapper::YAW_LEFT, makeInput(AXIS_X_NEG, 1), JOYSTICK_YAW_SPEED);
|
mapper.addInputChannel(UserInputMapper::YAW_LEFT, makeInput(AXIS_X_NEG, 1), JOYSTICK_YAW_SPEED);
|
||||||
mapper.addInputChannel(UserInputMapper::PITCH_UP, makeInput(AXIS_Y_POS, 1), JOYSTICK_PITCH_SPEED);
|
mapper.addInputChannel(UserInputMapper::PITCH_UP, makeInput(AXIS_Y_POS, 1), JOYSTICK_PITCH_SPEED);
|
||||||
mapper.addInputChannel(UserInputMapper::PITCH_DOWN, makeInput(AXIS_Y_NEG, 1), JOYSTICK_PITCH_SPEED);
|
mapper.addInputChannel(UserInputMapper::PITCH_DOWN, makeInput(AXIS_Y_NEG, 1), JOYSTICK_PITCH_SPEED);
|
||||||
|
|
||||||
// Buttons
|
// Buttons
|
||||||
mapper.addInputChannel(UserInputMapper::BOOM_IN, makeInput(BUTTON_3, 0), BOOM_SPEED);
|
mapper.addInputChannel(UserInputMapper::BOOM_IN, makeInput(BUTTON_3, 0), BOOM_SPEED);
|
||||||
mapper.addInputChannel(UserInputMapper::BOOM_OUT, makeInput(BUTTON_1, 0), BOOM_SPEED);
|
mapper.addInputChannel(UserInputMapper::BOOM_OUT, makeInput(BUTTON_1, 0), BOOM_SPEED);
|
||||||
|
|
||||||
mapper.addInputChannel(UserInputMapper::VERTICAL_UP, makeInput(BUTTON_3, 1), BUTTON_MOVE_SPEED);
|
mapper.addInputChannel(UserInputMapper::VERTICAL_UP, makeInput(BUTTON_3, 1), BUTTON_MOVE_SPEED);
|
||||||
mapper.addInputChannel(UserInputMapper::VERTICAL_DOWN, makeInput(BUTTON_1, 1), BUTTON_MOVE_SPEED);
|
mapper.addInputChannel(UserInputMapper::VERTICAL_DOWN, makeInput(BUTTON_1, 1), BUTTON_MOVE_SPEED);
|
||||||
|
|
||||||
mapper.addInputChannel(UserInputMapper::SHIFT, makeInput(BUTTON_2, 0));
|
mapper.addInputChannel(UserInputMapper::SHIFT, makeInput(BUTTON_2, 0));
|
||||||
mapper.addInputChannel(UserInputMapper::SHIFT, makeInput(BUTTON_2, 1));
|
mapper.addInputChannel(UserInputMapper::SHIFT, makeInput(BUTTON_2, 1));
|
||||||
|
|
||||||
mapper.addInputChannel(UserInputMapper::ACTION1, makeInput(BUTTON_4, 0));
|
mapper.addInputChannel(UserInputMapper::ACTION1, makeInput(BUTTON_4, 0));
|
||||||
mapper.addInputChannel(UserInputMapper::ACTION2, makeInput(BUTTON_4, 1));
|
mapper.addInputChannel(UserInputMapper::ACTION2, makeInput(BUTTON_4, 1));
|
||||||
|
|
||||||
mapper.addInputChannel(UserInputMapper::LEFT_HAND, makeInput(LEFT_HAND));
|
mapper.addInputChannel(UserInputMapper::LEFT_HAND, makeInput(LEFT_HAND));
|
||||||
mapper.addInputChannel(UserInputMapper::RIGHT_HAND, makeInput(RIGHT_HAND));
|
mapper.addInputChannel(UserInputMapper::RIGHT_HAND, makeInput(RIGHT_HAND));
|
||||||
|
|
||||||
mapper.addInputChannel(UserInputMapper::LEFT_HAND_CLICK, makeInput(BACK_TRIGGER, 0));
|
mapper.addInputChannel(UserInputMapper::LEFT_HAND_CLICK, makeInput(BACK_TRIGGER, 0));
|
||||||
mapper.addInputChannel(UserInputMapper::RIGHT_HAND_CLICK, makeInput(BACK_TRIGGER, 1));
|
mapper.addInputChannel(UserInputMapper::RIGHT_HAND_CLICK, makeInput(BACK_TRIGGER, 1));
|
||||||
|
|
||||||
|
|
|
@ -230,7 +230,7 @@ void UserInputMapper::update(float deltaTime) {
|
||||||
for (auto i = 0; i < NUM_ACTIONS; i++) {
|
for (auto i = 0; i < NUM_ACTIONS; i++) {
|
||||||
_actionStates[i] *= _actionScales[i];
|
_actionStates[i] *= _actionScales[i];
|
||||||
// Emit only on change, and emit when moving back to 0
|
// Emit only on change, and emit when moving back to 0
|
||||||
if (fabs(_actionStates[i] - _lastActionStates[i]) > EPSILON) {
|
if (fabsf(_actionStates[i] - _lastActionStates[i]) > EPSILON) {
|
||||||
_lastActionStates[i] = _actionStates[i];
|
_lastActionStates[i] = _actionStates[i];
|
||||||
emit actionEvent(i, _actionStates[i]);
|
emit actionEvent(i, _actionStates[i]);
|
||||||
}
|
}
|
||||||
|
@ -319,4 +319,4 @@ void UserInputMapper::createActionNames() {
|
||||||
_actionNames[SHIFT] = "SHIFT";
|
_actionNames[SHIFT] = "SHIFT";
|
||||||
_actionNames[ACTION1] = "ACTION1";
|
_actionNames[ACTION1] = "ACTION1";
|
||||||
_actionNames[ACTION2] = "ACTION2";
|
_actionNames[ACTION2] = "ACTION2";
|
||||||
}
|
}
|
||||||
|
|
|
@ -324,9 +324,64 @@ void ViveControllerManager::handlePoseEvent(const mat4& mat, int index) {
|
||||||
glm::vec3 position = extractTranslation(mat);
|
glm::vec3 position = extractTranslation(mat);
|
||||||
glm::quat rotation = glm::quat_cast(mat);
|
glm::quat rotation = glm::quat_cast(mat);
|
||||||
|
|
||||||
// Flip the rotation appropriately for each hand
|
// When the sensor-to-world rotation is identity the coordinate axes look like this:
|
||||||
int sign = index == LEFT_HAND ? 1 : -1;
|
//
|
||||||
rotation = rotation * glm::angleAxis(PI, glm::vec3(1.0f, 0.0f, 0.0f)) * glm::angleAxis(sign * PI_OVER_TWO, glm::vec3(0.0f, 0.0f, 1.0f));
|
// user
|
||||||
|
// forward
|
||||||
|
// z
|
||||||
|
// |
|
||||||
|
// y| user
|
||||||
|
// y o----x right
|
||||||
|
// o-----x user
|
||||||
|
// | up
|
||||||
|
// |
|
||||||
|
// z
|
||||||
|
//
|
||||||
|
// Vive
|
||||||
|
//
|
||||||
|
|
||||||
|
// From ABOVE the hand canonical axes looks like this:
|
||||||
|
//
|
||||||
|
// | | | | y | | | |
|
||||||
|
// | | | | | | | | |
|
||||||
|
// | | | | |
|
||||||
|
// |left | / x---- + \ |right|
|
||||||
|
// | _/ z \_ |
|
||||||
|
// | | | |
|
||||||
|
// | | | |
|
||||||
|
//
|
||||||
|
|
||||||
|
// So when the user is standing in Vive space facing the -zAxis with hands outstretched and palms down
|
||||||
|
// the rotation to align the Vive axes with those of the hands is:
|
||||||
|
//
|
||||||
|
// QviveToHand = halfTurnAboutY * quaterTurnAboutX
|
||||||
|
|
||||||
|
// Due to how the Vive controllers fit into the palm there is an offset that is different for each hand.
|
||||||
|
// You can think of this offset as the inverse of the measured rotation when the hands are posed, such that
|
||||||
|
// the combination (measurement * offset) is identity at this orientation.
|
||||||
|
//
|
||||||
|
// Qoffset = glm::inverse(deltaRotation when hand is posed fingers forward, palm down)
|
||||||
|
//
|
||||||
|
// An approximate offset for the Vive can be obtained by inpection:
|
||||||
|
//
|
||||||
|
// Qoffset = glm::inverse(glm::angleAxis(sign * PI/4.0f, zAxis) * glm::angleAxis(PI/2.0f, xAxis))
|
||||||
|
//
|
||||||
|
|
||||||
|
// Finally there is another flip around the yAxis to re-align from model to Vive space, so the full equation is:
|
||||||
|
//
|
||||||
|
// Q = yFlip * combinedMeasurement * viveToHand
|
||||||
|
//
|
||||||
|
// Q = yFlip * (deltaQ * QOffset) * (yFlip * quarterTurnAboutX)
|
||||||
|
//
|
||||||
|
// Q = yFlip * (deltaQ * inverse(deltaQForAlignedHand)) * (yFlip * quarterTurnAboutX)
|
||||||
|
|
||||||
|
const glm::quat quarterX = glm::angleAxis(PI / 2.0f, glm::vec3(1.0f, 0.0f, 0.0f));
|
||||||
|
const glm::quat yFlip = glm::angleAxis(PI, glm::vec3(0.0f, 1.0f, 0.0f));
|
||||||
|
float sign = (index == LEFT_HAND) ? -1.0f : 1.0f;
|
||||||
|
const glm::quat signedQuaterZ = glm::angleAxis(sign * PI / 2.0f, glm::vec3(0.0f, 0.0f, 1.0f));
|
||||||
|
const glm::quat eighthX = glm::angleAxis(PI / 4.0f, glm::vec3(1.0f, 0.0f, 0.0f));
|
||||||
|
const glm::quat offset = glm::inverse(signedQuaterZ * eighthX);
|
||||||
|
rotation = yFlip * rotation * offset * yFlip * quarterX;
|
||||||
|
|
||||||
position += rotation * glm::vec3(0, 0, -CONTROLLER_LENGTH_OFFSET);
|
position += rotation * glm::vec3(0, 0, -CONTROLLER_LENGTH_OFFSET);
|
||||||
|
|
||||||
|
|
|
@ -99,7 +99,7 @@ void EntityMotionState::updateServerPhysicsVariables() {
|
||||||
}
|
}
|
||||||
|
|
||||||
// virtual
|
// virtual
|
||||||
void EntityMotionState::handleEasyChanges(uint32_t flags, PhysicsEngine* engine) {
|
bool EntityMotionState::handleEasyChanges(uint32_t flags, PhysicsEngine* engine) {
|
||||||
assert(entityTreeIsLocked());
|
assert(entityTreeIsLocked());
|
||||||
updateServerPhysicsVariables();
|
updateServerPhysicsVariables();
|
||||||
ObjectMotionState::handleEasyChanges(flags, engine);
|
ObjectMotionState::handleEasyChanges(flags, engine);
|
||||||
|
@ -131,13 +131,15 @@ void EntityMotionState::handleEasyChanges(uint32_t flags, PhysicsEngine* engine)
|
||||||
if ((flags & EntityItem::DIRTY_PHYSICS_ACTIVATION) && !_body->isActive()) {
|
if ((flags & EntityItem::DIRTY_PHYSICS_ACTIVATION) && !_body->isActive()) {
|
||||||
_body->activate();
|
_body->activate();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// virtual
|
// virtual
|
||||||
void EntityMotionState::handleHardAndEasyChanges(uint32_t flags, PhysicsEngine* engine) {
|
bool EntityMotionState::handleHardAndEasyChanges(uint32_t flags, PhysicsEngine* engine) {
|
||||||
updateServerPhysicsVariables();
|
updateServerPhysicsVariables();
|
||||||
ObjectMotionState::handleHardAndEasyChanges(flags, engine);
|
return ObjectMotionState::handleHardAndEasyChanges(flags, engine);
|
||||||
}
|
}
|
||||||
|
|
||||||
void EntityMotionState::clearObjectBackPointer() {
|
void EntityMotionState::clearObjectBackPointer() {
|
||||||
|
@ -222,6 +224,15 @@ void EntityMotionState::setWorldTransform(const btTransform& worldTrans) {
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// virtual and protected
|
||||||
|
bool EntityMotionState::isReadyToComputeShape() {
|
||||||
|
if (_entity) {
|
||||||
|
return _entity->isReadyToComputeShape();
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
// virtual and protected
|
// virtual and protected
|
||||||
btCollisionShape* EntityMotionState::computeNewShape() {
|
btCollisionShape* EntityMotionState::computeNewShape() {
|
||||||
if (_entity) {
|
if (_entity) {
|
||||||
|
@ -493,12 +504,11 @@ void EntityMotionState::sendUpdate(OctreeEditPacketSender* packetSender, const Q
|
||||||
_lastStep = step;
|
_lastStep = step;
|
||||||
}
|
}
|
||||||
|
|
||||||
uint32_t EntityMotionState::getAndClearIncomingDirtyFlags() {
|
uint32_t EntityMotionState::getIncomingDirtyFlags() {
|
||||||
assert(entityTreeIsLocked());
|
assert(entityTreeIsLocked());
|
||||||
uint32_t dirtyFlags = 0;
|
uint32_t dirtyFlags = 0;
|
||||||
if (_body && _entity) {
|
if (_body && _entity) {
|
||||||
dirtyFlags = _entity->getDirtyFlags();
|
dirtyFlags = _entity->getDirtyFlags();
|
||||||
_entity->clearDirtyFlags();
|
|
||||||
// we add DIRTY_MOTION_TYPE if the body's motion type disagrees with entity velocity settings
|
// we add DIRTY_MOTION_TYPE if the body's motion type disagrees with entity velocity settings
|
||||||
int bodyFlags = _body->getCollisionFlags();
|
int bodyFlags = _body->getCollisionFlags();
|
||||||
bool isMoving = _entity->isMoving();
|
bool isMoving = _entity->isMoving();
|
||||||
|
@ -510,6 +520,13 @@ uint32_t EntityMotionState::getAndClearIncomingDirtyFlags() {
|
||||||
return dirtyFlags;
|
return dirtyFlags;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void EntityMotionState::clearIncomingDirtyFlags() {
|
||||||
|
assert(entityTreeIsLocked());
|
||||||
|
if (_body && _entity) {
|
||||||
|
_entity->clearDirtyFlags();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// virtual
|
// virtual
|
||||||
quint8 EntityMotionState::getSimulationPriority() const {
|
quint8 EntityMotionState::getSimulationPriority() const {
|
||||||
if (_entity) {
|
if (_entity) {
|
||||||
|
|
|
@ -29,8 +29,8 @@ public:
|
||||||
virtual ~EntityMotionState();
|
virtual ~EntityMotionState();
|
||||||
|
|
||||||
void updateServerPhysicsVariables();
|
void updateServerPhysicsVariables();
|
||||||
virtual void handleEasyChanges(uint32_t flags, PhysicsEngine* engine);
|
virtual bool handleEasyChanges(uint32_t flags, PhysicsEngine* engine);
|
||||||
virtual void handleHardAndEasyChanges(uint32_t flags, PhysicsEngine* engine);
|
virtual bool handleHardAndEasyChanges(uint32_t flags, PhysicsEngine* engine);
|
||||||
|
|
||||||
/// \return MOTION_TYPE_DYNAMIC or MOTION_TYPE_STATIC based on params set in EntityItem
|
/// \return MOTION_TYPE_DYNAMIC or MOTION_TYPE_STATIC based on params set in EntityItem
|
||||||
virtual MotionType computeObjectMotionType() const;
|
virtual MotionType computeObjectMotionType() const;
|
||||||
|
@ -48,7 +48,8 @@ public:
|
||||||
bool shouldSendUpdate(uint32_t simulationStep, const QUuid& sessionID);
|
bool shouldSendUpdate(uint32_t simulationStep, const QUuid& sessionID);
|
||||||
void sendUpdate(OctreeEditPacketSender* packetSender, const QUuid& sessionID, uint32_t step);
|
void sendUpdate(OctreeEditPacketSender* packetSender, const QUuid& sessionID, uint32_t step);
|
||||||
|
|
||||||
virtual uint32_t getAndClearIncomingDirtyFlags();
|
virtual uint32_t getIncomingDirtyFlags();
|
||||||
|
virtual void clearIncomingDirtyFlags();
|
||||||
|
|
||||||
void incrementAccelerationNearlyGravityCount() { _accelerationNearlyGravityCount++; }
|
void incrementAccelerationNearlyGravityCount() { _accelerationNearlyGravityCount++; }
|
||||||
void resetAccelerationNearlyGravityCount() { _accelerationNearlyGravityCount = 0; }
|
void resetAccelerationNearlyGravityCount() { _accelerationNearlyGravityCount = 0; }
|
||||||
|
@ -91,6 +92,7 @@ protected:
|
||||||
bool entityTreeIsLocked() const;
|
bool entityTreeIsLocked() const;
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
virtual bool isReadyToComputeShape();
|
||||||
virtual btCollisionShape* computeNewShape();
|
virtual btCollisionShape* computeNewShape();
|
||||||
virtual void clearObjectBackPointer();
|
virtual void clearObjectBackPointer();
|
||||||
virtual void setMotionType(MotionType motionType);
|
virtual void setMotionType(MotionType motionType);
|
||||||
|
|
|
@ -125,7 +125,7 @@ void ObjectMotionState::setRigidBody(btRigidBody* body) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void ObjectMotionState::handleEasyChanges(uint32_t flags, PhysicsEngine* engine) {
|
bool ObjectMotionState::handleEasyChanges(uint32_t flags, PhysicsEngine* engine) {
|
||||||
if (flags & EntityItem::DIRTY_POSITION) {
|
if (flags & EntityItem::DIRTY_POSITION) {
|
||||||
btTransform worldTrans;
|
btTransform worldTrans;
|
||||||
if (flags & EntityItem::DIRTY_ROTATION) {
|
if (flags & EntityItem::DIRTY_ROTATION) {
|
||||||
|
@ -156,11 +156,16 @@ void ObjectMotionState::handleEasyChanges(uint32_t flags, PhysicsEngine* engine)
|
||||||
if (flags & EntityItem::DIRTY_MASS) {
|
if (flags & EntityItem::DIRTY_MASS) {
|
||||||
updateBodyMassProperties();
|
updateBodyMassProperties();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
void ObjectMotionState::handleHardAndEasyChanges(uint32_t flags, PhysicsEngine* engine) {
|
bool ObjectMotionState::handleHardAndEasyChanges(uint32_t flags, PhysicsEngine* engine) {
|
||||||
if (flags & EntityItem::DIRTY_SHAPE) {
|
if (flags & EntityItem::DIRTY_SHAPE) {
|
||||||
// make sure the new shape is valid
|
// make sure the new shape is valid
|
||||||
|
if (!isReadyToComputeShape()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
btCollisionShape* newShape = computeNewShape();
|
btCollisionShape* newShape = computeNewShape();
|
||||||
if (!newShape) {
|
if (!newShape) {
|
||||||
qCDebug(physics) << "Warning: failed to generate new shape!";
|
qCDebug(physics) << "Warning: failed to generate new shape!";
|
||||||
|
@ -172,7 +177,7 @@ void ObjectMotionState::handleHardAndEasyChanges(uint32_t flags, PhysicsEngine*
|
||||||
if (flags & EASY_DIRTY_PHYSICS_FLAGS) {
|
if (flags & EASY_DIRTY_PHYSICS_FLAGS) {
|
||||||
handleEasyChanges(flags, engine);
|
handleEasyChanges(flags, engine);
|
||||||
}
|
}
|
||||||
return;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
getShapeManager()->releaseShape(_shape);
|
getShapeManager()->releaseShape(_shape);
|
||||||
|
@ -192,6 +197,8 @@ void ObjectMotionState::handleHardAndEasyChanges(uint32_t flags, PhysicsEngine*
|
||||||
if (flags & HARD_DIRTY_PHYSICS_FLAGS) {
|
if (flags & HARD_DIRTY_PHYSICS_FLAGS) {
|
||||||
engine->reinsertObject(this);
|
engine->reinsertObject(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
void ObjectMotionState::updateBodyMaterialProperties() {
|
void ObjectMotionState::updateBodyMaterialProperties() {
|
||||||
|
|
|
@ -71,8 +71,8 @@ public:
|
||||||
ObjectMotionState(btCollisionShape* shape);
|
ObjectMotionState(btCollisionShape* shape);
|
||||||
~ObjectMotionState();
|
~ObjectMotionState();
|
||||||
|
|
||||||
virtual void handleEasyChanges(uint32_t flags, PhysicsEngine* engine);
|
virtual bool handleEasyChanges(uint32_t flags, PhysicsEngine* engine);
|
||||||
virtual void handleHardAndEasyChanges(uint32_t flags, PhysicsEngine* engine);
|
virtual bool handleHardAndEasyChanges(uint32_t flags, PhysicsEngine* engine);
|
||||||
|
|
||||||
void updateBodyMaterialProperties();
|
void updateBodyMaterialProperties();
|
||||||
void updateBodyVelocities();
|
void updateBodyVelocities();
|
||||||
|
@ -92,7 +92,8 @@ public:
|
||||||
glm::vec3 getBodyAngularVelocity() const;
|
glm::vec3 getBodyAngularVelocity() const;
|
||||||
virtual glm::vec3 getObjectLinearVelocityChange() const;
|
virtual glm::vec3 getObjectLinearVelocityChange() const;
|
||||||
|
|
||||||
virtual uint32_t getAndClearIncomingDirtyFlags() = 0;
|
virtual uint32_t getIncomingDirtyFlags() = 0;
|
||||||
|
virtual void clearIncomingDirtyFlags() = 0;
|
||||||
|
|
||||||
virtual MotionType computeObjectMotionType() const = 0;
|
virtual MotionType computeObjectMotionType() const = 0;
|
||||||
|
|
||||||
|
@ -132,6 +133,7 @@ public:
|
||||||
friend class PhysicsEngine;
|
friend class PhysicsEngine;
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
|
virtual bool isReadyToComputeShape() = 0;
|
||||||
virtual btCollisionShape* computeNewShape() = 0;
|
virtual btCollisionShape* computeNewShape() = 0;
|
||||||
void setMotionType(MotionType motionType);
|
void setMotionType(MotionType motionType);
|
||||||
|
|
||||||
|
|
|
@ -173,6 +173,12 @@ VectorOfMotionStates& PhysicalEntitySimulation::getObjectsToAdd() {
|
||||||
return _tempVector;
|
return _tempVector;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void PhysicalEntitySimulation::setObjectsToChange(VectorOfMotionStates& objectsToChange) {
|
||||||
|
for (auto object : objectsToChange) {
|
||||||
|
_pendingChanges.insert(static_cast<EntityMotionState*>(object));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
VectorOfMotionStates& PhysicalEntitySimulation::getObjectsToChange() {
|
VectorOfMotionStates& PhysicalEntitySimulation::getObjectsToChange() {
|
||||||
_tempVector.clear();
|
_tempVector.clear();
|
||||||
for (auto stateItr : _pendingChanges) {
|
for (auto stateItr : _pendingChanges) {
|
||||||
|
|
|
@ -46,11 +46,14 @@ protected: // only called by EntitySimulation
|
||||||
public:
|
public:
|
||||||
VectorOfMotionStates& getObjectsToDelete();
|
VectorOfMotionStates& getObjectsToDelete();
|
||||||
VectorOfMotionStates& getObjectsToAdd();
|
VectorOfMotionStates& getObjectsToAdd();
|
||||||
|
void setObjectsToChange(VectorOfMotionStates& objectsToChange);
|
||||||
VectorOfMotionStates& getObjectsToChange();
|
VectorOfMotionStates& getObjectsToChange();
|
||||||
|
|
||||||
void handleOutgoingChanges(VectorOfMotionStates& motionStates, const QUuid& sessionID);
|
void handleOutgoingChanges(VectorOfMotionStates& motionStates, const QUuid& sessionID);
|
||||||
void handleCollisionEvents(CollisionEvents& collisionEvents);
|
void handleCollisionEvents(CollisionEvents& collisionEvents);
|
||||||
|
|
||||||
|
EntityEditPacketSender* getPacketSender() { return _entityPacketSender; }
|
||||||
|
|
||||||
private:
|
private:
|
||||||
// incoming changes
|
// incoming changes
|
||||||
SetOfEntityMotionStates _pendingRemoves; // EntityMotionStates to be removed from PhysicsEngine (and deleted)
|
SetOfEntityMotionStates _pendingRemoves; // EntityMotionStates to be removed from PhysicsEngine (and deleted)
|
||||||
|
|
|
@ -140,7 +140,7 @@ void PhysicsEngine::addObject(ObjectMotionState* motionState) {
|
||||||
int16_t group = motionState->computeCollisionGroup();
|
int16_t group = motionState->computeCollisionGroup();
|
||||||
_dynamicsWorld->addRigidBody(body, group, getCollisionMask(group));
|
_dynamicsWorld->addRigidBody(body, group, getCollisionMask(group));
|
||||||
|
|
||||||
motionState->getAndClearIncomingDirtyFlags();
|
motionState->clearIncomingDirtyFlags();
|
||||||
}
|
}
|
||||||
|
|
||||||
void PhysicsEngine::removeObject(ObjectMotionState* object) {
|
void PhysicsEngine::removeObject(ObjectMotionState* object) {
|
||||||
|
@ -188,15 +188,25 @@ void PhysicsEngine::addObjects(VectorOfMotionStates& objects) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void PhysicsEngine::changeObjects(VectorOfMotionStates& objects) {
|
VectorOfMotionStates PhysicsEngine::changeObjects(VectorOfMotionStates& objects) {
|
||||||
|
VectorOfMotionStates stillNeedChange;
|
||||||
for (auto object : objects) {
|
for (auto object : objects) {
|
||||||
uint32_t flags = object->getAndClearIncomingDirtyFlags() & DIRTY_PHYSICS_FLAGS;
|
uint32_t flags = object->getIncomingDirtyFlags() & DIRTY_PHYSICS_FLAGS;
|
||||||
if (flags & HARD_DIRTY_PHYSICS_FLAGS) {
|
if (flags & HARD_DIRTY_PHYSICS_FLAGS) {
|
||||||
object->handleHardAndEasyChanges(flags, this);
|
if (object->handleHardAndEasyChanges(flags, this)) {
|
||||||
|
object->clearIncomingDirtyFlags();
|
||||||
|
} else {
|
||||||
|
stillNeedChange.push_back(object);
|
||||||
|
}
|
||||||
} else if (flags & EASY_DIRTY_PHYSICS_FLAGS) {
|
} else if (flags & EASY_DIRTY_PHYSICS_FLAGS) {
|
||||||
object->handleEasyChanges(flags, this);
|
if (object->handleEasyChanges(flags, this)) {
|
||||||
|
object->clearIncomingDirtyFlags();
|
||||||
|
} else {
|
||||||
|
stillNeedChange.push_back(object);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
return stillNeedChange;
|
||||||
}
|
}
|
||||||
|
|
||||||
void PhysicsEngine::reinsertObject(ObjectMotionState* object) {
|
void PhysicsEngine::reinsertObject(ObjectMotionState* object) {
|
||||||
|
|
|
@ -60,7 +60,7 @@ public:
|
||||||
void deleteObjects(VectorOfMotionStates& objects);
|
void deleteObjects(VectorOfMotionStates& objects);
|
||||||
void deleteObjects(SetOfMotionStates& objects); // only called during teardown
|
void deleteObjects(SetOfMotionStates& objects); // only called during teardown
|
||||||
void addObjects(VectorOfMotionStates& objects);
|
void addObjects(VectorOfMotionStates& objects);
|
||||||
void changeObjects(VectorOfMotionStates& objects);
|
VectorOfMotionStates changeObjects(VectorOfMotionStates& objects);
|
||||||
void reinsertObject(ObjectMotionState* object);
|
void reinsertObject(ObjectMotionState* object);
|
||||||
|
|
||||||
void stepSimulation();
|
void stepSimulation();
|
||||||
|
|
|
@ -94,7 +94,6 @@ btCollisionShape* ShapeFactory::createShapeFromInfo(const ShapeInfo& info) {
|
||||||
if (numSubShapes == 1) {
|
if (numSubShapes == 1) {
|
||||||
shape = createConvexHull(info.getPoints()[0]);
|
shape = createConvexHull(info.getPoints()[0]);
|
||||||
} else {
|
} else {
|
||||||
assert(numSubShapes > 1);
|
|
||||||
auto compound = new btCompoundShape();
|
auto compound = new btCompoundShape();
|
||||||
btTransform trans;
|
btTransform trans;
|
||||||
trans.setIdentity();
|
trans.setIdentity();
|
||||||
|
|
|
@ -243,7 +243,7 @@ void AmbientOcclusion::run(const render::SceneContextPointer& sceneContext, cons
|
||||||
batch._glUniform2f(_depthTexCoordScaleLoc, depthTexCoordScaleS, depthTexCoordScaleT);
|
batch._glUniform2f(_depthTexCoordScaleLoc, depthTexCoordScaleS, depthTexCoordScaleT);
|
||||||
|
|
||||||
batch._glUniform2f(_renderTargetResLoc, fbWidth, fbHeight);
|
batch._glUniform2f(_renderTargetResLoc, fbWidth, fbHeight);
|
||||||
batch._glUniform2f(_renderTargetResInvLoc, 1.0/fbWidth, 1.0/fbHeight);
|
batch._glUniform2f(_renderTargetResInvLoc, 1.0f / fbWidth, 1.0f / fbHeight);
|
||||||
|
|
||||||
glm::vec4 color(0.0f, 0.0f, 0.0f, 1.0f);
|
glm::vec4 color(0.0f, 0.0f, 0.0f, 1.0f);
|
||||||
glm::vec2 bottomLeft(-1.0f, -1.0f);
|
glm::vec2 bottomLeft(-1.0f, -1.0f);
|
||||||
|
|
|
@ -60,6 +60,8 @@ float Model::FAKE_DIMENSION_PLACEHOLDER = -1.0f;
|
||||||
|
|
||||||
Model::Model(RigPointer rig, QObject* parent) :
|
Model::Model(RigPointer rig, QObject* parent) :
|
||||||
QObject(parent),
|
QObject(parent),
|
||||||
|
_translation(0.0f),
|
||||||
|
_rotation(),
|
||||||
_scale(1.0f, 1.0f, 1.0f),
|
_scale(1.0f, 1.0f, 1.0f),
|
||||||
_scaleToFit(false),
|
_scaleToFit(false),
|
||||||
_scaleToFitDimensions(0.0f),
|
_scaleToFitDimensions(0.0f),
|
||||||
|
@ -195,6 +197,13 @@ void Model::RenderPipelineLib::initLocations(gpu::ShaderPointer& program, Model:
|
||||||
|
|
||||||
AbstractViewStateInterface* Model::_viewState = NULL;
|
AbstractViewStateInterface* Model::_viewState = NULL;
|
||||||
|
|
||||||
|
void Model::setTranslation(const glm::vec3& translation) {
|
||||||
|
_translation = translation;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Model::setRotation(const glm::quat& rotation) {
|
||||||
|
_rotation = rotation;
|
||||||
|
}
|
||||||
|
|
||||||
void Model::setScale(const glm::vec3& scale) {
|
void Model::setScale(const glm::vec3& scale) {
|
||||||
setScaleInternal(scale);
|
setScaleInternal(scale);
|
||||||
|
@ -433,14 +442,14 @@ void Model::initJointStates(QVector<JointState> states) {
|
||||||
int rightElbowJointIndex = rightHandJointIndex >= 0 ? geometry.joints.at(rightHandJointIndex).parentIndex : -1;
|
int rightElbowJointIndex = rightHandJointIndex >= 0 ? geometry.joints.at(rightHandJointIndex).parentIndex : -1;
|
||||||
int rightShoulderJointIndex = rightElbowJointIndex >= 0 ? geometry.joints.at(rightElbowJointIndex).parentIndex : -1;
|
int rightShoulderJointIndex = rightElbowJointIndex >= 0 ? geometry.joints.at(rightElbowJointIndex).parentIndex : -1;
|
||||||
|
|
||||||
_boundingRadius = _rig->initJointStates(states, parentTransform,
|
_rig->initJointStates(states, parentTransform,
|
||||||
rootJointIndex,
|
rootJointIndex,
|
||||||
leftHandJointIndex,
|
leftHandJointIndex,
|
||||||
leftElbowJointIndex,
|
leftElbowJointIndex,
|
||||||
leftShoulderJointIndex,
|
leftShoulderJointIndex,
|
||||||
rightHandJointIndex,
|
rightHandJointIndex,
|
||||||
rightElbowJointIndex,
|
rightElbowJointIndex,
|
||||||
rightShoulderJointIndex);
|
rightShoulderJointIndex);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool Model::findRayIntersectionAgainstSubMeshes(const glm::vec3& origin, const glm::vec3& direction, float& distance,
|
bool Model::findRayIntersectionAgainstSubMeshes(const glm::vec3& origin, const glm::vec3& direction, float& distance,
|
||||||
|
|
|
@ -27,7 +27,6 @@
|
||||||
#include <gpu/Stream.h>
|
#include <gpu/Stream.h>
|
||||||
#include <gpu/Batch.h>
|
#include <gpu/Batch.h>
|
||||||
#include <gpu/Pipeline.h>
|
#include <gpu/Pipeline.h>
|
||||||
#include "PhysicsEntity.h"
|
|
||||||
#include <render/Scene.h>
|
#include <render/Scene.h>
|
||||||
#include <Transform.h>
|
#include <Transform.h>
|
||||||
|
|
||||||
|
@ -54,7 +53,7 @@ inline uint qHash(const std::shared_ptr<MeshPartPayload>& a, uint seed) {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A generic 3D model displaying geometry loaded from a URL.
|
/// A generic 3D model displaying geometry loaded from a URL.
|
||||||
class Model : public QObject, public PhysicsEntity {
|
class Model : public QObject {
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
|
|
||||||
public:
|
public:
|
||||||
|
@ -172,6 +171,12 @@ public:
|
||||||
/// Returns the extents of the model's mesh
|
/// Returns the extents of the model's mesh
|
||||||
Extents getMeshExtents() const;
|
Extents getMeshExtents() const;
|
||||||
|
|
||||||
|
void setTranslation(const glm::vec3& translation);
|
||||||
|
void setRotation(const glm::quat& rotation);
|
||||||
|
|
||||||
|
const glm::vec3& getTranslation() const { return _translation; }
|
||||||
|
const glm::quat& getRotation() const { return _rotation; }
|
||||||
|
|
||||||
void setScale(const glm::vec3& scale);
|
void setScale(const glm::vec3& scale);
|
||||||
const glm::vec3& getScale() const { return _scale; }
|
const glm::vec3& getScale() const { return _scale; }
|
||||||
|
|
||||||
|
@ -232,6 +237,8 @@ protected:
|
||||||
QSharedPointer<NetworkGeometry> _geometry;
|
QSharedPointer<NetworkGeometry> _geometry;
|
||||||
void setGeometry(const QSharedPointer<NetworkGeometry>& newGeometry);
|
void setGeometry(const QSharedPointer<NetworkGeometry>& newGeometry);
|
||||||
|
|
||||||
|
glm::vec3 _translation;
|
||||||
|
glm::quat _rotation;
|
||||||
glm::vec3 _scale;
|
glm::vec3 _scale;
|
||||||
glm::vec3 _offset;
|
glm::vec3 _offset;
|
||||||
|
|
||||||
|
|
|
@ -1,34 +0,0 @@
|
||||||
//
|
|
||||||
// PhysicsEntity.cpp
|
|
||||||
// libraries/physics/src
|
|
||||||
//
|
|
||||||
// Created by Andrew Meadows 2014.06.11
|
|
||||||
// Copyright 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 "PhysicsEntity.h"
|
|
||||||
|
|
||||||
PhysicsEntity::PhysicsEntity() :
|
|
||||||
_translation(0.0f),
|
|
||||||
_rotation(),
|
|
||||||
_boundingRadius(0.0f) {
|
|
||||||
}
|
|
||||||
|
|
||||||
PhysicsEntity::~PhysicsEntity() {
|
|
||||||
}
|
|
||||||
|
|
||||||
void PhysicsEntity::setTranslation(const glm::vec3& translation) {
|
|
||||||
if (_translation != translation) {
|
|
||||||
_translation = translation;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void PhysicsEntity::setRotation(const glm::quat& rotation) {
|
|
||||||
if (_rotation != rotation) {
|
|
||||||
_rotation = rotation;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
@ -1,42 +0,0 @@
|
||||||
//
|
|
||||||
// PhysicsEntity.h
|
|
||||||
// libraries/physics/src
|
|
||||||
//
|
|
||||||
// Created by Andrew Meadows 2014.05.30
|
|
||||||
// Copyright 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_PhysicsEntity_h
|
|
||||||
#define hifi_PhysicsEntity_h
|
|
||||||
|
|
||||||
#include <QVector>
|
|
||||||
#include <QSet>
|
|
||||||
|
|
||||||
#include <glm/glm.hpp>
|
|
||||||
#include <glm/gtc/quaternion.hpp>
|
|
||||||
|
|
||||||
class PhysicsEntity {
|
|
||||||
|
|
||||||
public:
|
|
||||||
PhysicsEntity();
|
|
||||||
virtual ~PhysicsEntity();
|
|
||||||
|
|
||||||
virtual void stepForward(float deltaTime) { }
|
|
||||||
|
|
||||||
void setTranslation(const glm::vec3& translation);
|
|
||||||
void setRotation(const glm::quat& rotation);
|
|
||||||
|
|
||||||
const glm::vec3& getTranslation() const { return _translation; }
|
|
||||||
const glm::quat& getRotation() const { return _rotation; }
|
|
||||||
float getBoundingRadius() const { return _boundingRadius; }
|
|
||||||
|
|
||||||
protected:
|
|
||||||
glm::vec3 _translation;
|
|
||||||
glm::quat _rotation;
|
|
||||||
float _boundingRadius;
|
|
||||||
};
|
|
||||||
|
|
||||||
#endif // hifi_PhysicsEntity_h
|
|
|
@ -372,20 +372,20 @@ QRectF glmToRect(const glm::vec2 & pos, const glm::vec2 & size) {
|
||||||
// create matrix from orientation and position
|
// create matrix from orientation and position
|
||||||
glm::mat4 createMatFromQuatAndPos(const glm::quat& q, const glm::vec3& p) {
|
glm::mat4 createMatFromQuatAndPos(const glm::quat& q, const glm::vec3& p) {
|
||||||
glm::mat4 m = glm::mat4_cast(q);
|
glm::mat4 m = glm::mat4_cast(q);
|
||||||
m[3] = glm::vec4(p, 1);
|
m[3] = glm::vec4(p, 1.0f);
|
||||||
return m;
|
return m;
|
||||||
}
|
}
|
||||||
|
|
||||||
// cancel out roll and pitch
|
// cancel out roll and pitch
|
||||||
glm::quat cancelOutRollAndPitch(const glm::quat& q) {
|
glm::quat cancelOutRollAndPitch(const glm::quat& q) {
|
||||||
glm::vec3 zAxis = q * glm::vec3(0, 0, 1);
|
glm::vec3 zAxis = q * glm::vec3(0.0f, 0.0f, 1.0f);
|
||||||
|
|
||||||
// cancel out the roll and pitch
|
// cancel out the roll and pitch
|
||||||
glm::vec3 newZ = (zAxis.x == 0 && zAxis.z == 0) ? vec3(1, 0, 0) : glm::normalize(vec3(zAxis.x, 0, zAxis.z));
|
glm::vec3 newZ = (zAxis.x == 0 && zAxis.z == 0.0f) ? vec3(1.0f, 0.0f, 0.0f) : glm::normalize(vec3(zAxis.x, 0.0f, zAxis.z));
|
||||||
glm::vec3 newX = glm::cross(vec3(0, 1, 0), newZ);
|
glm::vec3 newX = glm::cross(vec3(0.0f, 1.0f, 0.0f), newZ);
|
||||||
glm::vec3 newY = glm::cross(newZ, newX);
|
glm::vec3 newY = glm::cross(newZ, newX);
|
||||||
|
|
||||||
glm::mat4 temp(glm::vec4(newX, 0), glm::vec4(newY, 0), glm::vec4(newZ, 0), glm::vec4(0, 0, 0, 1));
|
glm::mat4 temp(glm::vec4(newX, 0.0f), glm::vec4(newY, 0.0f), glm::vec4(newZ, 0.0f), glm::vec4(0.0f, 0.0f, 0.0f, 1.0f));
|
||||||
return glm::quat_cast(temp);
|
return glm::quat_cast(temp);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -394,15 +394,15 @@ glm::mat4 cancelOutRollAndPitch(const glm::mat4& m) {
|
||||||
glm::vec3 zAxis = glm::vec3(m[2]);
|
glm::vec3 zAxis = glm::vec3(m[2]);
|
||||||
|
|
||||||
// cancel out the roll and pitch
|
// cancel out the roll and pitch
|
||||||
glm::vec3 newZ = (zAxis.x == 0 && zAxis.z == 0) ? vec3(1, 0, 0) : glm::normalize(vec3(zAxis.x, 0, zAxis.z));
|
glm::vec3 newZ = (zAxis.x == 0.0f && zAxis.z == 0.0f) ? vec3(1.0f, 0.0f, 0.0f) : glm::normalize(vec3(zAxis.x, 0.0f, zAxis.z));
|
||||||
glm::vec3 newX = glm::cross(vec3(0, 1, 0), newZ);
|
glm::vec3 newX = glm::cross(vec3(0.0f, 1.0f, 0.0f), newZ);
|
||||||
glm::vec3 newY = glm::cross(newZ, newX);
|
glm::vec3 newY = glm::cross(newZ, newX);
|
||||||
|
|
||||||
glm::mat4 temp(glm::vec4(newX, 0), glm::vec4(newY, 0), glm::vec4(newZ, 0), m[3]);
|
glm::mat4 temp(glm::vec4(newX, 0.0f), glm::vec4(newY, 0.0f), glm::vec4(newZ, 0.0f), m[3]);
|
||||||
return temp;
|
return temp;
|
||||||
}
|
}
|
||||||
|
|
||||||
glm::vec3 transformPoint(const glm::mat4& m, const glm::vec3& p) {
|
glm::vec3 transformPoint(const glm::mat4& m, const glm::vec3& p) {
|
||||||
glm::vec4 temp = m * glm::vec4(p, 1);
|
glm::vec4 temp = m * glm::vec4(p, 1.0f);
|
||||||
return glm::vec3(temp.x / temp.w, temp.y / temp.w, temp.z / temp.w);
|
return glm::vec3(temp.x / temp.w, temp.y / temp.w, temp.z / temp.w);
|
||||||
}
|
}
|
||||||
|
|
|
@ -93,8 +93,8 @@ template <typename T>
|
||||||
void testByteCountCoded() {
|
void testByteCountCoded() {
|
||||||
testByteCountCodedStable<T>(0);
|
testByteCountCodedStable<T>(0);
|
||||||
testByteCountCodedStable<T>(1);
|
testByteCountCodedStable<T>(1);
|
||||||
testByteCountCodedStable<T>(1 << 16);
|
testByteCountCodedStable<T>(1 << 8*sizeof(T));
|
||||||
testByteCountCodedStable<T>(std::numeric_limits<T>::max() >> 16);
|
testByteCountCodedStable<T>(std::numeric_limits<T>::max() >> 8*sizeof(T));
|
||||||
testByteCountCodedStable<T>(std::numeric_limits<T>::max() >> 8);
|
testByteCountCodedStable<T>(std::numeric_limits<T>::max() >> 8);
|
||||||
testByteCountCodedStable<T>(std::numeric_limits<T>::max() >> 1);
|
testByteCountCodedStable<T>(std::numeric_limits<T>::max() >> 1);
|
||||||
testByteCountCodedStable<T>(std::numeric_limits<T>::max());
|
testByteCountCodedStable<T>(std::numeric_limits<T>::max());
|
||||||
|
|
|
@ -342,8 +342,8 @@ public:
|
||||||
glm::vec3 unitscale { 1.0f };
|
glm::vec3 unitscale { 1.0f };
|
||||||
glm::vec3 up { 0.0f, 1.0f, 0.0f };
|
glm::vec3 up { 0.0f, 1.0f, 0.0f };
|
||||||
|
|
||||||
glm::vec3 cam_pos { 1.5f * sin(t), 0.0f, 2.0f };
|
glm::vec3 cam_pos { 1.5f * sinf(t), 0.0f, 2.0f };
|
||||||
// glm::vec3 camera_focus { 5.0f * cos(t * 0.1f), 0.0f, 0.0f };
|
// glm::vec3 camera_focus { 5.0f * cosf(t * 0.1f), 0.0f, 0.0f };
|
||||||
glm::vec3 camera_focus { 0.0f, 0.0f, 0.0f };
|
glm::vec3 camera_focus { 0.0f, 0.0f, 0.0f };
|
||||||
glm::quat cam_rotation;
|
glm::quat cam_rotation;
|
||||||
// glm::quat cam_rotation = glm::quat_cast(glm::lookAt(cam_pos, camera_focus, up));
|
// glm::quat cam_rotation = glm::quat_cast(glm::lookAt(cam_pos, camera_focus, up));
|
||||||
|
|
Loading…
Reference in a new issue