mirror of
https://github.com/overte-org/overte.git
synced 2025-04-19 15:43:50 +02:00
Magballs: Removing entity scripts, using onUpdate
This commit is contained in:
parent
a574251173
commit
9d07bf1585
12 changed files with 619 additions and 460 deletions
|
@ -1,124 +0,0 @@
|
|||
//
|
||||
// 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
|
||||
//
|
||||
|
||||
(function(){
|
||||
this.preload = function(entityId) {
|
||||
this.MIN_CHECK_INTERVAL = 0.05;
|
||||
this.MAX_VARIANCE = 0.005;
|
||||
this.ZERO_VECTOR = { x: 0, y: 0, z: 0 };
|
||||
|
||||
this.entityId = entityId;
|
||||
var properties = Entities.getEntityProperties(this.entityId);
|
||||
var userData = JSON.parse(properties.userData);
|
||||
this.start = userData.magBalls.start;
|
||||
this.end = userData.magBalls.end;
|
||||
this.desiredLength = userData.magBalls.length;
|
||||
this.timeSinceLastUpdate = 0;
|
||||
this.nextCheckInterval = this.MIN_CHECK_INTERVAL;
|
||||
|
||||
// FIXME do I really need to do this nonsense?
|
||||
var _this = this;
|
||||
this.updateWrapper = function(deltaTime) {
|
||||
_this.onUpdate(deltaTime);
|
||||
};
|
||||
Script.update.connect(this.updateWrapper);
|
||||
|
||||
Script.scriptEnding.connect(function() {
|
||||
_this.onCleanup();
|
||||
});
|
||||
Entities.deletingEntity.connect(function(entityId) {
|
||||
_this.onCleanup();
|
||||
});
|
||||
};
|
||||
|
||||
this.onUpdate = function(deltaTime) {
|
||||
this.timeSinceLastUpdate += deltaTime;
|
||||
if (this.timeSinceLastUpdate > this.nextCheckInterval) {
|
||||
this.updateProperties();
|
||||
this.timeSinceLastUpdate = 0;
|
||||
var length = this.getLength();
|
||||
if (length == 0) {
|
||||
this.onCleanup();
|
||||
return;
|
||||
}
|
||||
var variance = this.getVariance(length);
|
||||
if (Math.abs(variance) <= this.MAX_VARIANCE) {
|
||||
this.incrementCheckInterval();
|
||||
return;
|
||||
}
|
||||
this.decrementCheckInterval();
|
||||
var adjustmentVector = Vec3.multiply(variance / 4, this.vector);
|
||||
var newPosition = Vec3.sum(Vec3.multiply(-1, adjustmentVector), this.position);
|
||||
var newVector = Vec3.sum(Vec3.multiply(2, adjustmentVector), this.vector);
|
||||
var newLength = Vec3.length(newVector);
|
||||
var newVariance = this.getVariance(newLength);
|
||||
Entities.editEntity(this.entityId, {
|
||||
position: newPosition,
|
||||
linePoints: [ this.ZERO_VECTOR, newVector ]
|
||||
});
|
||||
Entities.editEntity(this.start, {
|
||||
position: newPosition
|
||||
});
|
||||
Entities.editEntity(this.end, {
|
||||
position: Vec3.sum(newPosition, newVector)
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
this.incrementCheckInterval = function() {
|
||||
this.nextCheckInterval = Math.min(this.nextCheckInterval * 2.0, 1.0);
|
||||
}
|
||||
|
||||
this.decrementCheckInterval = function() {
|
||||
this.nextCheckInterval = 0.05;
|
||||
}
|
||||
|
||||
this.onCleanup = function() {
|
||||
if (this.updateWrapper) {
|
||||
Script.update.disconnect(this.updateWrapper);
|
||||
delete this.updateWrapper;
|
||||
}
|
||||
}
|
||||
|
||||
this.getVariance = function(length) {
|
||||
if (!length) {
|
||||
length = this.getLength();
|
||||
}
|
||||
var difference = this.desiredLength - length;
|
||||
return difference / this.desiredLength;
|
||||
}
|
||||
|
||||
this.getLength = function() {
|
||||
return Vec3.length(this.vector);
|
||||
}
|
||||
|
||||
this.getPosition = function(entityId) {
|
||||
var properties = Entities.getEntityProperties(entityId);
|
||||
return properties.position;
|
||||
}
|
||||
|
||||
this.updateProperties = function() {
|
||||
var properties = Entities.getEntityProperties(this.entityId);
|
||||
var curStart = properties.position;
|
||||
var curVector = properties.linePoints[1]
|
||||
var curEnd = Vec3.sum(curVector, curStart);
|
||||
var startPos = this.getPosition(this.start);
|
||||
var endPos = this.getPosition(this.end);
|
||||
var startError = Vec3.distance(curStart, startPos);
|
||||
var endError = Vec3.distance(curEnd, endPos);
|
||||
this.vector = Vec3.subtract(endPos, startPos);
|
||||
if (startError > 0.005 || endError > 0.005) {
|
||||
print("Fixing up edge");
|
||||
Entities.editEntity(this.entityId, {
|
||||
position: startPos,
|
||||
linePoints: [ this.ZERO_VECTOR, this.vector ]
|
||||
});
|
||||
}
|
||||
this.position = startPos;
|
||||
}
|
||||
});
|
|
@ -1,306 +0,0 @@
|
|||
//
|
||||
// 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("magSticks/constants.js");
|
||||
Script.include("magSticks/utils.js");
|
||||
Script.include("magSticks/graph.js");
|
||||
Script.include("magSticks/highlighter.js");
|
||||
Script.include("magSticks/handController.js");
|
||||
|
||||
var BALL_NAME = "MagBall"
|
||||
var EDGE_NAME = "MagStick"
|
||||
var BALL_SELECTION_RADIUS = BALL_SIZE / 2.0 * 1.5;
|
||||
|
||||
var BALL_DIMENSIONS = {
|
||||
x: BALL_SIZE,
|
||||
y: BALL_SIZE,
|
||||
z: BALL_SIZE
|
||||
};
|
||||
|
||||
var BALL_COLOR = {
|
||||
red: 128,
|
||||
green: 128,
|
||||
blue: 128
|
||||
};
|
||||
|
||||
var STICK_DIMENSIONS = {
|
||||
x: STICK_LENGTH / 6,
|
||||
y: STICK_LENGTH / 6,
|
||||
z: STICK_LENGTH
|
||||
};
|
||||
|
||||
var BALL_DISTANCE = STICK_LENGTH + BALL_SIZE;
|
||||
|
||||
var BALL_PROTOTYPE = {
|
||||
type: "Sphere",
|
||||
name: BALL_NAME,
|
||||
dimensions: BALL_DIMENSIONS,
|
||||
color: BALL_COLOR,
|
||||
ignoreCollisions: true,
|
||||
collisionsWillMove: false
|
||||
};
|
||||
|
||||
// 2 millimeters
|
||||
var EPSILON = (.002) / BALL_DISTANCE;
|
||||
|
||||
var LINE_DIMENSIONS = {
|
||||
x: 5,
|
||||
y: 5,
|
||||
z: 5
|
||||
}
|
||||
|
||||
//var EDGE_ENTITY_SCRIPT_BASE = "file:/Users/bdavis/Git/hifi/examples/entityScripts";
|
||||
var EDGE_ENTITY_SCRIPT_BASE = "https://s3.amazonaws.com/hifi-public/scripts/entityScripts";
|
||||
var EDGE_ENTITY_SCRIPT = EDGE_ENTITY_SCRIPT_BASE + "/magBallEdge.js",
|
||||
|
||||
var LINE_PROTOTYPE = {
|
||||
type: "Line",
|
||||
name: EDGE_NAME,
|
||||
color: COLORS.CYAN,
|
||||
dimensions: LINE_DIMENSIONS,
|
||||
lineWidth: 5,
|
||||
visible: true,
|
||||
ignoreCollisions: true,
|
||||
collisionsWillMove: false,
|
||||
script: EDGE_ENTITY_SCRIPT
|
||||
}
|
||||
|
||||
var 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
|
||||
// }
|
||||
|
||||
|
||||
// A collection of balls and edges connecting them.
|
||||
MagBalls = function() {
|
||||
this.selectedNodes = {};
|
||||
Graph.call(this);
|
||||
Script.scriptEnding.connect(function() {
|
||||
_this.onCleanup();
|
||||
});
|
||||
}
|
||||
|
||||
MagBalls.prototype = Object.create( Graph.prototype );
|
||||
|
||||
MagBalls.prototype.onUpdate = function(deltaTime) {
|
||||
// FIXME move to a physics based implementation as soon as bullet
|
||||
// is exposed to entities
|
||||
}
|
||||
|
||||
MagBalls.prototype.createNodeEntity = function(customProperies) {
|
||||
return Entities.addEntity(mergeObjects(BALL_PROTOTYPE, customProperies));
|
||||
}
|
||||
|
||||
MagBalls.prototype.createEdgeEntity = function(nodeIdA, nodeIdB) {
|
||||
var apos = this.getNodePosition(nodeIdA);
|
||||
var bpos = this.getNodePosition(nodeIdB);
|
||||
return 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
|
||||
}
|
||||
})
|
||||
}));
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
// Too far to attempt
|
||||
var distance = this.getNodeDistance(nodeId, otherNodeId);
|
||||
var variance = this.getVariance(distance);
|
||||
if (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.breakEdges(selected);
|
||||
this.selectedNodes[selected] = true;
|
||||
}
|
||||
return selected;
|
||||
}
|
||||
|
||||
MagBalls.prototype.releaseBall = function(releasedBall) {
|
||||
delete this.selectedNodes[releasedBall];
|
||||
logDebug("Released ball: " + releasedBall);
|
||||
|
||||
// FIXME iterate through the other balls and ensure we don't intersect with
|
||||
// any of them. If we do, just delete this ball and return. (play a pop
|
||||
// sound)
|
||||
|
||||
var releasePosition = this.getNodePosition(releasedBall);
|
||||
|
||||
// Don't overlap other balls
|
||||
for (var nodeId in this.nodes) {
|
||||
if (nodeId == releasedBall) {
|
||||
continue;
|
||||
}
|
||||
var distance = this.getNodeDistance(releasedBall, nodeId);
|
||||
if (distance < BALL_SIZE / 2.0) {
|
||||
this.destroyNode(nodeId);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
var targets = this.findPotentialEdges(releasedBall);
|
||||
for (var otherBallId in targets) {
|
||||
this.createEdge(otherBallId, releasedBall);
|
||||
}
|
||||
this.clean();
|
||||
this.validate();
|
||||
}
|
||||
|
||||
|
||||
MagBalls.prototype.getVariance = function(distance) {
|
||||
// Given two points, how big is the difference between their distance
|
||||
// and the desired length length
|
||||
return (Math.abs(distance - BALL_DISTANCE)) / BALL_DISTANCE;
|
||||
}
|
||||
|
||||
// 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);
|
||||
}
|
||||
}
|
||||
|
||||
var magBalls = new MagBalls();
|
||||
|
||||
// Clear any previous balls
|
||||
// magBalls.clear();
|
||||
|
||||
// How close do we need to be to a ball to select it.... radius + 10%
|
||||
|
||||
BallController = function(side) {
|
||||
HandController.call(this, side);
|
||||
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 = 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 = magBalls.findPotentialEdges(this.selected);
|
||||
for (var ballId in targetBalls) {
|
||||
if (!this.ghostEdges[ballId]) {
|
||||
// create the ovleray
|
||||
this.ghostEdges[ballId] = Overlays.addOverlay("line3d", {
|
||||
start: magBalls.getNodePosition(ballId),
|
||||
end: this.tipPosition,
|
||||
color: { red: 255, green: 0, blue: 0},
|
||||
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 = magBalls.grabBall(this.tipPosition, BALL_SELECTION_RADIUS);
|
||||
this.highlighter.highlight(null);
|
||||
}
|
||||
|
||||
BallController.prototype.onRelease = function() {
|
||||
this.clearGhostEdges();
|
||||
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();
|
||||
}
|
||||
|
||||
// FIXME resolve some of the issues with dual controllers before allowing both controllers active
|
||||
var handControllers = [new BallController(LEFT_CONTROLLER)]; //, new HandController(RIGHT) ];
|
74
examples/toys/magBalls/ballController.js
Normal file
74
examples/toys/magBalls/ballController.js
Normal file
|
@ -0,0 +1,74 @@
|
|||
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 = {};
|
||||
this.lastUpdate = 0;
|
||||
this.updateInterval = 0.05;
|
||||
}
|
||||
|
||||
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();
|
||||
}
|
|
@ -16,6 +16,10 @@ 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 = {
|
||||
|
@ -66,3 +70,71 @@ COLORS = {
|
|||
}
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
|
@ -16,12 +16,16 @@ Graph = function() {
|
|||
nodeId2: {
|
||||
edgeId1: true
|
||||
},
|
||||
// Nodes can many edges
|
||||
nodeId3: {
|
||||
edgeId2: true
|
||||
edgeId3: true
|
||||
edgeId4: true
|
||||
edgeId5: true
|
||||
},
|
||||
// Nodes can have 0 edges
|
||||
nodeId5: {
|
||||
},
|
||||
...
|
||||
}
|
||||
*/
|
|
@ -54,7 +54,7 @@ HandController.prototype.setActive = function(active) {
|
|||
if (active == this.active) {
|
||||
return;
|
||||
}
|
||||
logDebug("Setting active: " + active);
|
||||
logDebug("Hand controller changing active state: " + active);
|
||||
this.active = active;
|
||||
Overlays.editOverlay(this.pointer, {
|
||||
visible: this.active
|
||||
|
@ -68,12 +68,8 @@ HandController.prototype.updateControllerState = function() {
|
|||
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);
|
||||
if (!this.active) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
HandController.prototype.onCleanup = function() {
|
289
examples/toys/magBalls/magBalls.js
Normal file
289
examples/toys/magBalls/magBalls.js
Normal file
|
@ -0,0 +1,289 @@
|
|||
//
|
||||
// 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.lastUpdateAge = 0;
|
||||
this.stable = false;
|
||||
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) {
|
||||
// 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.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.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.clean();
|
||||
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]));
|
||||
}
|
||||
|
26
examples/toys/magBalls/magBallsMain.js
Normal file
26
examples/toys/magBalls/magBallsMain.js
Normal file
|
@ -0,0 +1,26 @@
|
|||
//
|
||||
// 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) ];
|
|
@ -28,27 +28,6 @@ findAction = function(name) {
|
|||
return 0;
|
||||
}
|
||||
|
||||
|
||||
var LINE_DIMENSIONS = {
|
||||
x: 5,
|
||||
y: 5,
|
||||
z: 5
|
||||
}
|
||||
|
||||
var EDGE_NAME = "MagStick"
|
||||
|
||||
var LINE_PROTOTYPE = {
|
||||
type: "Line",
|
||||
name: EDGE_NAME,
|
||||
color: COLORS.CYAN,
|
||||
dimensions: LINE_DIMENSIONS,
|
||||
lineWidth: 5,
|
||||
visible: true,
|
||||
ignoreCollisions: true,
|
||||
collisionsWillMove: false
|
||||
}
|
||||
|
||||
|
||||
addLine = function(origin, vector, color) {
|
||||
if (!color) {
|
||||
color = COLORS.WHITE
|
||||
|
@ -65,7 +44,8 @@ addLine = function(origin, vector, color) {
|
|||
|
||||
// FIXME fetch from a subkey of user data to support non-destructive modifications
|
||||
setEntityUserData = function(id, data) {
|
||||
Entities.editEntity(id, { userData: JSON.stringify(data) });
|
||||
var json = JSON.stringify(data)
|
||||
Entities.editEntity(id, { userData: json });
|
||||
}
|
||||
|
||||
// FIXME do non-destructive modification of the existing user data
|
||||
|
@ -73,9 +53,9 @@ getEntityUserData = function(id) {
|
|||
var results = null;
|
||||
var properties = Entities.getEntityProperties(id);
|
||||
if (properties.userData) {
|
||||
results = JSON.parse(this.properties.userData);
|
||||
results = JSON.parse(properties.userData);
|
||||
}
|
||||
return results;
|
||||
return results ? results : {};
|
||||
}
|
||||
|
||||
// Non-destructively modify the user data of an entity.
|
||||
|
@ -86,10 +66,18 @@ setEntityCustomData = function(customKey, id, data) {
|
|||
}
|
||||
|
||||
getEntityCustomData = function(customKey, id, defaultValue) {
|
||||
var userData = getEntityUserData();
|
||||
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) {
|
Loading…
Reference in a new issue