mirror of
https://github.com/overte-org/overte.git
synced 2025-08-06 16:36:54 +02:00
Merge branch 'master' of github.com:highfidelity/hifi into polyvox-again
This commit is contained in:
commit
0dfe6e8054
11 changed files with 1351 additions and 0 deletions
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);
|
||||||
|
}
|
Loading…
Reference in a new issue