Merge branch 'master' of github.com:highfidelity/hifi into polyvox-again

This commit is contained in:
Seth Alves 2015-09-04 09:12:32 -07:00
commit 72322c0270
35 changed files with 1065 additions and 249 deletions

View file

@ -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

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

@ -3553,6 +3553,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;

View file

@ -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);

View file

@ -132,6 +132,7 @@ namespace MenuOption {
const QString AddRemoveFriends = "Add/Remove Friends...";
const QString AddressBar = "Show Address Bar";
const QString Animations = "Animations...";
const QString Antialiasing = "Antialiasing";
const QString Atmosphere = "Atmosphere";
const QString Attachments = "Attachments...";
const QString AudioNoiseReduction = "Audio Noise Reduction";

View file

@ -68,7 +68,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;

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

@ -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<TextureCache>()->
// 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
}

View file

@ -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 <glm/gtc/random.hpp>
#include <PathUtils.h>
#include <SharedUtil.h>
#include <gpu/Context.h>
#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<FramebufferCache>()->getFrameBufferSize().width(), DependencyManager::get<FramebufferCache>()->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<FramebufferCache>()->getFrameBufferSize().width();
int h = DependencyManager::get<FramebufferCache>()->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<FramebufferCache>();
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<GeometryCache>()->renderQuad(batch, bottomLeft, topRight, texCoordTopLeft, texCoordBottomRight, color);
// Blend step
getBlendPipeline();
batch.setResourceTexture(0, _antialiasingTexture);
batch.setFramebuffer(framebufferCache->getPrimaryFramebuffer());
batch.setPipeline(getBlendPipeline());
DependencyManager::get<GeometryCache>()->renderQuad(batch, bottomLeft, topRight, texCoordTopLeft, texCoordBottomRight, color);
// Ready to render
args->_context->render((batch));
}

View file

@ -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 <DependencyManager.h>
#include "render/DrawTask.h"
class Antialiasing {
public:
Antialiasing();
void run(const render::SceneContextPointer& sceneContext, const render::RenderContextPointer& renderContext);
typedef render::Job::Model<Antialiasing> 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

View file

@ -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) {

View file

@ -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);

View file

@ -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;
}

View file

@ -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;
}

View file

@ -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);
}

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();

View file

@ -53,6 +53,7 @@ public:
bool _drawHitEffect = false;
bool _occlusionStatus = false;
bool _fxaaStatus = false;
RenderContext() {}
};