diff --git a/README.md b/README.md
index a4d193324d..a2eb058ae6 100644
--- a/README.md
+++ b/README.md
@@ -1,5 +1,4 @@
-High Fidelity (hifi) is an early-stage technology
-lab experimenting with Virtual Worlds and VR.
+High Fidelity (hifi) is an early-stage technology lab experimenting with Virtual Worlds and VR.
In this repository you'll find the source to many of the components in our
alpha-stage virtual world. The project embraces distributed development
diff --git a/examples/defaultScripts.js b/examples/defaultScripts.js
index c602c36382..44801ef737 100644
--- a/examples/defaultScripts.js
+++ b/examples/defaultScripts.js
@@ -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");
diff --git a/examples/html/magBalls/addMode.html b/examples/html/magBalls/addMode.html
new file mode 100644
index 0000000000..f1db142daa
--- /dev/null
+++ b/examples/html/magBalls/addMode.html
@@ -0,0 +1,25 @@
+
+
+
+
+
+
+
+
Add
+
+
\ No newline at end of file
diff --git a/examples/html/magBalls/deleteMode.html b/examples/html/magBalls/deleteMode.html
new file mode 100644
index 0000000000..94f23fdef8
--- /dev/null
+++ b/examples/html/magBalls/deleteMode.html
@@ -0,0 +1,25 @@
+
+
+
+
+
+
+
+
Delete
+
+
\ No newline at end of file
diff --git a/examples/html/magBalls/magBalls.css b/examples/html/magBalls/magBalls.css
new file mode 100644
index 0000000000..339f094ea7
--- /dev/null
+++ b/examples/html/magBalls/magBalls.css
@@ -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;
+}
\ No newline at end of file
diff --git a/examples/html/magBalls/moveMode.html b/examples/html/magBalls/moveMode.html
new file mode 100644
index 0000000000..eae53be635
--- /dev/null
+++ b/examples/html/magBalls/moveMode.html
@@ -0,0 +1,25 @@
+
+
+
+
+
+
+
+
Move
+
+
\ No newline at end of file
diff --git a/examples/libraries/avatarRelativeOverlays.js b/examples/libraries/avatarRelativeOverlays.js
new file mode 100644
index 0000000000..b12971037c
--- /dev/null
+++ b/examples/libraries/avatarRelativeOverlays.js
@@ -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 = {};
+}
+
diff --git a/examples/libraries/constants.js b/examples/libraries/constants.js
index 3ed7c02633..117e72267c 100644
--- a/examples/libraries/constants.js
+++ b/examples/libraries/constants.js
@@ -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,
diff --git a/examples/libraries/highlighter.js b/examples/libraries/highlighter.js
index b3550b6c8a..33aa445b88 100644
--- a/examples/libraries/highlighter.js
+++ b/examples/libraries/highlighter.js
@@ -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 {
diff --git a/examples/libraries/omniTool.js b/examples/libraries/omniTool.js
index 724f30c548..26c299cdfb 100644
--- a/examples/libraries/omniTool.js
+++ b/examples/libraries/omniTool.js
@@ -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);
}
diff --git a/examples/libraries/omniTool/models/invisibleWand.js b/examples/libraries/omniTool/models/invisibleWand.js
new file mode 100644
index 0000000000..63e0945af3
--- /dev/null
+++ b/examples/libraries/omniTool/models/invisibleWand.js
@@ -0,0 +1,6 @@
+
+InvisibleWand = function() {
+}
+
+InvisibleWand.prototype = Object.create( ModelBase.prototype );
+
diff --git a/examples/libraries/omniTool/models/modelBase.js b/examples/libraries/omniTool/models/modelBase.js
index 7697856d3f..87f93b1204 100644
--- a/examples/libraries/omniTool/models/modelBase.js
+++ b/examples/libraries/omniTool/models/modelBase.js
@@ -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() {
+}
diff --git a/examples/libraries/omniTool/models/wand.js b/examples/libraries/omniTool/models/wand.js
index 8f0fe92b53..af28afb81c 100644
--- a/examples/libraries/omniTool/models/wand.js
+++ b/examples/libraries/omniTool/models/wand.js
@@ -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);
+}
\ No newline at end of file
diff --git a/examples/libraries/omniTool/modules/test.js b/examples/libraries/omniTool/modules/test.js
index 1ca806affa..9f7191b2d0 100644
--- a/examples/libraries/omniTool/modules/test.js
+++ b/examples/libraries/omniTool/modules/test.js
@@ -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"
\ No newline at end of file
diff --git a/examples/libraries/utils.js b/examples/libraries/utils.js
index 1c2816e7a0..c6143a51a8 100644
--- a/examples/libraries/utils.js
+++ b/examples/libraries/utils.js
@@ -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);
+}
diff --git a/examples/toys/magBalls.js b/examples/toys/magBalls.js
index 8e441901a2..7c11a94e06 100644
--- a/examples/toys/magBalls.js
+++ b/examples/toys/magBalls.js
@@ -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();
-}
diff --git a/examples/toys/magBalls/constants.js b/examples/toys/magBalls/constants.js
index b692f0908c..d9dee94329 100644
--- a/examples/toys/magBalls/constants.js
+++ b/examples/toys/magBalls/constants.js
@@ -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";
diff --git a/examples/toys/magBalls/debugUtils.js b/examples/toys/magBalls/debugUtils.js
index 8dadd34679..e69de29bb2 100644
--- a/examples/toys/magBalls/debugUtils.js
+++ b/examples/toys/magBalls/debugUtils.js
@@ -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);
- }
- }
-}
diff --git a/examples/toys/magBalls/edgeSpring.js b/examples/toys/magBalls/edgeSpring.js
index 852c9257c2..e1b717c39b 100644
--- a/examples/toys/magBalls/edgeSpring.js
+++ b/examples/toys/magBalls/edgeSpring.js
@@ -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);
diff --git a/examples/toys/magBalls/magBalls.js b/examples/toys/magBalls/magBalls.js
index 307be9f5e1..b689fb1272 100644
--- a/examples/toys/magBalls/magBalls.js
+++ b/examples/toys/magBalls/magBalls.js
@@ -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;
}
-}
\ No newline at end of file
+}
+
+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);
+ }
+ }
+}
+
diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp
index 8de286f9c4..7321be5ac1 100644
--- a/interface/src/Application.cpp
+++ b/interface/src/Application.cpp
@@ -3558,6 +3558,7 @@ void Application::displaySide(RenderArgs* renderArgs, Camera& theCamera, bool se
renderContext._drawHitEffect = sceneInterface->doEngineDisplayHitEffect();
renderContext._occlusionStatus = Menu::getInstance()->isOptionChecked(MenuOption::DebugAmbientOcclusion);
+ renderContext._fxaaStatus = Menu::getInstance()->isOptionChecked(MenuOption::Antialiasing);
renderArgs->_shouldRender = LODManager::shouldRender;
diff --git a/interface/src/Menu.cpp b/interface/src/Menu.cpp
index 216fa70121..48b6758474 100644
--- a/interface/src/Menu.cpp
+++ b/interface/src/Menu.cpp
@@ -317,6 +317,7 @@ Menu::Menu() {
0, // QML Qt::SHIFT | Qt::Key_A,
true);
addCheckableActionToQMenuAndActionHash(renderOptionsMenu, MenuOption::DebugAmbientOcclusion);
+ addCheckableActionToQMenuAndActionHash(renderOptionsMenu, MenuOption::Antialiasing);
MenuWrapper* ambientLightMenu = renderOptionsMenu->addMenu(MenuOption::RenderAmbientLight);
QActionGroup* ambientLightGroup = new QActionGroup(ambientLightMenu);
diff --git a/interface/src/Menu.h b/interface/src/Menu.h
index 3b87de2c43..b98775e3c6 100644
--- a/interface/src/Menu.h
+++ b/interface/src/Menu.h
@@ -134,6 +134,7 @@ namespace MenuOption {
const QString Animations = "Animations...";
const QString AnimDebugDrawAnimPose = "Debug Draw Animation";
const QString AnimDebugDrawBindPose = "Debug Draw Bind Pose";
+ const QString Antialiasing = "Antialiasing";
const QString Atmosphere = "Atmosphere";
const QString Attachments = "Attachments...";
const QString AudioNoiseReduction = "Audio Noise Reduction";
diff --git a/interface/src/avatar/MyAvatar.cpp b/interface/src/avatar/MyAvatar.cpp
index 2d1dd2dc47..de7e2ba8fb 100644
--- a/interface/src/avatar/MyAvatar.cpp
+++ b/interface/src/avatar/MyAvatar.cpp
@@ -69,7 +69,7 @@ float DEFAULT_SCRIPTED_MOTOR_TIMESCALE = 1.0e6f;
const int SCRIPTED_MOTOR_CAMERA_FRAME = 0;
const int SCRIPTED_MOTOR_AVATAR_FRAME = 1;
const int SCRIPTED_MOTOR_WORLD_FRAME = 2;
-const QString& DEFAULT_AVATAR_COLLISION_SOUND_URL = "https://s3.amazonaws.com/hifi-public/sounds/Collisions-hitsandslaps/airhockey_hit1.wav";
+const QString& DEFAULT_AVATAR_COLLISION_SOUND_URL = "https://hifi-public.s3.amazonaws.com/sounds/Collisions-otherorganic/Body_Hits_Impact.wav";
const float MyAvatar::ZOOM_MIN = 0.5f;
const float MyAvatar::ZOOM_MAX = 25.0f;
diff --git a/interface/src/ui/overlays/Web3DOverlay.cpp b/interface/src/ui/overlays/Web3DOverlay.cpp
index c173c5927f..f14fcf4dc0 100644
--- a/interface/src/ui/overlays/Web3DOverlay.cpp
+++ b/interface/src/ui/overlays/Web3DOverlay.cpp
@@ -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) {
diff --git a/libraries/display-plugins/src/display-plugins/oculus/OculusDisplayPlugin.cpp b/libraries/display-plugins/src/display-plugins/oculus/OculusDisplayPlugin.cpp
index 2ed5e69fe7..c8d9993e5a 100644
--- a/libraries/display-plugins/src/display-plugins/oculus/OculusDisplayPlugin.cpp
+++ b/libraries/display-plugins/src/display-plugins/oculus/OculusDisplayPlugin.cpp
@@ -310,8 +310,6 @@ void OculusDisplayPlugin::activate() {
// not needed since the structure was zeroed on init, but explicit
sceneLayer.ColorTexture[1] = nullptr;
- PerformanceTimer::setActive(true);
-
if (!OVR_SUCCESS(ovr_ConfigureTracking(_hmd,
ovrTrackingCap_Orientation | ovrTrackingCap_Position | ovrTrackingCap_MagYawCorrection, 0))) {
qFatal("Could not attach to sensor device");
@@ -322,15 +320,12 @@ void OculusDisplayPlugin::activate() {
void OculusDisplayPlugin::customizeContext() {
WindowOpenGLDisplayPlugin::customizeContext();
#if (OVR_MAJOR_VERSION >= 6)
- //_texture = DependencyManager::get()->
- // getImageTexture(PathUtils::resourcesPath() + "/images/cube_texture.png");
- uvec2 mirrorSize = toGlm(_window->geometry().size());
-
_sceneFbo = SwapFboPtr(new SwapFramebufferWrapper(_hmd));
_sceneFbo->Init(getRecommendedRenderSize());
#endif
enableVsync(false);
- isVsyncEnabled();
+ // Only enable mirroring if we know vsync is disabled
+ _enableMirror = !isVsyncEnabled();
}
void OculusDisplayPlugin::deactivate() {
@@ -338,7 +333,6 @@ void OculusDisplayPlugin::deactivate() {
makeCurrent();
_sceneFbo.reset();
doneCurrent();
- PerformanceTimer::setActive(false);
WindowOpenGLDisplayPlugin::deactivate();
@@ -355,6 +349,16 @@ void OculusDisplayPlugin::display(GLuint finalTexture, const glm::uvec2& sceneSi
// controlling vsync
wglSwapIntervalEXT(0);
+ // screen mirroring
+ if (_enableMirror) {
+ auto windowSize = toGlm(_window->size());
+ Context::Viewport(windowSize.x, windowSize.y);
+ glBindTexture(GL_TEXTURE_2D, finalTexture);
+ GLenum err = glGetError();
+ Q_ASSERT(0 == err);
+ drawUnitQuad();
+ }
+
_sceneFbo->Bound([&] {
auto size = _sceneFbo->size;
Context::Viewport(size.x, size.y);
@@ -369,25 +373,7 @@ void OculusDisplayPlugin::display(GLuint finalTexture, const glm::uvec2& sceneSi
});
auto windowSize = toGlm(_window->size());
-
- /*
- Two alternatives for mirroring to the screen, the first is to copy our own composited
- scene to the window framebuffer, before distortion. Note this only works if we're doing
- ui compositing ourselves, and not relying on the Oculus SDK compositor (or we don't want
- the UI visible in the output window (unlikely). This should be done before
- _sceneFbo->Increment or we're be using the wrong texture
- */
- if (_enableMirror) {
- _sceneFbo->Bound(Framebuffer::Target::Read, [&] {
- glBlitFramebuffer(
- 0, 0, _sceneFbo->size.x, _sceneFbo->size.y,
- 0, 0, windowSize.x, windowSize.y,
- GL_COLOR_BUFFER_BIT, GL_NEAREST);
- });
- }
-
{
- PerformanceTimer("OculusSubmit");
ovrViewScaleDesc viewScaleDesc;
viewScaleDesc.HmdSpaceToWorldScaleInMeters = 1.0f;
viewScaleDesc.HmdToEyeViewOffset[0] = _eyeOffsets[0];
@@ -401,20 +387,6 @@ void OculusDisplayPlugin::display(GLuint finalTexture, const glm::uvec2& sceneSi
}
_sceneFbo->Increment();
- /*
- The other alternative for mirroring is to use the Oculus mirror texture support, which
- will contain the post-distorted and fully composited scene regardless of how many layers
- we send.
- Currently generates an error.
- */
- //auto mirrorSize = _mirrorFbo->size;
- //_mirrorFbo->Bound(Framebuffer::Target::Read, [&] {
- // Context::BlitFramebuffer(
- // 0, mirrorSize.y, mirrorSize.x, 0,
- // 0, 0, windowSize.x, windowSize.y,
- // BufferSelectBit::ColorBuffer, BlitFilter::Nearest);
- //});
-
++_frameIndex;
#endif
}
diff --git a/libraries/render-utils/src/AntialiasingEffect.cpp b/libraries/render-utils/src/AntialiasingEffect.cpp
new file mode 100644
index 0000000000..0e362bfb49
--- /dev/null
+++ b/libraries/render-utils/src/AntialiasingEffect.cpp
@@ -0,0 +1,165 @@
+//
+// AntialiasingEffect.cpp
+// libraries/render-utils/src/
+//
+// Created by Raffi Bedikian on 8/30/15
+// 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
+//
+
+
+#include
+
+#include
+#include
+#include
+
+#include "gpu/StandardShaderLib.h"
+#include "AntialiasingEffect.h"
+#include "TextureCache.h"
+#include "FramebufferCache.h"
+#include "DependencyManager.h"
+#include "ViewFrustum.h"
+#include "GeometryCache.h"
+
+#include "fxaa_vert.h"
+#include "fxaa_frag.h"
+#include "fxaa_blend_frag.h"
+
+
+Antialiasing::Antialiasing() {
+}
+
+const gpu::PipelinePointer& Antialiasing::getAntialiasingPipeline() {
+ if (!_antialiasingPipeline) {
+ auto vs = gpu::ShaderPointer(gpu::Shader::createVertex(std::string(fxaa_vert)));
+ auto ps = gpu::ShaderPointer(gpu::Shader::createPixel(std::string(fxaa_frag)));
+ gpu::ShaderPointer program = gpu::ShaderPointer(gpu::Shader::createProgram(vs, ps));
+
+ gpu::Shader::BindingSet slotBindings;
+ slotBindings.insert(gpu::Shader::Binding(std::string("colorTexture"), 0));
+
+ gpu::Shader::makeProgram(*program, slotBindings);
+
+ _texcoordOffsetLoc = program->getUniforms().findLocation("texcoordOffset");
+
+ gpu::StatePointer state = gpu::StatePointer(new gpu::State());
+
+ state->setDepthTest(false, false, gpu::LESS_EQUAL);
+
+ // Link the antialiasing FBO to texture
+ _antialiasingBuffer = gpu::FramebufferPointer(gpu::Framebuffer::create(gpu::Element::COLOR_RGBA_32,
+ DependencyManager::get()->getFrameBufferSize().width(), DependencyManager::get()->getFrameBufferSize().height()));
+ auto format = gpu::Element(gpu::VEC4, gpu::NUINT8, gpu::RGBA);
+ auto width = _antialiasingBuffer->getWidth();
+ auto height = _antialiasingBuffer->getHeight();
+ auto defaultSampler = gpu::Sampler(gpu::Sampler::FILTER_MIN_MAG_POINT);
+ _antialiasingTexture = gpu::TexturePointer(gpu::Texture::create2D(format, width, height, defaultSampler));
+
+ // Good to go add the brand new pipeline
+ _antialiasingPipeline.reset(gpu::Pipeline::create(program, state));
+ }
+
+ int w = DependencyManager::get()->getFrameBufferSize().width();
+ int h = DependencyManager::get()->getFrameBufferSize().height();
+ if (w != _antialiasingBuffer->getWidth() || h != _antialiasingBuffer->getHeight()) {
+ _antialiasingBuffer->resize(w, h);
+ }
+
+ return _antialiasingPipeline;
+}
+
+const gpu::PipelinePointer& Antialiasing::getBlendPipeline() {
+ if (!_blendPipeline) {
+ auto vs = gpu::ShaderPointer(gpu::Shader::createVertex(std::string(fxaa_vert)));
+ auto ps = gpu::ShaderPointer(gpu::Shader::createPixel(std::string(fxaa_blend_frag)));
+ gpu::ShaderPointer program = gpu::ShaderPointer(gpu::Shader::createProgram(vs, ps));
+
+ gpu::Shader::BindingSet slotBindings;
+ slotBindings.insert(gpu::Shader::Binding(std::string("colorTexture"), 0));
+
+ gpu::Shader::makeProgram(*program, slotBindings);
+
+ gpu::StatePointer state = gpu::StatePointer(new gpu::State());
+
+ state->setDepthTest(false, false, gpu::LESS_EQUAL);
+
+ // Good to go add the brand new pipeline
+ _blendPipeline.reset(gpu::Pipeline::create(program, state));
+ }
+ return _blendPipeline;
+}
+
+void Antialiasing::run(const render::SceneContextPointer& sceneContext, const render::RenderContextPointer& renderContext) {
+ assert(renderContext->args);
+ assert(renderContext->args->_viewFrustum);
+
+ if (renderContext->args->_renderMode == RenderArgs::MIRROR_RENDER_MODE) {
+ return;
+ }
+
+ gpu::Batch batch;
+
+ batch.enableStereo(false);
+
+ RenderArgs* args = renderContext->args;
+
+ auto framebufferCache = DependencyManager::get();
+ QSize framebufferSize = framebufferCache->getFrameBufferSize();
+ float fbWidth = framebufferSize.width();
+ float fbHeight = framebufferSize.height();
+ float sMin = args->_viewport.x / fbWidth;
+ float sWidth = args->_viewport.z / fbWidth;
+ float tMin = args->_viewport.y / fbHeight;
+ float tHeight = args->_viewport.w / fbHeight;
+
+ glm::mat4 projMat;
+ Transform viewMat;
+ args->_viewFrustum->evalProjectionMatrix(projMat);
+ args->_viewFrustum->evalViewTransform(viewMat);
+ batch.setProjectionTransform(projMat);
+ batch.setViewTransform(viewMat);
+ batch.setModelTransform(Transform());
+
+ // FXAA step
+ getAntialiasingPipeline();
+ batch.setResourceTexture(0, framebufferCache->getPrimaryColorTexture());
+ _antialiasingBuffer->setRenderBuffer(0, _antialiasingTexture);
+ batch.setFramebuffer(_antialiasingBuffer);
+ batch.setPipeline(getAntialiasingPipeline());
+
+ // initialize the view-space unpacking uniforms using frustum data
+ float left, right, bottom, top, nearVal, farVal;
+ glm::vec4 nearClipPlane, farClipPlane;
+
+ args->_viewFrustum->computeOffAxisFrustum(left, right, bottom, top, nearVal, farVal, nearClipPlane, farClipPlane);
+
+ float depthScale = (farVal - nearVal) / farVal;
+ float nearScale = -1.0f / nearVal;
+ float depthTexCoordScaleS = (right - left) * nearScale / sWidth;
+ float depthTexCoordScaleT = (top - bottom) * nearScale / tHeight;
+ float depthTexCoordOffsetS = left * nearScale - sMin * depthTexCoordScaleS;
+ float depthTexCoordOffsetT = bottom * nearScale - tMin * depthTexCoordScaleT;
+
+ batch._glUniform2f(_texcoordOffsetLoc, 1.0 / fbWidth, 1.0 / fbHeight);
+
+ glm::vec4 color(0.0f, 0.0f, 0.0f, 1.0f);
+ glm::vec2 bottomLeft(-1.0f, -1.0f);
+ glm::vec2 topRight(1.0f, 1.0f);
+ glm::vec2 texCoordTopLeft(0.0f, 0.0f);
+ glm::vec2 texCoordBottomRight(1.0f, 1.0f);
+ DependencyManager::get()->renderQuad(batch, bottomLeft, topRight, texCoordTopLeft, texCoordBottomRight, color);
+
+ // Blend step
+ getBlendPipeline();
+ batch.setResourceTexture(0, _antialiasingTexture);
+ batch.setFramebuffer(framebufferCache->getPrimaryFramebuffer());
+ batch.setPipeline(getBlendPipeline());
+
+ DependencyManager::get()->renderQuad(batch, bottomLeft, topRight, texCoordTopLeft, texCoordBottomRight, color);
+
+ // Ready to render
+ args->_context->render((batch));
+}
diff --git a/libraries/render-utils/src/AntialiasingEffect.h b/libraries/render-utils/src/AntialiasingEffect.h
new file mode 100644
index 0000000000..c7cce4cb15
--- /dev/null
+++ b/libraries/render-utils/src/AntialiasingEffect.h
@@ -0,0 +1,44 @@
+//
+// AntialiasingEffect.h
+// libraries/render-utils/src/
+//
+// Created by Raffi Bedikian on 8/30/15
+// 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
+//
+
+#ifndef hifi_AntialiasingEffect_h
+#define hifi_AntialiasingEffect_h
+
+#include
+
+#include "render/DrawTask.h"
+
+class Antialiasing {
+public:
+
+ Antialiasing();
+
+ void run(const render::SceneContextPointer& sceneContext, const render::RenderContextPointer& renderContext);
+ typedef render::Job::Model JobModel;
+
+ const gpu::PipelinePointer& getAntialiasingPipeline();
+ const gpu::PipelinePointer& getBlendPipeline();
+
+private:
+
+ // Uniforms for AA
+ gpu::int32 _texcoordOffsetLoc;
+
+ gpu::FramebufferPointer _antialiasingBuffer;
+
+ gpu::TexturePointer _antialiasingTexture;
+
+ gpu::PipelinePointer _antialiasingPipeline;
+ gpu::PipelinePointer _blendPipeline;
+
+};
+
+#endif // hifi_AntialiasingEffect_h
diff --git a/libraries/render-utils/src/RenderDeferredTask.cpp b/libraries/render-utils/src/RenderDeferredTask.cpp
index ca3f87f53f..137294c5b6 100755
--- a/libraries/render-utils/src/RenderDeferredTask.cpp
+++ b/libraries/render-utils/src/RenderDeferredTask.cpp
@@ -23,6 +23,7 @@
#include "render/DrawStatus.h"
#include "AmbientOcclusionEffect.h"
+#include "AntialiasingEffect.h"
#include "overlay3D_vert.h"
#include "overlay3D_frag.h"
@@ -88,6 +89,11 @@ RenderDeferredTask::RenderDeferredTask() : Task() {
_jobs.back().setEnabled(false);
_occlusionJobIndex = _jobs.size() - 1;
+ _jobs.push_back(Job(new Antialiasing::JobModel("Antialiasing")));
+
+ _jobs.back().setEnabled(false);
+ _antialiasingJobIndex = _jobs.size() - 1;
+
_jobs.push_back(Job(new FetchItems::JobModel("FetchTransparent",
FetchItems(
ItemFilter::Builder::transparentShape().withoutLayered(),
@@ -146,6 +152,8 @@ void RenderDeferredTask::run(const SceneContextPointer& sceneContext, const Rend
// TODO: turn on/off AO through menu item
setOcclusionStatus(renderContext->_occlusionStatus);
+ setAntialiasingStatus(renderContext->_fxaaStatus);
+
renderContext->args->_context->syncCache();
for (auto job : _jobs) {
diff --git a/libraries/render-utils/src/RenderDeferredTask.h b/libraries/render-utils/src/RenderDeferredTask.h
index 0041f5d9aa..8366a2665d 100755
--- a/libraries/render-utils/src/RenderDeferredTask.h
+++ b/libraries/render-utils/src/RenderDeferredTask.h
@@ -91,6 +91,11 @@ public:
void setOcclusionStatus(bool draw) { if (_occlusionJobIndex >= 0) { _jobs[_occlusionJobIndex].setEnabled(draw); } }
bool doOcclusionStatus() const { if (_occlusionJobIndex >= 0) { return _jobs[_occlusionJobIndex].isEnabled(); } else { return false; } }
+ int _antialiasingJobIndex = -1;
+
+ void setAntialiasingStatus(bool draw) { if (_antialiasingJobIndex >= 0) { _jobs[_antialiasingJobIndex].setEnabled(draw); } }
+ bool doAntialiasingStatus() const { if (_antialiasingJobIndex >= 0) { return _jobs[_antialiasingJobIndex].isEnabled(); } else { return false; } }
+
virtual void run(const render::SceneContextPointer& sceneContext, const render::RenderContextPointer& renderContext);
diff --git a/libraries/render-utils/src/fxaa.slf b/libraries/render-utils/src/fxaa.slf
new file mode 100644
index 0000000000..d1c50b2c58
--- /dev/null
+++ b/libraries/render-utils/src/fxaa.slf
@@ -0,0 +1,94 @@
+<@include gpu/Config.slh@>
+<$VERSION_HEADER$>
+// Generated on <$_SCRIBE_DATE$>
+//
+// fxaa.frag
+// fragment shader
+//
+// Created by Raffi Bedikian on 8/30/15
+// 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
+//
+
+// FXAA shader, GLSL code adapted from:
+// http://horde3d.org/wiki/index.php5?title=Shading_Technique_-_FXAA
+// Whitepaper describing the technique:
+// http://developer.download.nvidia.com/assets/gamedev/files/sdk/11/FXAA_WhitePaper.pdf
+
+#ifdef GL_ES
+precision mediump float;
+precision mediump int;
+#endif
+
+uniform sampler2D colorTexture;
+uniform vec2 texcoordOffset;
+
+in vec2 varTexcoord;
+out vec4 outFragColor;
+
+void main() {
+ // filter width limit for dependent "two-tap" texture samples
+ float FXAA_SPAN_MAX = 8.0;
+
+ // local contrast multiplier for performing AA
+ // higher = sharper, but setting this value too high will cause near-vertical and near-horizontal edges to fail
+ // see "fxaaQualityEdgeThreshold"
+ float FXAA_REDUCE_MUL = 1.0 / 8.0;
+
+ // luminance threshold for processing dark colors
+ // see "fxaaQualityEdgeThresholdMin"
+ float FXAA_REDUCE_MIN = 1.0 / 128.0;
+
+ // fetch raw RGB values for nearby locations
+ // sampling pattern is "five on a die" (each diagonal direction and the center)
+ // computing the coordinates for these texture reads could be moved to the vertex shader for speed if needed
+ vec3 rgbNW = texture2D(colorTexture, varTexcoord + (vec2(-1.0, -1.0) * texcoordOffset)).xyz;
+ vec3 rgbNE = texture2D(colorTexture, varTexcoord + (vec2(+1.0, -1.0) * texcoordOffset)).xyz;
+ vec3 rgbSW = texture2D(colorTexture, varTexcoord + (vec2(-1.0, +1.0) * texcoordOffset)).xyz;
+ vec3 rgbSE = texture2D(colorTexture, varTexcoord + (vec2(+1.0, +1.0) * texcoordOffset)).xyz;
+ vec3 rgbM = texture2D(colorTexture, varTexcoord).xyz;
+
+ // convert RGB values to luminance
+ vec3 luma = vec3(0.299, 0.587, 0.114);
+ float lumaNW = dot(rgbNW, luma);
+ float lumaNE = dot(rgbNE, luma);
+ float lumaSW = dot(rgbSW, luma);
+ float lumaSE = dot(rgbSE, luma);
+ float lumaM = dot( rgbM, luma);
+
+ // luma range of local neighborhood
+ float lumaMin = min(lumaM, min(min(lumaNW, lumaNE), min(lumaSW, lumaSE)));
+ float lumaMax = max(lumaM, max(max(lumaNW, lumaNE), max(lumaSW, lumaSE)));
+
+ // direction perpendicular to local luma gradient
+ vec2 dir;
+ dir.x = -((lumaNW + lumaNE) - (lumaSW + lumaSE));
+ dir.y = ((lumaNW + lumaSW) - (lumaNE + lumaSE));
+
+ // compute clamped direction offset for additional "two-tap" samples
+ // longer vector = blurry, shorter vector = sharp
+ float dirReduce = max((lumaNW + lumaNE + lumaSW + lumaSE) * (0.25 * FXAA_REDUCE_MUL), FXAA_REDUCE_MIN);
+ float rcpDirMin = 1.0 / (min(abs(dir.x), abs(dir.y)) + dirReduce);
+ dir = min(vec2(FXAA_SPAN_MAX, FXAA_SPAN_MAX),
+ max(vec2(-FXAA_SPAN_MAX, -FXAA_SPAN_MAX), dir * rcpDirMin)) * texcoordOffset;
+
+ // perform additional texture sampling perpendicular to gradient
+ vec3 rgbA = (1.0 / 2.0) * (
+ texture2D(colorTexture, varTexcoord + dir * (1.0 / 3.0 - 0.5)).xyz +
+ texture2D(colorTexture, varTexcoord + dir * (2.0 / 3.0 - 0.5)).xyz);
+ vec3 rgbB = rgbA * (1.0 / 2.0) + (1.0 / 4.0) * (
+ texture2D(colorTexture, varTexcoord + dir * (0.0 / 3.0 - 0.5)).xyz +
+ texture2D(colorTexture, varTexcoord + dir * (3.0 / 3.0 - 0.5)).xyz);
+ float lumaB = dot(rgbB, luma);
+
+ // compare luma of new samples to the luma range of the original neighborhood
+ // if the new samples exceed this range, just use the first two samples instead of all four
+ if (lumaB < lumaMin || lumaB > lumaMax) {
+ outFragColor.xyz=rgbA;
+ } else {
+ outFragColor.xyz=rgbB;
+ }
+ outFragColor.a = 1.0;
+}
diff --git a/libraries/render-utils/src/fxaa.slv b/libraries/render-utils/src/fxaa.slv
new file mode 100644
index 0000000000..35a96ceb24
--- /dev/null
+++ b/libraries/render-utils/src/fxaa.slv
@@ -0,0 +1,26 @@
+<@include gpu/Config.slh@>
+<$VERSION_HEADER$>
+// Generated on <$_SCRIBE_DATE$>
+//
+// fxaa.vert
+// vertex shader
+//
+// Created by Raffi Bedikian on 8/30/15
+// 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
+//
+
+<@include gpu/Inputs.slh@>
+
+<@include gpu/Transform.slh@>
+
+<$declareStandardTransform()$>
+
+out vec2 varTexcoord;
+
+void main(void) {
+ varTexcoord = inTexCoord0.xy;
+ gl_Position = inPosition;
+}
diff --git a/libraries/render-utils/src/fxaa_blend.slf b/libraries/render-utils/src/fxaa_blend.slf
new file mode 100644
index 0000000000..d5819cc9a6
--- /dev/null
+++ b/libraries/render-utils/src/fxaa_blend.slf
@@ -0,0 +1,24 @@
+<@include gpu/Config.slh@>
+<$VERSION_HEADER$>
+// Generated on <$_SCRIBE_DATE$>
+//
+// fxaa_blend.frag
+// fragment shader
+//
+// Created by Raffi Bedikian on 8/30/15
+// 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
+//
+
+<@include DeferredBufferWrite.slh@>
+
+in vec2 varTexcoord;
+out vec4 outFragColor;
+
+uniform sampler2D colorTexture;
+
+void main(void) {
+ outFragColor = texture(colorTexture, varTexcoord);
+}
diff --git a/libraries/render-utils/src/simple.slv b/libraries/render-utils/src/simple.slv
index 99f404eaec..b22bb36d83 100644
--- a/libraries/render-utils/src/simple.slv
+++ b/libraries/render-utils/src/simple.slv
@@ -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();
diff --git a/libraries/render/src/render/Engine.h b/libraries/render/src/render/Engine.h
index 8d096c5e15..5da7956b22 100644
--- a/libraries/render/src/render/Engine.h
+++ b/libraries/render/src/render/Engine.h
@@ -53,6 +53,7 @@ public:
bool _drawHitEffect = false;
bool _occlusionStatus = false;
+ bool _fxaaStatus = false;
RenderContext() {}
};
diff --git a/libraries/script-engine/src/BatchLoader.cpp b/libraries/script-engine/src/BatchLoader.cpp
index db0743808f..ac0cafa6a9 100644
--- a/libraries/script-engine/src/BatchLoader.cpp
+++ b/libraries/script-engine/src/BatchLoader.cpp
@@ -58,11 +58,13 @@ void BatchLoader::start() {
connect(this, &QObject::destroyed, reply, &QObject::deleteLater);
} else {
-#ifdef _WIN32
- QString fileName = url.toString();
-#else
QString fileName = url.toLocalFile();
-#endif
+
+ // sometimes on windows, we see the toLocalFile() return null,
+ // in this case we will attempt to simply use the url as a string
+ if (fileName.isEmpty()) {
+ fileName = url.toString();
+ }
qCDebug(scriptengine) << "Reading file at " << fileName;
diff --git a/libraries/shared/src/RegisteredMetaTypes.cpp b/libraries/shared/src/RegisteredMetaTypes.cpp
index 2c4b213fcb..b2389f4db6 100644
--- a/libraries/shared/src/RegisteredMetaTypes.cpp
+++ b/libraries/shared/src/RegisteredMetaTypes.cpp
@@ -26,8 +26,7 @@ static int quatMetaTypeId = qRegisterMetaType();
static int xColorMetaTypeId = qRegisterMetaType();
static int pickRayMetaTypeId = qRegisterMetaType();
static int collisionMetaTypeId = qRegisterMetaType();
-
-
+static int qMapURLStringMetaTypeId = qRegisterMetaType>();
void registerMetaTypes(QScriptEngine* engine) {
qScriptRegisterMetaType(engine, vec4toScriptValue, vec4FromScriptValue);