mirror of
https://github.com/overte-org/overte.git
synced 2025-08-09 08:49:05 +02:00
Magnetic sticks and balls
This commit is contained in:
parent
08986dcb17
commit
38b62108af
6 changed files with 767 additions and 0 deletions
92
examples/toys/magSticks.js
Normal file
92
examples/toys/magSticks.js
Normal file
|
@ -0,0 +1,92 @@
|
||||||
|
//
|
||||||
|
// 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/utils.js");
|
||||||
|
Script.include("magSticks/constants.js");
|
||||||
|
Script.include("magSticks/magBalls.js");
|
||||||
|
Script.include("magSticks/highlighter.js");
|
||||||
|
Script.include("magSticks/handController.js");
|
||||||
|
|
||||||
|
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%
|
||||||
|
var BALL_SELECTION_RADIUS = BALL_SIZE / 2.0 * 1.1;
|
||||||
|
|
||||||
|
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.updateControllerState.call(this, deltaTime);
|
||||||
|
if (!this.selected) {
|
||||||
|
// Find the highlight target and set it.
|
||||||
|
var target = magBalls.findNearestBall(this.tipPosition, BALL_SELECTION_RADIUS);
|
||||||
|
this.highlighter.highlight(target);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.highlighter.highlight(null);
|
||||||
|
Entities.editEntity(this.selected, { position: this.tipPosition });
|
||||||
|
var targetBalls = magBalls.findMatches(this.selected);
|
||||||
|
for (var ballId in targetBalls) {
|
||||||
|
if (!this.ghostEdges[ballId]) {
|
||||||
|
// create the ovleray
|
||||||
|
this.ghostEdges[ballId] = Overlays.addOverlay("line3d", {
|
||||||
|
start: magBalls.getBallPosition(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.updateControllerState.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) ];
|
16
examples/toys/magSticks/constants.js
Normal file
16
examples/toys/magSticks/constants.js
Normal file
|
@ -0,0 +1,16 @@
|
||||||
|
//
|
||||||
|
// 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;
|
||||||
|
|
94
examples/toys/magSticks/handController.js
Normal file
94
examples/toys/magSticks/handController.js
Normal file
|
@ -0,0 +1,94 @@
|
||||||
|
|
||||||
|
LEFT_CONTROLLER = 0;
|
||||||
|
RIGHT_CONTROLLER = 1;
|
||||||
|
|
||||||
|
|
||||||
|
HandController = function(side) {
|
||||||
|
this.side = side;
|
||||||
|
this.palm = 2 * side;
|
||||||
|
this.tip = 2 * side + 1;
|
||||||
|
this.action = findAction(side ? "ACTION2" : "ACTION1");
|
||||||
|
this.active = false;
|
||||||
|
this.pointer = Overlays.addOverlay("sphere", {
|
||||||
|
position: {
|
||||||
|
x: 0,
|
||||||
|
y: 0,
|
||||||
|
z: 0
|
||||||
|
},
|
||||||
|
size: 0.01,
|
||||||
|
color: {
|
||||||
|
red: 255,
|
||||||
|
green: 255,
|
||||||
|
blue: 0
|
||||||
|
},
|
||||||
|
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) {
|
||||||
|
if (action == this.action) {
|
||||||
|
if (state) {
|
||||||
|
this.onClick();
|
||||||
|
} else {
|
||||||
|
this.onRelease();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
HandController.prototype.setActive = function(active) {
|
||||||
|
if (active == this.active) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
debugPrint("Setting active: " + active);
|
||||||
|
this.active = active;
|
||||||
|
Overlays.editOverlay(this.pointer, {
|
||||||
|
position: this.tipPosition,
|
||||||
|
visible: this.active
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
HandController.prototype.updateControllerState = function() {
|
||||||
|
var palmPos = Controller.getSpatialControlPosition(this.palm);
|
||||||
|
// When on the base hydras report a position of 0
|
||||||
|
this.setActive(Vec3.length(palmPos) > 0.001);
|
||||||
|
if (!this.active) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
var tipPos = Controller.getSpatialControlPosition(this.tip);
|
||||||
|
this.tipPosition = scaleLine(palmPos, tipPos, 1.4);
|
||||||
|
Overlays.editOverlay(this.pointer, {
|
||||||
|
position: this.tipPosition
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
HandController.prototype.onCleanup = function() {
|
||||||
|
Overlays.deleteOverlay(this.pointer);
|
||||||
|
}
|
||||||
|
|
||||||
|
HandController.prototype.onUpdate = function(deltaTime) {
|
||||||
|
this.updateControllerState();
|
||||||
|
}
|
||||||
|
|
||||||
|
HandController.prototype.onClick = function() {
|
||||||
|
debugPrint("Base hand controller does nothing on click");
|
||||||
|
}
|
||||||
|
|
||||||
|
HandController.prototype.onRelease = function() {
|
||||||
|
debugPrint("Base hand controller does nothing on release");
|
||||||
|
}
|
63
examples/toys/magSticks/highlighter.js
Normal file
63
examples/toys/magSticks/highlighter.js
Normal file
|
@ -0,0 +1,63 @@
|
||||||
|
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
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
454
examples/toys/magSticks/magBalls.js
Normal file
454
examples/toys/magSticks/magBalls.js
Normal file
|
@ -0,0 +1,454 @@
|
||||||
|
var BALL_NAME = "MagBall"
|
||||||
|
|
||||||
|
var EDGE_NAME = "MagStick"
|
||||||
|
|
||||||
|
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
|
||||||
|
};
|
||||||
|
|
||||||
|
// A collection of balls and edges connecting them.
|
||||||
|
MagBalls = function() {
|
||||||
|
/*
|
||||||
|
this.balls: {
|
||||||
|
ballId1: {
|
||||||
|
edgeId1: true
|
||||||
|
}
|
||||||
|
ballId2: {
|
||||||
|
edgeId1: true
|
||||||
|
},
|
||||||
|
ballId3: {
|
||||||
|
edgeId2: true
|
||||||
|
edgeId3: true
|
||||||
|
edgeId4: true
|
||||||
|
edgeId5: true
|
||||||
|
},
|
||||||
|
...
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
this.balls = {};
|
||||||
|
/*
|
||||||
|
this.edges: {
|
||||||
|
edgeId1: {
|
||||||
|
ballId1: true
|
||||||
|
ballId2: true
|
||||||
|
},
|
||||||
|
edgeId2: {
|
||||||
|
ballId3: true
|
||||||
|
ballId4: true
|
||||||
|
},
|
||||||
|
...
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
|
||||||
|
// FIXME initialize from nearby entities
|
||||||
|
this.edges = {};
|
||||||
|
this.selectedBalls = {};
|
||||||
|
|
||||||
|
var _this = this;
|
||||||
|
Script.update.connect(function(deltaTime) {
|
||||||
|
_this.onUpdate(deltaTime);
|
||||||
|
});
|
||||||
|
|
||||||
|
Script.scriptEnding.connect(function() {
|
||||||
|
_this.onCleanup();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
MagBalls.prototype.findNearestBall = function(position, maxDist) {
|
||||||
|
var resultId = null;
|
||||||
|
var resultDist = 0;
|
||||||
|
for (var id in this.balls) {
|
||||||
|
var properties = Entities.getEntityProperties(id);
|
||||||
|
var curDist = Vec3.distance(properties.position, position);
|
||||||
|
if (!maxDist || curDist <= maxDist) {
|
||||||
|
if (!resultId || curDist < resultDist) {
|
||||||
|
resultId = id;
|
||||||
|
resultDist = curDist;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return resultId;
|
||||||
|
}
|
||||||
|
|
||||||
|
// FIXME move to a physics based implementation as soon as bullet
|
||||||
|
// is exposed to entities
|
||||||
|
MagBalls.prototype.onUpdate = function(deltaTime) {
|
||||||
|
}
|
||||||
|
|
||||||
|
function mergeObjects(proto, custom) {
|
||||||
|
var result = {};
|
||||||
|
for (var attrname in proto) {
|
||||||
|
result[attrname] = proto[attrname];
|
||||||
|
}
|
||||||
|
for (var attrname in custom) {
|
||||||
|
result[attrname] = custom[attrname];
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
MagBalls.prototype.createBall = function(customProperies) {
|
||||||
|
var ballId = Entities.addEntity(mergeObjects(BALL_PROTOTYPE, customProperies));
|
||||||
|
this.balls[ballId] = {};
|
||||||
|
this.validate();
|
||||||
|
return ballId;
|
||||||
|
}
|
||||||
|
|
||||||
|
MagBalls.prototype.grabBall = function(position, maxDist) {
|
||||||
|
var selected = this.findNearestBall(position, maxDist);
|
||||||
|
if (!selected) {
|
||||||
|
selected = this.createBall({ position: position });
|
||||||
|
}
|
||||||
|
if (selected) {
|
||||||
|
this.breakEdges(selected);
|
||||||
|
this.selectedBalls[selected] = true;
|
||||||
|
}
|
||||||
|
return selected;
|
||||||
|
}
|
||||||
|
|
||||||
|
MagBalls.prototype.findMatches = function(ballId) {
|
||||||
|
var variances = {};
|
||||||
|
for (var otherBallId in this.balls) {
|
||||||
|
if (otherBallId == ballId || this.areConnected(otherBallId, ballId)) {
|
||||||
|
// can't self connect or doubly connect
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
var variance = this.getStickLengthVariance(ballId, otherBallId);
|
||||||
|
if (variance > BALL_DISTANCE / 4) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
variances[otherBallId] = variance;
|
||||||
|
}
|
||||||
|
return variances;
|
||||||
|
}
|
||||||
|
|
||||||
|
MagBalls.prototype.releaseBall = function(releasedBall) {
|
||||||
|
delete this.selectedBalls[releasedBall];
|
||||||
|
debugPrint("Released ball: " + releasedBall);
|
||||||
|
|
||||||
|
// sort other balls by distance from the stick length
|
||||||
|
var edgeTargetBall = null;
|
||||||
|
do {
|
||||||
|
var releasePosition = this.getBallPosition(releasedBall);
|
||||||
|
// Get the list of candidate connections
|
||||||
|
var variances = this.findMatches(releasedBall);
|
||||||
|
// sort them by the difference from an ideal distance
|
||||||
|
var sortedBalls = Object.keys(variances);
|
||||||
|
if (!sortedBalls.length) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
sortedBalls.sort(function(a, b){
|
||||||
|
return variances[a] - variances[b];
|
||||||
|
});
|
||||||
|
// find the nearest matching unconnected ball
|
||||||
|
edgeTargetBall = sortedBalls[0];
|
||||||
|
// note that createEdge will preferentially move the second parameter, the target ball
|
||||||
|
} while(this.createEdge(edgeTargetBall, releasedBall))
|
||||||
|
|
||||||
|
this.clean();
|
||||||
|
}
|
||||||
|
|
||||||
|
// FIXME the Quat should be able to do this
|
||||||
|
function findOrientation(from, to) {
|
||||||
|
//float m = sqrt(2.f + 2.f * dot(u, v));
|
||||||
|
//vec3 w = (1.f / m) * cross(u, v);
|
||||||
|
//return quat(0.5f * m, w.x, w.y, w.z);
|
||||||
|
var v2 = Vec3.normalize(Vec3.subtract(to, from));
|
||||||
|
var v1 = { x: 0.0, y: 1.0, z: 0.0 };
|
||||||
|
var m = Math.sqrt(2 + 2 * Vec3.dot(v1, v2));
|
||||||
|
var w = Vec3.multiply(1.0 / m, Vec3.cross(v1, v2));
|
||||||
|
return {
|
||||||
|
w: 0.5 * m,
|
||||||
|
x: w.x,
|
||||||
|
y: w.y,
|
||||||
|
z: w.z
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
var LINE_DIMENSIONS = 5;
|
||||||
|
|
||||||
|
var EDGE_PROTOTYPE = {
|
||||||
|
type: "Line",
|
||||||
|
name: EDGE_NAME,
|
||||||
|
color: { red: 0, green: 255, blue: 255 },
|
||||||
|
dimensions: {
|
||||||
|
x: LINE_DIMENSIONS,
|
||||||
|
y: LINE_DIMENSIONS,
|
||||||
|
z: LINE_DIMENSIONS
|
||||||
|
},
|
||||||
|
lineWidth: 5,
|
||||||
|
visible: true,
|
||||||
|
ignoreCollisions: true,
|
||||||
|
collisionsWillMove: false
|
||||||
|
}
|
||||||
|
|
||||||
|
var ZERO_VECTOR = {
|
||||||
|
x: 0,
|
||||||
|
y: 0,
|
||||||
|
z: 0
|
||||||
|
};
|
||||||
|
|
||||||
|
//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
|
||||||
|
//}
|
||||||
|
|
||||||
|
MagBalls.prototype.createEdge = function(from, to) {
|
||||||
|
// FIXME find the constraints on from an to and determine if there is an intersection.
|
||||||
|
// Do only first order scanning for now, unless we can expose a mechanism for interacting
|
||||||
|
// to reach equilibrium via Bullet
|
||||||
|
// * a ball zero edges is fully free...
|
||||||
|
// * a ball with one edge free to move on a sphere section
|
||||||
|
// * a ball with two edges is free to move in a circle
|
||||||
|
// * a ball with more than two edges is not free
|
||||||
|
|
||||||
|
var fromPos = this.getBallPosition(from);
|
||||||
|
var toPos = this.getBallPosition(to);
|
||||||
|
var vector = Vec3.subtract(toPos, fromPos);
|
||||||
|
var originalLength = Vec3.length(originalLength);
|
||||||
|
|
||||||
|
// if they're already at a close enough distance, just create the edge
|
||||||
|
if ((originalLength - BALL_DISTANCE) > (BALL_DISTANCE * 0.01)) {
|
||||||
|
//code
|
||||||
|
} else {
|
||||||
|
// Attempt to move the ball to match the distance
|
||||||
|
vector = Vec3.multiply(BALL_DISTANCE, Vec3.normalize(vector));
|
||||||
|
// Zero edges for the destination
|
||||||
|
var edgeCount = Object.keys(this.balls[to]).length;
|
||||||
|
if (!edgeCount) {
|
||||||
|
// update the entity
|
||||||
|
var newPosition = Vec3.sum(vector, fromPos);
|
||||||
|
Entities.editEntity(to, { position: newPosition });
|
||||||
|
} else if (1 == edgeCount) {
|
||||||
|
// FIXME
|
||||||
|
// find the other end of the edge already connected, call it ball2
|
||||||
|
// given two spheres of radius BALL_DISTANCE centered at fromPos and ball2.position,
|
||||||
|
// find the closest point of intersection to toPos
|
||||||
|
// move the ball to toPos
|
||||||
|
} else if (2 == edgeCount) {
|
||||||
|
// FIXME
|
||||||
|
} else {
|
||||||
|
// FIXME check for the ability to move fromPos
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// Fixup existing edges
|
||||||
|
for (var edgeId in this.balls[to]) {
|
||||||
|
this.fixupEdge(edgeId);
|
||||||
|
}
|
||||||
|
|
||||||
|
// FIXME, find the correct orientation for a box or model between the two balls
|
||||||
|
// for now use a line
|
||||||
|
var newEdge = Entities.addEntity(mergeObjects(EDGE_PROTOTYPE, this.findEdgeParams(from, to)));
|
||||||
|
|
||||||
|
this.edges[newEdge] = {};
|
||||||
|
this.edges[newEdge][from] = true;
|
||||||
|
this.edges[newEdge][to] = true;
|
||||||
|
this.balls[from][newEdge] = true;
|
||||||
|
this.balls[to][newEdge] = true;
|
||||||
|
this.validate();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
MagBalls.prototype.findEdgeParams = function(startBall, endBall) {
|
||||||
|
var startBallPos = this.getBallPosition(startBall);
|
||||||
|
var endBallPos = this.getBallPosition(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]));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Given two balls, how big is the difference between their distances and the stick length
|
||||||
|
MagBalls.prototype.getStickLengthVariance = function(a, b) {
|
||||||
|
var apos = this.getBallPosition(a);
|
||||||
|
var bpos = this.getBallPosition(b);
|
||||||
|
var distance = Vec3.distance(apos, bpos);
|
||||||
|
var variance = Math.abs(distance - BALL_DISTANCE);
|
||||||
|
return variance;
|
||||||
|
}
|
||||||
|
|
||||||
|
// FIXME remove unconnected balls
|
||||||
|
MagBalls.prototype.clean = function() {
|
||||||
|
//var deletedBalls = {};
|
||||||
|
//if (Object.keys(this.balls).length > 1) {
|
||||||
|
// for (var ball in this.balls) {
|
||||||
|
// if (!this.getConnections(ball)) {
|
||||||
|
// deletedBalls[ball] = true;
|
||||||
|
// Entities.deleteEntity(ball);
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
//}
|
||||||
|
//for (var ball in deletedBalls) {
|
||||||
|
// delete this.balls[ball];
|
||||||
|
//}
|
||||||
|
}
|
||||||
|
|
||||||
|
MagBalls.prototype.getBallPosition = function(ball) {
|
||||||
|
var properties = Entities.getEntityProperties(ball);
|
||||||
|
return properties.position;
|
||||||
|
}
|
||||||
|
|
||||||
|
MagBalls.prototype.breakEdges = function(ballId) {
|
||||||
|
for (var edgeId in this.balls[ballId]) {
|
||||||
|
this.destroyEdge(edgeId);
|
||||||
|
}
|
||||||
|
// This shouldn't be necessary
|
||||||
|
this.balls[ballId] = {};
|
||||||
|
}
|
||||||
|
|
||||||
|
MagBalls.prototype.destroyEdge = function(edgeId) {
|
||||||
|
logDebug("Deleting edge " + edgeId);
|
||||||
|
// Delete the edge from other balls
|
||||||
|
for (var edgeBallId in this.edges[edgeId]) {
|
||||||
|
delete this.balls[edgeBallId][edgeId];
|
||||||
|
}
|
||||||
|
delete this.edges[edgeId];
|
||||||
|
Entities.deleteEntity(edgeId);
|
||||||
|
this.validate();
|
||||||
|
}
|
||||||
|
|
||||||
|
MagBalls.prototype.destroyBall = function(ballId) {
|
||||||
|
logDebug("Deleting ball " + ballId);
|
||||||
|
breakEdges(ballId);
|
||||||
|
Entities.deleteEntity(ballId);
|
||||||
|
}
|
||||||
|
|
||||||
|
MagBalls.prototype.clear = function() {
|
||||||
|
if (DEBUG_MAGSTICKS) {
|
||||||
|
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.areConnected = function(a, b) {
|
||||||
|
for (var edge in this.balls[a]) {
|
||||||
|
// edge already exists
|
||||||
|
if (this.balls[b][edge]) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
MagBalls.prototype.validate = function() {
|
||||||
|
var error = false;
|
||||||
|
for (ballId in this.balls) {
|
||||||
|
for (edgeId in this.balls[ballId]) {
|
||||||
|
var edge = this.edges[edgeId];
|
||||||
|
if (!edge) {
|
||||||
|
logError("Error: ball " + ballId + " refers to unknown edge " + edgeId);
|
||||||
|
error = true;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (!edge[ballId]) {
|
||||||
|
logError("Error: ball " + ballId + " refers to edge " + edgeId + " but not vice versa");
|
||||||
|
error = true;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (edgeId in this.edges) {
|
||||||
|
for (ballId in this.edges[edgeId]) {
|
||||||
|
var ball = this.balls[ballId];
|
||||||
|
if (!ball) {
|
||||||
|
logError("Error: edge " + edgeId + " refers to unknown ball " + ballId);
|
||||||
|
error = true;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (!ball[edgeId]) {
|
||||||
|
logError("Error: edge " + edgeId + " refers to ball " + ballId + " but not vice versa");
|
||||||
|
error = true;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (error) {
|
||||||
|
logDebug(JSON.stringify({ edges: this.edges, balls: this.balls }, null, 2));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// FIXME fetch from a subkey of user data to support non-destructive modifications
|
||||||
|
MagBalls.prototype.setUserData = function(id, data) {
|
||||||
|
Entities.editEntity(id, { userData: JSON.stringify(data) });
|
||||||
|
}
|
||||||
|
|
||||||
|
// FIXME do non-destructive modification of the existing user data
|
||||||
|
MagBalls.prototype.getUserData = function(id) {
|
||||||
|
var results = null;
|
||||||
|
var properties = Entities.getEntityProperties(id);
|
||||||
|
if (properties.userData) {
|
||||||
|
results = JSON.parse(this.properties.userData);
|
||||||
|
}
|
||||||
|
return results;
|
||||||
|
}
|
||||||
|
|
||||||
|
//MagBalls.prototype.findBalls = function() {
|
||||||
|
// var ids = Entities.findEntities(MyAvatar.position, 50);
|
||||||
|
// var result = [];
|
||||||
|
// ids.forEach(function(id) {
|
||||||
|
// var properties = Entities.getEntityProperties(id);
|
||||||
|
// if (properties.name == BALL_NAME) {
|
||||||
|
// result.push(id);
|
||||||
|
// }
|
||||||
|
// }, this);
|
||||||
|
// return result;
|
||||||
|
//};
|
||||||
|
//
|
||||||
|
//MagBalls.prototype.findEdges = function() {
|
||||||
|
// var ids = Entities.findEntities(MyAvatar.position, 50);
|
||||||
|
// var result = [];
|
||||||
|
// ids.forEach(function(id) {
|
||||||
|
// var properties = Entities.getEntityProperties(id);
|
||||||
|
// if (properties.name == EDGE_NAME) {
|
||||||
|
// result.push(id);
|
||||||
|
// }
|
||||||
|
// }, this);
|
||||||
|
// return result;
|
||||||
|
//};
|
48
examples/toys/magSticks/utils.js
Normal file
48
examples/toys/magSticks/utils.js
Normal file
|
@ -0,0 +1,48 @@
|
||||||
|
|
||||||
|
DEBUG_MAGSTICKS = true;
|
||||||
|
|
||||||
|
debugPrint = function (str) {
|
||||||
|
if (DEBUG_MAGSTICKS) {
|
||||||
|
print(str);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
vec3toStr = function (v) {
|
||||||
|
return "{ " +
|
||||||
|
(Math.round(v.x*1000)/1000) + ", " +
|
||||||
|
(Math.round(v.y*1000)/1000) + ", " +
|
||||||
|
(Math.round(v.z*1000)/1000) + " }";
|
||||||
|
}
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
logWarn = function(str) {
|
||||||
|
print(str);
|
||||||
|
}
|
||||||
|
|
||||||
|
logError = function(str) {
|
||||||
|
print(str);
|
||||||
|
}
|
||||||
|
|
||||||
|
logInfo = function(str) {
|
||||||
|
print(str);
|
||||||
|
}
|
||||||
|
|
||||||
|
logDebug = function(str) {
|
||||||
|
debugPrint(str);
|
||||||
|
}
|
Loading…
Reference in a new issue