mirror of
https://github.com/overte-org/overte.git
synced 2025-08-08 02:57:10 +02:00
454 lines
13 KiB
JavaScript
454 lines
13 KiB
JavaScript
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;
|
|
//};
|