Merge pull request #5714 from jherico/lisa

Magballs polish
This commit is contained in:
Brad Hefta-Gaub 2015-09-04 08:48:15 -07:00
commit 127271110c
21 changed files with 681 additions and 206 deletions

View file

@ -17,3 +17,4 @@ Script.load("users.js");
Script.load("grab.js");
Script.load("directory.js");
Script.load("dialTone.js");
Script.load("libraries/omniTool.js");

View file

@ -0,0 +1,25 @@
<!DOCTYPE html>
<html>
<head>
<link rel="stylesheet" type="text/css" href="../style.css">
<style>
.container {
display:table;
position:absolute;
width: 100%;
height: 100%;
}
span {
font-size: 25vw;
display: table-cell;
vertical-align: middle;
line-height: normal;
text-align: center;
}
</style>
</head>
<body>
<div class="container"><span>Add</span></div>
</body>
</html>

View file

@ -0,0 +1,25 @@
<!DOCTYPE html>
<html>
<head>
<link rel="stylesheet" type="text/css" href="../style.css">
<style>
.container {
display:table;
position:absolute;
width: 100%;
height: 100%;
}
span {
font-size: 25vw;
display: table-cell;
vertical-align: middle;
line-height: normal;
text-align: center;
}
</style>
</head>
<body>
<div class="container"><span>Delete</span></div>
</body>
</html>

View file

@ -0,0 +1,14 @@
.container {
display:table;
position:absolute;
width: 100%;
height: 100%;
}
span {
font-size: 25vw;
display: table-cell;
vertical-align: middle;
line-height: normal;
text-align: center;
}

View file

@ -0,0 +1,25 @@
<!DOCTYPE html>
<html>
<head>
<link rel="stylesheet" type="text/css" href="../style.css">
<style>
.container {
display:table;
position:absolute;
width: 100%;
height: 100%;
}
span {
font-size: 25vw;
display: table-cell;
vertical-align: middle;
line-height: normal;
text-align: center;
}
</style>
</head>
<body>
<div class="container"><span>Move</span></div>
</body>
</html>

View file

@ -0,0 +1,55 @@
AvatarRelativeOverlays = function() {
// id -> position & rotation
this.overlays = {};
this.lastAvatarTransform = {
position: ZERO_VECTOR,
rotation: IDENTITY_QUATERNION,
};
}
// FIXME judder in movement is annoying.... add an option to
// automatically hide all overlays when the position or orientation change and then
// restore the ones that were previously visible once the movement stops.
AvatarRelativeOverlays.prototype.onUpdate = function(deltaTime) {
// cache avatar position and orientation and only update on change
if (Vec3.equal(this.lastAvatarTransform.position, MyAvatar.position) &&
Quat.equal(this.lastAvatarTransform.rotation, MyAvatar.orientation)) {
return;
}
this.lastAvatarTransform.position = MyAvatar.position;
this.lastAvatarTransform.rotation = MyAvatar.orientation;
for (var overlayId in this.overlays) {
this.updateOverlayTransform(overlayId);
}
}
AvatarRelativeOverlays.prototype.updateOverlayTransform = function(overlayId) {
Overlays.editOverlay(overlayId, {
position: getEyeRelativePosition(this.overlays[overlayId].position),
rotation: getAvatarRelativeRotation(this.overlays[overlayId].rotation),
})
}
AvatarRelativeOverlays.prototype.addOverlay = function(type, overlayDefinition) {
var overlayId = Overlays.addOverlay(type, overlayDefinition);
if (!overlayId) {
logDebug("Failed to create overlay of type " + type);
return;
}
this.overlays[overlayId] = {
position: overlayDefinition.position || ZERO_VECTOR,
rotation: overlayDefinition.rotation || IDENTITY_QUATERNION,
};
this.updateOverlayTransform(overlayId);
return overlayId;
}
AvatarRelativeOverlays.prototype.deleteAll = function() {
for (var overlayId in this.overlays) {
Overlays.deleteOverlay(overlayId);
}
this.overlays = {};
}

View file

@ -11,6 +11,8 @@ STICK_URL = HIFI_PUBLIC_BUCKET + "models/props/geo_stick.fbx";
ZERO_VECTOR = { x: 0, y: 0, z: 0 };
IDENTITY_QUATERNION = { w: 1, x: 0, y: 0, z: 0 };
COLORS = {
WHITE: {
red: 255,

View file

@ -29,7 +29,7 @@ var SELECTION_OVERLAY = {
Highlighter = function() {
this.highlightCube = Overlays.addOverlay("cube", this.SELECTION_OVERLAY);
this.hightlighted = null;
this.highlighted = null;
var _this = this;
Script.scriptEnding.connect(function() {
_this.onCleanup();
@ -40,9 +40,9 @@ Highlighter.prototype.onCleanup = function() {
Overlays.deleteOverlay(this.highlightCube);
}
Highlighter.prototype.highlight = function(entityId) {
if (entityId != this.hightlighted) {
this.hightlighted = entityId;
Highlighter.prototype.highlight = function(entityIdOrPosition) {
if (entityIdOrPosition != this.highlighted) {
this.highlighted = entityIdOrPosition;
this.updateHighlight();
}
}
@ -53,6 +53,13 @@ Highlighter.prototype.setSize = function(newSize) {
});
}
Highlighter.prototype.setColor = function(color) {
Overlays.editOverlay(this.highlightCube, {
color: color
});
}
Highlighter.prototype.setRotation = function(newRotation) {
Overlays.editOverlay(this.highlightCube, {
rotation: newRotation
@ -60,11 +67,15 @@ Highlighter.prototype.setRotation = function(newRotation) {
}
Highlighter.prototype.updateHighlight = function() {
if (this.hightlighted) {
var properties = Entities.getEntityProperties(this.hightlighted);
if (this.highlighted) {
var position = this.highlighted;
if (typeof this.highlighted === "string") {
var properties = Entities.getEntityProperties(this.highlighted);
position = properties.position;
}
// logDebug("Making highlight " + this.highlightCube + " visible @ " + vec3toStr(properties.position));
Overlays.editOverlay(this.highlightCube, {
position: properties.position,
position: position,
visible: true
});
} else {

View file

@ -11,13 +11,14 @@ Script.include("utils.js");
Script.include("highlighter.js");
Script.include("omniTool/models/modelBase.js");
Script.include("omniTool/models/wand.js");
Script.include("omniTool/models/invisibleWand.js");
OmniToolModules = {};
OmniToolModuleType = null;
OmniTool = function(side) {
this.OMNI_KEY = "OmniTool";
this.MAX_FRAMERATE = 30;
this.MAX_FRAMERATE = 60;
this.UPDATE_INTERVAL = 1.0 / this.MAX_FRAMERATE
this.SIDE = side;
this.PALM = 2 * side;
@ -28,6 +29,7 @@ OmniTool = function(side) {
this.ignoreEntities = {};
this.nearestOmniEntity = {
id: null,
inside: false,
position: null,
distance: Infinity,
@ -38,13 +40,11 @@ OmniTool = function(side) {
this.activeOmniEntityId = null;
this.lastUpdateInterval = 0;
this.tipLength = 0.4;
this.active = false;
this.module = null;
this.moduleEntityId = null;
this.lastScanPosition = ZERO_VECTOR;
this.model = new Wand();
this.model.setLength(this.tipLength);
this.showWand(false);
// Connect to desired events
var _this = this;
@ -65,6 +65,23 @@ OmniTool = function(side) {
});
}
OmniTool.prototype.showWand = function(show) {
if (this.model && this.model.onCleanup) {
this.model.onCleanup();
}
logDebug("Showing wand: " + show);
if (show) {
this.model = new Wand();
this.model.setLength(0.4);
this.model.setVisible(true);
} else {
this.model = new InvisibleWand();
this.model.setLength(0.1);
this.model.setVisible(true);
}
}
OmniTool.prototype.onCleanup = function(action) {
this.unloadModule();
}
@ -110,10 +127,9 @@ OmniTool.prototype.setActive = function(active) {
if (active === this.active) {
return;
}
logDebug("omnitool changing active state: " + active);
logDebug("OmniTool changing active state: " + active);
this.active = active;
this.model.setVisible(this.active);
if (this.module && this.module.onActiveChanged) {
this.module.onActiveChanged(this.side);
}
@ -125,14 +141,25 @@ OmniTool.prototype.onUpdate = function(deltaTime) {
this.position = Controller.getSpatialControlPosition(this.PALM);
// When on the base, hydras report a position of 0
this.setActive(Vec3.length(this.position) > 0.001);
if (!this.active) {
return;
}
if (this.model) {
// Update the wand
var rawRotation = Controller.getSpatialControlRawRotation(this.PALM);
this.rotation = Quat.multiply(MyAvatar.orientation, rawRotation);
this.model.setTransform({
rotation: this.rotation,
position: this.position,
});
if (this.model.onUpdate) {
this.model.onUpdate(deltaTime);
}
}
var rawRotation = Controller.getSpatialControlRawRotation(this.PALM);
this.rotation = Quat.multiply(MyAvatar.orientation, rawRotation);
this.model.setTransform({
rotation: this.rotation,
position: this.position,
});
this.scan();
@ -144,6 +171,19 @@ OmniTool.prototype.onUpdate = function(deltaTime) {
OmniTool.prototype.onClick = function() {
// First check to see if the user is switching to a new omni module
if (this.nearestOmniEntity.inside && this.nearestOmniEntity.omniProperties.script) {
// If this is already the active entity, turn it off
// FIXME add a flag to allow omni modules to cause this entity to be
// ignored in order to support items that will be picked up.
if (this.moduleEntityId && this.moduleEntityId == this.nearestOmniEntity.id) {
this.showWand(false);
this.unloadModule();
this.highlighter.setColor("White");
return;
}
this.showWand(true);
this.highlighter.setColor("Red");
this.activateNewOmniModule();
return;
}
@ -157,12 +197,10 @@ OmniTool.prototype.onClick = function() {
}
OmniTool.prototype.onRelease = function() {
// FIXME how to I switch to a new module?
if (this.module && this.module.onRelease) {
this.module.onRelease();
return;
}
logDebug("Base omnitool does nothing on release");
}
// FIXME resturn a structure of all nearby entities to distances
@ -210,6 +248,11 @@ OmniTool.prototype.getPosition = function() {
OmniTool.prototype.onEnterNearestOmniEntity = function() {
this.nearestOmniEntity.inside = true;
this.highlighter.highlight(this.nearestOmniEntity.id);
if (this.moduleEntityId && this.moduleEntityId == this.nearestOmniEntity.id) {
this.highlighter.setColor("Red");
} else {
this.highlighter.setColor("White");
}
logDebug("On enter omniEntity " + this.nearestOmniEntity.id);
}

View file

@ -0,0 +1,6 @@
InvisibleWand = function() {
}
InvisibleWand.prototype = Object.create( ModelBase.prototype );

View file

@ -17,3 +17,9 @@ ModelBase.prototype.setTransform = function(transform) {
this.tipVector = Vec3.multiplyQbyV(this.rotation, { x: 0, y: this.length, z: 0 });
this.tipPosition = Vec3.sum(this.position, this.tipVector);
}
ModelBase.prototype.setTipColors = function(color1, color2) {
}
ModelBase.prototype.onCleanup = function() {
}

View file

@ -1,22 +1,18 @@
Wand = function() {
// Max updates fps
this.MAX_FRAMERATE = 30
this.UPDATE_INTERVAL = 1.0 / this.MAX_FRAMERATE
this.DEFAULT_TIP_COLORS = [ {
red: 128,
green: 128,
blue: 128,
}, {
red: 64,
green: 64,
blue: 64,
red: 0,
green: 0,
blue: 0,
}];
this.POINTER_ROTATION = Quat.fromPitchYawRollDegrees(45, 0, 45);
// FIXME does this need to be a member of this?
this.lastUpdateInterval = 0;
this.pointers = [
Overlays.addOverlay("cube", {
position: ZERO_VECTOR,
@ -45,17 +41,7 @@ Wand = function() {
var _this = this;
Script.scriptEnding.connect(function() {
Overlays.deleteOverlay(_this.pointers[0]);
Overlays.deleteOverlay(_this.pointers[1]);
Overlays.deleteOverlay(_this.wand);
});
Script.update.connect(function(deltaTime) {
_this.lastUpdateInterval += deltaTime;
if (_this.lastUpdateInterval >= _this.UPDATE_INTERVAL) {
_this.onUpdate(_this.lastUpdateInterval);
_this.lastUpdateInterval = 0;
}
_this.onCleanup();
});
}
@ -76,7 +62,6 @@ Wand.prototype.setVisible = function(visible) {
Wand.prototype.setTransform = function(transform) {
ModelBase.prototype.setTransform.call(this, transform);
var wandPosition = Vec3.sum(this.position, Vec3.multiply(0.5, this.tipVector));
Overlays.editOverlay(this.pointers[0], {
position: this.tipPosition,
@ -106,7 +91,10 @@ Wand.prototype.setTipColors = function(color1, color2) {
}
Wand.prototype.onUpdate = function(deltaTime) {
logDebug("Z4");
if (this.visible) {
logDebug("5");
var time = new Date().getTime() / 250;
var scale1 = Math.abs(Math.sin(time));
var scale2 = Math.abs(Math.cos(time));
@ -118,3 +106,9 @@ Wand.prototype.onUpdate = function(deltaTime) {
});
}
}
Wand.prototype.onCleanup = function() {
Overlays.deleteOverlay(this.pointers[0]);
Overlays.deleteOverlay(this.pointers[1]);
Overlays.deleteOverlay(this.wand);
}

View file

@ -1,9 +1,36 @@
OmniToolModules.Test = function() {
Script.include("avatarRelativeOverlays.js");
OmniToolModules.Test = function(omniTool, activeEntityId) {
this.omniTool = omniTool;
this.activeEntityId = activeEntityId;
this.avatarOverlays = new AvatarRelativeOverlays();
}
OmniToolModules.Test.prototype.onUnload = function() {
if (this.testOverlay) {
Overlays.deleteOverlay(this.testOverlay);
this.testOverlay = 0;
}
}
var CUBE_POSITION = {
x: 0.1,
y: -0.1,
z: -0.4
};
OmniToolModules.Test.prototype.onClick = function() {
logDebug("Test module onClick");
if (this.testOverlay) {
Overlays.deleteOverlay(this.testOverlay);
this.testOverlay = 0;
}
}
OmniToolModules.Test.prototype.onUpdate = function(deltaTime) {
this.avatarOverlays.onUpdate(deltaTime);
}
OmniToolModuleType = "Test"

View file

@ -11,6 +11,15 @@ vec3toStr = function (v, digits) {
return "{ " + v.x.toFixed(digits) + ", " + v.y.toFixed(digits) + ", " + v.z.toFixed(digits)+ " }";
}
colorMix = function(colorA, colorB, mix) {
var result = {};
for (var key in colorA) {
result[key] = (colorA[key] * (1 - mix)) + (colorB[key] * mix);
}
return result;
}
scaleLine = function (start, end, scale) {
var v = Vec3.subtract(end, start);
var length = Vec3.length(v);
@ -127,3 +136,13 @@ findSpherePointHit = function(sphereCenter, sphereRadius, point) {
findSphereSphereHit = function(firstCenter, firstRadius, secondCenter, secondRadius) {
return findSpherePointHit(firstCenter, firstRadius + secondRadius, secondCenter);
}
// Given a vec3 v, return a vec3 that is the same vector relative to the avatars
// DEFAULT eye position, rotated into the avatars reference frame.
getEyeRelativePosition = function(v) {
return Vec3.sum(MyAvatar.getDefaultEyePosition(), Vec3.multiplyQbyV(MyAvatar.orientation, v));
}
getAvatarRelativeRotation = function(q) {
return Quat.multiply(MyAvatar.orientation, q);
}

View file

@ -11,21 +11,10 @@ Script.include("../toys/magBalls/constants.js");
Script.include("../toys/magBalls/graph.js");
Script.include("../toys/magBalls/edgeSpring.js");
Script.include("../toys/magBalls/magBalls.js");
Script.include("avatarRelativeOverlays.js");
OmniToolModuleType = "MagBallsController"
OmniToolModules.MagBallsController = function(omniTool, entityId) {
this.omniTool = omniTool;
this.entityId = entityId;
this.highlighter = new Highlighter();
this.magBalls = new MagBalls();
this.highlighter.setSize(BALL_SIZE);
this.ghostEdges = {};
}
var MAG_BALLS_DATA_NAME = "magBalls";
getMagBallsData = function(id) {
return getEntityCustomData(MAG_BALLS_DATA_NAME, id, {});
}
@ -34,54 +23,177 @@ setMagBallsData = function(id, value) {
setEntityCustomData(MAG_BALLS_DATA_NAME, id, value);
}
//var magBalls = new MagBalls();
// DEBUGGING ONLY - Clear any previous balls
// magBalls.clear();
var UI_BALL_RADIUS = 0.01;
var MODE_INFO = { };
OmniToolModules.MagBallsController.prototype.onClick = function() {
logDebug("MagBallsController onClick: " + vec3toStr(this.tipPosition));
MODE_INFO[BALL_EDIT_MODE_ADD] = {
uiPosition: {
x: 0.15,
y: -0.08,
z: -0.35,
},
colors: [ COLORS.GREEN, COLORS.BLUE ],
// FIXME use an http path or find a way to get the relative path to the file
url: "file:///" + Script.resolvePath('../html/magBalls/addMode.html').replace("c:", "C:"),
};
MODE_INFO[BALL_EDIT_MODE_DELETE] = {
uiPosition: {
x: 0.20,
y: -0.08,
z: -0.32,
},
colors: [ COLORS.RED, COLORS.BLUE ],
// FIXME use an http path or find a way to get the relative path to the file
url: "file:///" + Script.resolvePath('../html/magBalls/deleteMode.html').replace("c:", "C:"),
};
var UI_POSITION_MODE_LABEL = Vec3.multiply(0.5,
Vec3.sum(MODE_INFO[BALL_EDIT_MODE_ADD].uiPosition,
MODE_INFO[BALL_EDIT_MODE_DELETE].uiPosition));
UI_POSITION_MODE_LABEL.y = -0.02;
var UI_BALL_PROTOTYPE = {
size: UI_BALL_RADIUS * 2.0,
alpha: 1.0,
solid: true,
visible: true,
}
OmniToolModules.MagBallsController = function(omniTool, entityId) {
this.omniTool = omniTool;
this.entityId = entityId;
// In hold mode, holding a ball requires that you keep the action
// button pressed, while if this is false, clicking on a ball selects
// it and clicking again will drop it.
this.holdMode = true;
this.highlighter = new Highlighter();
this.magBalls = new MagBalls();
this.highlighter.setSize(BALL_SIZE);
this.ghostEdges = {};
this.selectionRadiusMultipler = 1.5;
this.uiOverlays = new AvatarRelativeOverlays();
this.selected = this.highlighter.hightlighted;
logDebug("This selected: " + this.selected);
if (!this.selected) {
this.selected = this.magBalls.createBall(this.tipPosition);
}
this.magBalls.selectBall(this.selected);
this.highlighter.highlight(null);
logDebug("Selected " + this.selected);
// create the overlay relative to the avatar
this.uiOverlays.addOverlay("sphere", mergeObjects(UI_BALL_PROTOTYPE, {
color: MODE_INFO[BALL_EDIT_MODE_ADD].colors[0],
position: MODE_INFO[BALL_EDIT_MODE_ADD].uiPosition,
}));
this.uiOverlays.addOverlay("sphere", mergeObjects(UI_BALL_PROTOTYPE, {
color: MODE_INFO[BALL_EDIT_MODE_DELETE].colors[0],
position: MODE_INFO[BALL_EDIT_MODE_DELETE].uiPosition,
}));
// FIXME find the proper URLs to use
this.modeLabel = this.uiOverlays.addOverlay("web3d", {
isFacingAvatar: true,
alpha: 1.0,
dimensions: { x: 0.16, y: 0.12, z: 0.001},
color: "White",
position: UI_POSITION_MODE_LABEL,
});
this.setMode(BALL_EDIT_MODE_ADD);
// DEBUGGING ONLY - Fix old, bad edge bounding boxes
//for (var edgeId in this.magBalls.edges) {
// Entities.editEntity(edgeId, {
// dimensions: LINE_DIMENSIONS,
// });
//}
// DEBUGGING ONLY - Clear any previous balls
// this.magBalls.clear();
// DEBUGGING ONLY - Attempt to fix connections between balls
// and delete bad connections. Warning... if you haven't looked around
// and caused the domain server to send you all the nearby balls as well as the connections,
// this can break your structures
// this.magBalls.repair();
}
OmniToolModules.MagBallsController.prototype.onRelease = function() {
logDebug("MagBallsController onRelease: " + vec3toStr(this.tipPosition));
OmniToolModules.MagBallsController.prototype.onUnload = function() {
this.clearGhostEdges();
if (this.selected) {
this.magBalls.releaseBall(this.selected);
this.selected = null;
}
this.uiOverlays.deleteAll();
}
OmniToolModules.MagBallsController.prototype.onUpdate = function(deltaTime) {
this.tipPosition = this.omniTool.getPosition();
if (!this.selected) {
// Find the highlight target and set it.
var target = this.magBalls.findNearestNode(this.tipPosition, BALL_SELECTION_RADIUS);
this.highlighter.highlight(target);
if (!target) {
this.magBalls.onUpdate(deltaTime);
OmniToolModules.MagBallsController.prototype.setMode = function(mode) {
if (mode === this.mode) {
return;
}
logDebug("Changing mode to '" + mode + "'");
Overlays.editOverlay(this.modeLabel, {
url: MODE_INFO[mode].url
});
this.mode = mode;
var color1;
var color2;
switch (this.mode) {
case BALL_EDIT_MODE_ADD:
color1 = COLORS.BLUE;
color2 = COLORS.GREEN;
break;
case BALL_EDIT_MODE_MOVE:
color1 = COLORS.GREEN;
color2 = COLORS.LIGHT_GREEN;
break;
case BALL_EDIT_MODE_DELETE:
color1 = COLORS.RED;
color2 = COLORS.BLUE;
break;
case BALL_EDIT_MODE_DELETE_SHAPE:
color1 = COLORS.RED;
color2 = COLORS.YELLOW;
break;
}
this.omniTool.model.setTipColors(color1, color2);
}
OmniToolModules.MagBallsController.prototype.findUiBallHit = function() {
var result = null;
for (var mode in MODE_INFO) {
var modeInfo = MODE_INFO[mode];
var spherePoint = getEyeRelativePosition(modeInfo.uiPosition);
if (findSpherePointHit(spherePoint, UI_BALL_RADIUS * 2, this.tipPosition)) {
this.highlighter.highlight(spherePoint);
this.highlighter.setColor("White");
// FIXME why doesn't this work?
this.highlighter.setSize(UI_BALL_RADIUS * 4);
return mode;
}
}
return;
}
OmniToolModules.MagBallsController.prototype.onUpdateSelected = function(deltaTime) {
if (!this.selected) {
return;
}
this.highlighter.highlight(null);
Entities.editEntity(this.selected, { position: this.tipPosition });
var targetBalls = this.magBalls.findPotentialEdges(this.selected);
for (var ballId in targetBalls) {
var targetPosition = this.magBalls.getNodePosition(ballId);
var distance = Vec3.distance(targetPosition, this.tipPosition);
var variance = this.magBalls.getVariance(distance);
var mix = Math.abs(variance) / this.magBalls.MAX_VARIANCE;
var color = colorMix(COLORS.YELLOW, COLORS.RED, mix);
if (!this.ghostEdges[ballId]) {
// create the ovleray
this.ghostEdges[ballId] = Overlays.addOverlay("line3d", {
start: this.magBalls.getNodePosition(ballId),
end: this.tipPosition,
color: COLORS.RED,
color: color,
alpha: 1,
lineWidth: 5,
visible: true,
@ -89,6 +201,7 @@ OmniToolModules.MagBallsController.prototype.onUpdate = function(deltaTime) {
} else {
Overlays.editOverlay(this.ghostEdges[ballId], {
end: this.tipPosition,
color: color,
});
}
}
@ -100,6 +213,95 @@ OmniToolModules.MagBallsController.prototype.onUpdate = function(deltaTime) {
}
}
OmniToolModules.MagBallsController.prototype.onUpdate = function(deltaTime) {
this.tipPosition = this.omniTool.getPosition();
this.uiOverlays.onUpdate(deltaTime);
this.onUpdateSelected();
if (this.findUiBallHit()) {
return;
}
if (!this.selected) {
// Find the highlight target and set it.
var target = this.magBalls.findNearestNode(this.tipPosition, BALL_RADIUS * this.selectionRadiusMultipler);
this.highlighter.highlight(target);
this.highlighter.setColor(MODE_INFO[this.mode].colors[0]);
if (!target) {
this.magBalls.onUpdate(deltaTime);
}
return;
}
}
OmniToolModules.MagBallsController.prototype.deselect = function() {
if (!this.selected) {
return false
}
this.clearGhostEdges();
this.magBalls.releaseBall(this.selected);
this.selected = null;
return true;
}
OmniToolModules.MagBallsController.prototype.onClick = function() {
var newMode = this.findUiBallHit();
if (newMode) {
if (this.selected) {
this.magBalls.destroyNode(highlighted);
this.selected = null;
}
this.setMode(newMode);
return;
}
if (this.deselect()) {
return;
}
logDebug("MagBallsController onClick: " + vec3toStr(this.tipPosition));
// TODO add checking against UI shapes for adding or deleting balls.
var highlighted = this.highlighter.highlighted;
if (this.mode == BALL_EDIT_MODE_ADD && !highlighted) {
highlighted = this.magBalls.createBall(this.tipPosition);
}
// Nothing to select or create means we're done here.
if (!highlighted) {
return;
}
switch (this.mode) {
case BALL_EDIT_MODE_ADD:
case BALL_EDIT_MODE_MOVE:
this.magBalls.selectBall(highlighted);
this.selected = highlighted;
logDebug("Selected " + this.selected);
break;
case BALL_EDIT_MODE_DELETE:
this.magBalls.destroyNode(highlighted);
break;
case BALL_EDIT_MODE_DELETE_SHAPE:
logDebug("Not implemented yet");
break;
}
if (this.selected) {
this.highlighter.highlight(null);
}
}
OmniToolModules.MagBallsController.prototype.onRelease = function() {
if (this.holdMode) {
this.deselect();
}
}
OmniToolModules.MagBallsController.prototype.clearGhostEdges = function() {
for(var ballId in this.ghostEdges) {
Overlays.deleteOverlay(this.ghostEdges[ballId]);
@ -108,6 +310,3 @@ OmniToolModules.MagBallsController.prototype.clearGhostEdges = function() {
}
BallController.prototype.onUnload = function() {
this.clearGhostEdges();
}

View file

@ -1,8 +1,10 @@
MAG_BALLS_DATA_NAME = "magBalls";
// 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;
MAG_BALLS_SCALE = 0.5;
BALL_SIZE = 0.08 * MAG_BALLS_SCALE;
STICK_LENGTH = 0.24 * MAG_BALLS_SCALE;
DEBUG_MAGSTICKS = true;
@ -11,8 +13,6 @@ EDGE_NAME = "MagStick";
BALL_RADIUS = BALL_SIZE / 2.0;
BALL_SELECTION_RADIUS = BALL_RADIUS * 1.5;
BALL_DIMENSIONS = {
x: BALL_SIZE,
y: BALL_SIZE,
@ -45,10 +45,13 @@ BALL_PROTOTYPE = {
// 2 millimeters
BALL_EPSILON = (.002) / BALL_DISTANCE;
// FIXME better handling of the line bounding box would require putting the
// origin in the middle of the line, not at one end
LINE_DIAGONAL = Math.sqrt((STICK_LENGTH * STICK_LENGTH * 2) * 3) * 1.1;
LINE_DIMENSIONS = {
x: 5,
y: 5,
z: 5
x: LINE_DIAGONAL,
y: LINE_DIAGONAL,
z: LINE_DIAGONAL
}
LINE_PROTOTYPE = {
@ -75,3 +78,9 @@ EDGE_PROTOTYPE = LINE_PROTOTYPE;
// ignoreCollisions: true,
// collisionsWillMove: false
// }
BALL_EDIT_MODE_ADD = "add";
BALL_EDIT_MODE_MOVE = "move";
BALL_EDIT_MODE_DELETE = "delete";
BALL_EDIT_MODE_DELETE_SHAPE = "deleteShape";

View file

@ -1,95 +0,0 @@
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);
}
}
}

View file

@ -9,7 +9,10 @@ EdgeSpring = function(edgeId, graph) {
this.desiredLength = magBallsData.length || BALL_DISTANCE;
}
EdgeSpring.prototype.adjust = function(results) {
// FIXME as iterations increase, start introducing some randomness
// to the adjustment so that we avoid false equilibriums
// Alternatively, larger iterations could increase the acceptable variance
EdgeSpring.prototype.adjust = function(results, iterations) {
var startPos = this.getAdjustedPosition(this.start, results);
var endPos = this.getAdjustedPosition(this.end, results);
var vector = Vec3.subtract(endPos, startPos);

View file

@ -25,10 +25,6 @@ MagBalls = function() {
this.refresh();
var _this = this;
//Script.update.connect(function(deltaTime) {
// _this.onUpdate(deltaTime);
//});
Script.scriptEnding.connect(function() {
_this.onCleanup();
});
@ -61,8 +57,13 @@ MagBalls.prototype.onUpdate = function(deltaTime) {
if (!this.unstableEdges[edgeId]) {
continue;
}
adjusted |= this.edgeObjects[edgeId].adjust(nodeAdjustResults);
// FIXME need to add some randomness to this so that objects don't hit a
// false equilibrium
// FIXME should this be done node-wise, to more easily account for the number of edge
// connections for a node?
adjusted |= this.edgeObjects[edgeId].adjust(nodeAdjustResults, this.adjustIterations);
}
for (var nodeId in nodeAdjustResults) {
var curPos = this.getNodePosition(nodeId);
var newPos = nodeAdjustResults[nodeId];
@ -71,18 +72,24 @@ MagBalls.prototype.onUpdate = function(deltaTime) {
fixupEdges[edgeId] = true;
}
// logDebug("Moving node Id " + nodeId + " " + (distance * 1000).toFixed(3) + " mm");
Entities.editEntity(nodeId, { position: newPos, color: COLORS.RED });
Entities.editEntity(nodeId, {
position: newPos,
// DEBUGGING, flashes moved balls
// color: COLORS.RED
});
}
// DEBUGGING, flashes moved balls
//Script.setTimeout(function(){
// for (var nodeId in nodeAdjustResults) {
// Entities.editEntity(nodeId, { color: BALL_COLOR });
// }
//}, ((UPDATE_INTERVAL * 1000) / 2));
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) {
if (adjusted) {
@ -357,4 +364,96 @@ MagBalls.prototype.onEntityAdded = function(entityId) {
if (properties.name == BALL_NAME || properties.name == EDGE_NAME) {
this.refreshNeeded = 1;
}
}
}
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;
}
}
}
MagBalls.prototype.repair = function() {
// Find all the balls and record their positions
var nodePositions = {};
for (var nodeId in this.nodes) {
nodePositions[nodeId] = this.getNodePosition(nodeId);
}
// 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 = {};
// WARNING O(n^2) algorithm, every edge that is broken does
// an O(N) search against the nodes
for (var edgeId in this.edges) {
var properties = Entities.getEntityProperties(edgeId);
var startPos = properties.position;
var endPos = Vec3.sum(startPos, properties.linePoints[1]);
var magBallData = getMagBallsData(edgeId);
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");
this.destroyEdge(edgeId);
continue;
}
if (!ballsToEdges[magBallData.start]) {
ballsToEdges[magBallData.start] = [ edgeId ];
} else {
ballsToEdges[magBallData.start].push(edgeId);
}
if (!ballsToEdges[magBallData.end]) {
ballsToEdges[magBallData.end] = [ edgeId ];
} else {
ballsToEdges[magBallData.end].push(edgeId);
}
if (update) {
logDebug("Updating incomplete edge " + edgeId);
magBallData.length = BALL_DISTANCE;
setMagBallsData(edgeId, 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);
}
}
}

View file

@ -147,7 +147,12 @@ QScriptValue Web3DOverlay::getProperty(const QString& property) {
void Web3DOverlay::setURL(const QString& url) {
_url = url;
_isLoaded = false;
if (_webSurface) {
AbstractViewStateInterface::instance()->postLambdaEvent([this, url] {
_webSurface->getRootItem()->setProperty("url", url);
});
}
}
bool Web3DOverlay::findRayIntersection(const glm::vec3& origin, const glm::vec3& direction, float& distance, BoxFace& face) {

View file

@ -23,10 +23,12 @@
out vec3 _normal;
out vec3 _color;
out vec2 _texCoord0;
out vec4 _position;
void main(void) {
_color = inColor.rgb;
_texCoord0 = inTexCoord0.st;
_position = inPosition;
// standard transform
TransformCamera cam = getTransformCamera();