mirror of
https://github.com/lubosz/overte.git
synced 2025-08-27 11:46:18 +02:00
merge upstream/master into andrew/isentropic
This commit is contained in:
commit
180d147806
43 changed files with 1226 additions and 410 deletions
|
@ -158,6 +158,7 @@ option(GET_GLM "Get GLM library automatically as external project" 1)
|
|||
option(GET_GVERB "Get Gverb library automatically as external project" 1)
|
||||
option(GET_SOXR "Get Soxr library automatically as external project" 1)
|
||||
option(GET_TBB "Get Threading Building Blocks library automatically as external project" 1)
|
||||
option(USE_NSIGHT "Attempt to find the nSight libraries" 1)
|
||||
|
||||
if (WIN32)
|
||||
option(GET_GLEW "Get GLEW library automatically as external project" 1)
|
||||
|
|
|
@ -9,7 +9,7 @@
|
|||
//
|
||||
|
||||
Script.load("progress.js");
|
||||
Script.load("editEntities.js");
|
||||
Script.load("edit.js");
|
||||
Script.load("selectAudioDevice.js");
|
||||
Script.load("controllers/hydra/hydraMove.js");
|
||||
Script.load("headMove.js");
|
||||
|
|
|
@ -29,12 +29,15 @@ Script.include([
|
|||
"libraries/entityCameraTool.js",
|
||||
"libraries/gridTool.js",
|
||||
"libraries/entityList.js",
|
||||
"libraries/lightOverlayManager.js",
|
||||
]);
|
||||
|
||||
var selectionDisplay = SelectionDisplay;
|
||||
var selectionManager = SelectionManager;
|
||||
var entityPropertyDialogBox = EntityPropertyDialogBox;
|
||||
|
||||
var lightOverlayManager = new LightOverlayManager();
|
||||
|
||||
var cameraManager = new CameraManager();
|
||||
|
||||
var grid = Grid();
|
||||
|
@ -45,6 +48,7 @@ var entityListTool = EntityListTool();
|
|||
|
||||
selectionManager.addEventListener(function() {
|
||||
selectionDisplay.updateHandles();
|
||||
lightOverlayManager.updatePositions();
|
||||
});
|
||||
|
||||
var windowDimensions = Controller.getViewportDimensions();
|
||||
|
@ -70,13 +74,17 @@ var DEFAULT_DIMENSIONS = {
|
|||
z: DEFAULT_DIMENSION
|
||||
};
|
||||
|
||||
var DEFAULT_LIGHT_DIMENSIONS = Vec3.multiply(20, DEFAULT_DIMENSIONS);
|
||||
|
||||
var MENU_INSPECT_TOOL_ENABLED = "Inspect Tool";
|
||||
var MENU_AUTO_FOCUS_ON_SELECT = "Auto Focus on Select";
|
||||
var MENU_EASE_ON_FOCUS = "Ease Orientation on Focus";
|
||||
var MENU_SHOW_LIGHTS_IN_EDIT_MODE = "Show Lights in Edit Mode";
|
||||
|
||||
var SETTING_INSPECT_TOOL_ENABLED = "inspectToolEnabled";
|
||||
var SETTING_AUTO_FOCUS_ON_SELECT = "autoFocusOnSelect";
|
||||
var SETTING_EASE_ON_FOCUS = "cameraEaseOnFocus";
|
||||
var SETTING_SHOW_LIGHTS_IN_EDIT_MODE = "showLightsInEditMode";
|
||||
|
||||
var INSUFFICIENT_PERMISSIONS_ERROR_MSG = "You do not have the necessary permissions to edit on this domain."
|
||||
|
||||
|
@ -95,12 +103,24 @@ var isActive = false;
|
|||
|
||||
var placingEntityID = null;
|
||||
|
||||
IMPORTING_SVO_OVERLAY_WIDTH = 130;
|
||||
IMPORTING_SVO_OVERLAY_WIDTH = 144;
|
||||
IMPORTING_SVO_OVERLAY_HEIGHT = 30;
|
||||
IMPORTING_SVO_OVERLAY_MARGIN = 6;
|
||||
var importingSVOOverlay = Overlays.addOverlay("text", {
|
||||
IMPORTING_SVO_OVERLAY_MARGIN = 5;
|
||||
IMPORTING_SVO_OVERLAY_LEFT_MARGIN = 34;
|
||||
var importingSVOImageOverlay = Overlays.addOverlay("image", {
|
||||
imageURL: HIFI_PUBLIC_BUCKET + "images/hourglass.svg",
|
||||
width: 20,
|
||||
height: 20,
|
||||
alpha: 1.0,
|
||||
color: { red: 255, green: 255, blue: 255 },
|
||||
x: Window.innerWidth - IMPORTING_SVO_OVERLAY_WIDTH,
|
||||
y: Window.innerHeight - IMPORTING_SVO_OVERLAY_HEIGHT,
|
||||
visible: false,
|
||||
});
|
||||
var importingSVOTextOverlay = Overlays.addOverlay("text", {
|
||||
font: { size: 14 },
|
||||
text: "Importing SVO...",
|
||||
leftMargin: IMPORTING_SVO_OVERLAY_LEFT_MARGIN,
|
||||
x: Window.innerWidth - IMPORTING_SVO_OVERLAY_WIDTH - IMPORTING_SVO_OVERLAY_MARGIN,
|
||||
y: Window.innerHeight - IMPORTING_SVO_OVERLAY_HEIGHT - IMPORTING_SVO_OVERLAY_MARGIN,
|
||||
width: IMPORTING_SVO_OVERLAY_WIDTH,
|
||||
|
@ -110,6 +130,10 @@ var importingSVOOverlay = Overlays.addOverlay("text", {
|
|||
visible: false,
|
||||
});
|
||||
|
||||
var MARKETPLACE_URL = "https://metaverse.highfidelity.io/marketplace";
|
||||
var marketplaceWindow = new WebWindow('Marketplace', MARKETPLACE_URL, 900, 700, false);
|
||||
marketplaceWindow.setVisible(false);
|
||||
|
||||
var toolBar = (function () {
|
||||
var that = {},
|
||||
toolBar,
|
||||
|
@ -126,7 +150,7 @@ var toolBar = (function () {
|
|||
|
||||
// Hide active button for now - this may come back, so not deleting yet.
|
||||
activeButton = toolBar.addTool({
|
||||
imageURL: toolIconUrl + "models-tool.svg",
|
||||
imageURL: toolIconUrl + "edit-status.svg",
|
||||
subImage: { x: 0, y: Tool.IMAGE_WIDTH, width: Tool.IMAGE_WIDTH, height: Tool.IMAGE_HEIGHT },
|
||||
width: toolWidth,
|
||||
height: toolHeight,
|
||||
|
@ -135,7 +159,7 @@ var toolBar = (function () {
|
|||
}, true, false);
|
||||
|
||||
newModelButton = toolBar.addTool({
|
||||
imageURL: toolIconUrl + "add-model-tool.svg",
|
||||
imageURL: toolIconUrl + "upload.svg",
|
||||
subImage: { x: 0, y: Tool.IMAGE_WIDTH, width: Tool.IMAGE_WIDTH, height: Tool.IMAGE_HEIGHT },
|
||||
width: toolWidth,
|
||||
height: toolHeight,
|
||||
|
@ -144,7 +168,7 @@ var toolBar = (function () {
|
|||
});
|
||||
|
||||
browseModelsButton = toolBar.addTool({
|
||||
imageURL: toolIconUrl + "list-icon.svg",
|
||||
imageURL: toolIconUrl + "marketplace.svg",
|
||||
width: toolWidth,
|
||||
height: toolHeight,
|
||||
alpha: 0.9,
|
||||
|
@ -186,6 +210,8 @@ var toolBar = (function () {
|
|||
alpha: 0.9,
|
||||
visible: false
|
||||
});
|
||||
|
||||
that.setActive(false);
|
||||
}
|
||||
|
||||
that.setActive = function(active) {
|
||||
|
@ -214,6 +240,7 @@ var toolBar = (function () {
|
|||
}
|
||||
}
|
||||
toolBar.selectTool(activeButton, isActive);
|
||||
lightOverlayManager.setVisible(isActive && Menu.isOptionChecked(MENU_SHOW_LIGHTS_IN_EDIT_MODE));
|
||||
};
|
||||
|
||||
// Sets visibility of tool buttons, excluding the power button
|
||||
|
@ -311,7 +338,7 @@ var toolBar = (function () {
|
|||
return true;
|
||||
}
|
||||
if (browseModelsButton === toolBar.clicked(clickedOverlay)) {
|
||||
browseModelsButtonDown = true;
|
||||
marketplaceWindow.setVisible(true);
|
||||
return true;
|
||||
}
|
||||
|
||||
|
@ -354,8 +381,8 @@ var toolBar = (function () {
|
|||
if (position.x > 0 && position.y > 0 && position.z > 0) {
|
||||
placingEntityID = Entities.addEntity({
|
||||
type: "Light",
|
||||
position: grid.snapToSurface(grid.snapToGrid(position, false, DEFAULT_DIMENSIONS), DEFAULT_DIMENSIONS),
|
||||
dimensions: DEFAULT_DIMENSIONS,
|
||||
position: grid.snapToSurface(grid.snapToGrid(position, false, DEFAULT_LIGHT_DIMENSIONS), DEFAULT_LIGHT_DIMENSIONS),
|
||||
dimensions: DEFAULT_LIGHT_DIMENSIONS,
|
||||
isSpotlight: false,
|
||||
diffuseColor: { red: 255, green: 255, blue: 255 },
|
||||
ambientColor: { red: 255, green: 255, blue: 255 },
|
||||
|
@ -469,12 +496,31 @@ function rayPlaneIntersection(pickRay, point, normal) {
|
|||
function findClickedEntity(event) {
|
||||
var pickRay = Camera.computePickRay(event.x, event.y);
|
||||
|
||||
var foundIntersection = Entities.findRayIntersection(pickRay, true); // want precision picking
|
||||
var entityResult = Entities.findRayIntersection(pickRay, true); // want precision picking
|
||||
var lightResult = lightOverlayManager.findRayIntersection(pickRay);
|
||||
lightResult.accurate = true;
|
||||
|
||||
if (!foundIntersection.accurate) {
|
||||
var result;
|
||||
|
||||
if (!entityResult.intersects && !lightResult.intersects) {
|
||||
return null;
|
||||
} else if (entityResult.intersects && !lightResult.intersects) {
|
||||
result = entityResult;
|
||||
} else if (!entityResult.intersects && lightResult.intersects) {
|
||||
result = lightResult;
|
||||
} else {
|
||||
if (entityResult.distance < lightResult.distance) {
|
||||
result = entityResult;
|
||||
} else {
|
||||
result = lightResult;
|
||||
}
|
||||
}
|
||||
|
||||
if (!result.accurate) {
|
||||
return null;
|
||||
}
|
||||
var foundEntity = foundIntersection.entityID;
|
||||
|
||||
var foundEntity = result.entityID;
|
||||
|
||||
if (!foundEntity.isKnownID) {
|
||||
var identify = Entities.identifyEntity(foundEntity);
|
||||
|
@ -720,6 +766,10 @@ function setupModelMenus() {
|
|||
afterItem: "Allow Selecting of Large Models", isCheckable: true, isChecked: true });
|
||||
Menu.addMenuItem({ menuName: "Edit", menuItemName: "Allow Selecting of Lights", shortcutKey: "CTRL+SHIFT+META+L",
|
||||
afterItem: "Allow Selecting of Small Models", isCheckable: true });
|
||||
Menu.addMenuItem({ menuName: "Edit", menuItemName: "Select All Entities In Box", shortcutKey: "CTRL+SHIFT+META+A",
|
||||
afterItem: "Allow Selecting of Lights" });
|
||||
Menu.addMenuItem({ menuName: "Edit", menuItemName: "Select All Entities Touching Box", shortcutKey: "CTRL+SHIFT+META+T",
|
||||
afterItem: "Select All Entities In Box" });
|
||||
|
||||
Menu.addMenuItem({ menuName: "File", menuItemName: "Models", isSeparator: true, beforeItem: "Settings" });
|
||||
Menu.addMenuItem({ menuName: "File", menuItemName: "Export Entities", shortcutKey: "CTRL+META+E", afterItem: "Models" });
|
||||
|
@ -730,6 +780,8 @@ function setupModelMenus() {
|
|||
isCheckable: true, isChecked: Settings.getValue(SETTING_AUTO_FOCUS_ON_SELECT) == "true" });
|
||||
Menu.addMenuItem({ menuName: "View", menuItemName: MENU_EASE_ON_FOCUS, afterItem: MENU_AUTO_FOCUS_ON_SELECT,
|
||||
isCheckable: true, isChecked: Settings.getValue(SETTING_EASE_ON_FOCUS) == "true" });
|
||||
Menu.addMenuItem({ menuName: "View", menuItemName: MENU_SHOW_LIGHTS_IN_EDIT_MODE, afterItem: MENU_EASE_ON_FOCUS,
|
||||
isCheckable: true, isChecked: Settings.getValue(SETTING_SHOW_LIGHTS_IN_EDIT_MODE) == "true" });
|
||||
|
||||
Entities.setLightsArePickable(false);
|
||||
}
|
||||
|
@ -747,6 +799,8 @@ function cleanupModelMenus() {
|
|||
Menu.removeMenuItem("Edit", "Allow Selecting of Large Models");
|
||||
Menu.removeMenuItem("Edit", "Allow Selecting of Small Models");
|
||||
Menu.removeMenuItem("Edit", "Allow Selecting of Lights");
|
||||
Menu.removeMenuItem("Edit", "Select All Entities In Box");
|
||||
Menu.removeMenuItem("Edit", "Select All Entities Touching Box");
|
||||
|
||||
Menu.removeSeparator("File", "Models");
|
||||
Menu.removeMenuItem("File", "Export Entities");
|
||||
|
@ -756,11 +810,13 @@ function cleanupModelMenus() {
|
|||
Menu.removeMenuItem("View", MENU_INSPECT_TOOL_ENABLED);
|
||||
Menu.removeMenuItem("View", MENU_AUTO_FOCUS_ON_SELECT);
|
||||
Menu.removeMenuItem("View", MENU_EASE_ON_FOCUS);
|
||||
Menu.removeMenuItem("View", MENU_SHOW_LIGHTS_IN_EDIT_MODE);
|
||||
}
|
||||
|
||||
Script.scriptEnding.connect(function() {
|
||||
Settings.setValue(SETTING_AUTO_FOCUS_ON_SELECT, Menu.isOptionChecked(MENU_AUTO_FOCUS_ON_SELECT));
|
||||
Settings.setValue(SETTING_EASE_ON_FOCUS, Menu.isOptionChecked(MENU_EASE_ON_FOCUS));
|
||||
Settings.setValue(SETTING_SHOW_LIGHTS_IN_EDIT_MODE, Menu.isOptionChecked(MENU_SHOW_LIGHTS_IN_EDIT_MODE));
|
||||
|
||||
progressDialog.cleanup();
|
||||
toolBar.cleanup();
|
||||
|
@ -769,7 +825,8 @@ Script.scriptEnding.connect(function() {
|
|||
selectionDisplay.cleanup();
|
||||
Entities.setLightsArePickable(originalLightsArePickable);
|
||||
|
||||
Overlays.deleteOverlay(importingSVOOverlay);
|
||||
Overlays.deleteOverlay(importingSVOImageOverlay);
|
||||
Overlays.deleteOverlay(importingSVOTextOverlay);
|
||||
});
|
||||
|
||||
// Do some stuff regularly, like check for placement of various overlays
|
||||
|
@ -779,6 +836,45 @@ Script.update.connect(function (deltaTime) {
|
|||
selectionDisplay.checkMove();
|
||||
});
|
||||
|
||||
function insideBox(center, dimensions, point) {
|
||||
return (Math.abs(point.x - center.x) <= (dimensions.x / 2.0))
|
||||
&& (Math.abs(point.y - center.y) <= (dimensions.y / 2.0))
|
||||
&& (Math.abs(point.z - center.z) <= (dimensions.z / 2.0));
|
||||
}
|
||||
|
||||
function selectAllEtitiesInCurrentSelectionBox(keepIfTouching) {
|
||||
if (selectionManager.hasSelection()) {
|
||||
// Get all entities touching the bounding box of the current selection
|
||||
var boundingBoxCorner = Vec3.subtract(selectionManager.worldPosition,
|
||||
Vec3.multiply(selectionManager.worldDimensions, 0.5));
|
||||
var entities = Entities.findEntitiesInBox(boundingBoxCorner, selectionManager.worldDimensions);
|
||||
|
||||
if (!keepIfTouching) {
|
||||
var isValid;
|
||||
if (selectionManager.localPosition === null) {
|
||||
isValid = function(position) {
|
||||
return insideBox(selectionManager.worldPosition, selectionManager.worldDimensions, position);
|
||||
}
|
||||
} else {
|
||||
isValid = function(position) {
|
||||
var localPosition = Vec3.multiplyQbyV(Quat.inverse(selectionManager.localRotation),
|
||||
Vec3.subtract(position,
|
||||
selectionManager.localPosition));
|
||||
return insideBox({ x: 0, y: 0, z: 0 }, selectionManager.localDimensions, localPosition);
|
||||
}
|
||||
}
|
||||
for (var i = 0; i < entities.length; ++i) {
|
||||
var properties = Entities.getEntityProperties(entities[i]);
|
||||
if (!isValid(properties.position)) {
|
||||
entities.splice(i, 1);
|
||||
--i;
|
||||
}
|
||||
}
|
||||
}
|
||||
selectionManager.setSelections(entities);
|
||||
}
|
||||
}
|
||||
|
||||
function deleteSelectedEntities() {
|
||||
if (SelectionManager.hasSelection()) {
|
||||
print(" Delete Entities");
|
||||
|
@ -837,12 +933,19 @@ function handeMenuEvent(menuItem) {
|
|||
}
|
||||
} else if (menuItem == "Entity List...") {
|
||||
entityListTool.toggleVisible();
|
||||
} else if (menuItem == "Select All Entities In Box") {
|
||||
selectAllEtitiesInCurrentSelectionBox(false);
|
||||
} else if (menuItem == "Select All Entities Touching Box") {
|
||||
selectAllEtitiesInCurrentSelectionBox(true);
|
||||
} else if (menuItem == MENU_SHOW_LIGHTS_IN_EDIT_MODE) {
|
||||
lightOverlayManager.setVisible(isActive && Menu.isOptionChecked(MENU_SHOW_LIGHTS_IN_EDIT_MODE));
|
||||
}
|
||||
tooltip.show(false);
|
||||
}
|
||||
|
||||
function importSVO(importURL) {
|
||||
Overlays.editOverlay(importingSVOOverlay, { visible: true });
|
||||
Overlays.editOverlay(importingSVOTextOverlay, { visible: true });
|
||||
Overlays.editOverlay(importingSVOImageOverlay, { visible: true });
|
||||
|
||||
var success = Clipboard.importEntities(importURL);
|
||||
|
||||
|
@ -861,11 +964,14 @@ function importSVO(importURL) {
|
|||
if (isActive) {
|
||||
selectionManager.setSelections(pastedEntityIDs);
|
||||
}
|
||||
|
||||
Window.raiseMainWindow();
|
||||
} else {
|
||||
Window.alert("There was an error importing the entity file.");
|
||||
}
|
||||
|
||||
Overlays.editOverlay(importingSVOOverlay, { visible: false });
|
||||
Overlays.editOverlay(importingSVOTextOverlay, { visible: false });
|
||||
Overlays.editOverlay(importingSVOImageOverlay, { visible: false });
|
||||
}
|
||||
Window.svoImportRequested.connect(importSVO);
|
||||
|
||||
|
@ -989,7 +1095,7 @@ PropertiesTool = function(opts) {
|
|||
var that = {};
|
||||
|
||||
var url = Script.resolvePath('html/entityProperties.html');
|
||||
var webView = new WebWindow('Entity Properties', url, 200, 280);
|
||||
var webView = new WebWindow('Entity Properties', url, 200, 280, true);
|
||||
|
||||
var visible = false;
|
||||
|
|
@ -2,7 +2,7 @@ EntityListTool = function(opts) {
|
|||
var that = {};
|
||||
|
||||
var url = Script.resolvePath('html/entityList.html');
|
||||
var webView = new WebWindow('Entities', url, 200, 280);
|
||||
var webView = new WebWindow('Entities', url, 200, 280, true);
|
||||
|
||||
var visible = false;
|
||||
|
||||
|
|
|
@ -229,7 +229,7 @@ GridTool = function(opts) {
|
|||
var listeners = [];
|
||||
|
||||
var url = Script.resolvePath('html/gridControls.html');
|
||||
var webView = new WebWindow('Grid', url, 200, 280);
|
||||
var webView = new WebWindow('Grid', url, 200, 280, true);
|
||||
|
||||
horizontalGrid.addListener(function(data) {
|
||||
webView.eventBridge.emitScriptEvent(JSON.stringify(data));
|
||||
|
|
135
examples/libraries/lightOverlayManager.js
Normal file
135
examples/libraries/lightOverlayManager.js
Normal file
|
@ -0,0 +1,135 @@
|
|||
var POINT_LIGHT_URL = "http://s3.amazonaws.com/hifi-public/images/tools/point-light.svg";
|
||||
var SPOT_LIGHT_URL = "http://s3.amazonaws.com/hifi-public/images/tools/spot-light.svg";
|
||||
|
||||
LightOverlayManager = function() {
|
||||
var self = this;
|
||||
|
||||
var visible = false;
|
||||
|
||||
// List of all created overlays
|
||||
var allOverlays = [];
|
||||
|
||||
// List of overlays not currently being used
|
||||
var unusedOverlays = [];
|
||||
|
||||
// Map from EntityItemID.id to overlay id
|
||||
var entityOverlays = {};
|
||||
|
||||
// Map from EntityItemID.id to EntityItemID object
|
||||
var entityIDs = {};
|
||||
|
||||
this.updatePositions = function(ids) {
|
||||
for (var id in entityIDs) {
|
||||
var entityID = entityIDs[id];
|
||||
var properties = Entities.getEntityProperties(entityID);
|
||||
Overlays.editOverlay(entityOverlays[entityID.id], {
|
||||
position: properties.position
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
this.findRayIntersection = function(pickRay) {
|
||||
var result = Overlays.findRayIntersection(pickRay);
|
||||
var found = false;
|
||||
|
||||
if (result.intersects) {
|
||||
for (var id in entityOverlays) {
|
||||
if (result.overlayID == entityOverlays[id]) {
|
||||
result.entityID = entityIDs[id];
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!found) {
|
||||
result.intersects = false;
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
};
|
||||
|
||||
this.setVisible = function(isVisible) {
|
||||
if (visible != isVisible) {
|
||||
visible = isVisible;
|
||||
for (var id in entityOverlays) {
|
||||
Overlays.editOverlay(entityOverlays[id], { visible: visible });
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// Allocate or get an unused overlay
|
||||
function getOverlay() {
|
||||
if (unusedOverlays.length == 0) {
|
||||
var overlay = Overlays.addOverlay("billboard", {
|
||||
});
|
||||
allOverlays.push(overlay);
|
||||
} else {
|
||||
var overlay = unusedOverlays.pop();
|
||||
};
|
||||
return overlay;
|
||||
}
|
||||
|
||||
function releaseOverlay(overlay) {
|
||||
unusedOverlays.push(overlay);
|
||||
Overlays.editOverlay(overlay, { visible: false });
|
||||
}
|
||||
|
||||
function addEntity(entityID) {
|
||||
var properties = Entities.getEntityProperties(entityID);
|
||||
if (properties.type == "Light" && !(entityID.id in entityOverlays)) {
|
||||
var overlay = getOverlay();
|
||||
entityOverlays[entityID.id] = overlay;
|
||||
entityIDs[entityID.id] = entityID;
|
||||
Overlays.editOverlay(overlay, {
|
||||
position: properties.position,
|
||||
url: properties.isSpotlight ? SPOT_LIGHT_URL : POINT_LIGHT_URL,
|
||||
rotation: Quat.fromPitchYawRollDegrees(0, 0, 270),
|
||||
visible: visible,
|
||||
alpha: 0.9,
|
||||
scale: 0.5,
|
||||
color: { red: 255, green: 255, blue: 255 }
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function deleteEntity(entityID) {
|
||||
if (entityID.id in entityOverlays) {
|
||||
releaseOverlay(entityOverlays[entityID.id]);
|
||||
delete entityOverlays[entityID.id];
|
||||
}
|
||||
}
|
||||
|
||||
function changeEntityID(oldEntityID, newEntityID) {
|
||||
entityOverlays[newEntityID.id] = entityOverlays[oldEntityID.id];
|
||||
entityIDs[newEntityID.id] = newEntityID;
|
||||
|
||||
delete entityOverlays[oldEntityID.id];
|
||||
delete entityIDs[oldEntityID.id];
|
||||
}
|
||||
|
||||
function clearEntities() {
|
||||
for (var id in entityOverlays) {
|
||||
releaseOverlay(entityOverlays[id]);
|
||||
}
|
||||
entityOverlays = {};
|
||||
entityIDs = {};
|
||||
}
|
||||
|
||||
Entities.addingEntity.connect(addEntity);
|
||||
Entities.changingEntityID.connect(changeEntityID);
|
||||
Entities.deletingEntity.connect(deleteEntity);
|
||||
Entities.clearingEntities.connect(clearEntities);
|
||||
|
||||
// Add existing entities
|
||||
var ids = Entities.findEntities(MyAvatar.position, 100);
|
||||
for (var i = 0; i < ids.length; i++) {
|
||||
addEntity(ids[i]);
|
||||
}
|
||||
|
||||
Script.scriptEnding.connect(function() {
|
||||
for (var i = 0; i < allOverlays.length; i++) {
|
||||
Overlays.deleteOverlay(allOverlays[i]);
|
||||
}
|
||||
});
|
||||
};
|
|
@ -94,7 +94,7 @@ Tool = function(properties, selectable, selected) { // selectable and selected a
|
|||
}
|
||||
|
||||
selected = doSelect;
|
||||
properties.subImage.y = (selected ? 2 : 1) * properties.subImage.height;
|
||||
properties.subImage.y = (selected ? 0 : 1) * properties.subImage.height;
|
||||
Overlays.editOverlay(this.overlay(), { subImage: properties.subImage });
|
||||
}
|
||||
this.toggle = function() {
|
||||
|
@ -102,7 +102,7 @@ Tool = function(properties, selectable, selected) { // selectable and selected a
|
|||
return;
|
||||
}
|
||||
selected = !selected;
|
||||
properties.subImage.y = (selected ? 2 : 1) * properties.subImage.height;
|
||||
properties.subImage.y = (selected ? 0 : 1) * properties.subImage.height;
|
||||
Overlays.editOverlay(this.overlay(), { subImage: properties.subImage });
|
||||
|
||||
return selected;
|
||||
|
|
|
@ -13,29 +13,39 @@ var usersWindow = (function () {
|
|||
|
||||
var WINDOW_WIDTH_2D = 150,
|
||||
WINDOW_MARGIN_2D = 12,
|
||||
WINDOW_FONT_2D = { size: 12 },
|
||||
WINDOW_FOREGROUND_COLOR_2D = { red: 240, green: 240, blue: 240 },
|
||||
WINDOW_FOREGROUND_ALPHA_2D = 0.9,
|
||||
WINDOW_HEADING_COLOR_2D = { red: 180, green: 180, blue: 180 },
|
||||
WINDOW_HEADING_ALPHA_2D = 0.9,
|
||||
WINDOW_BACKGROUND_COLOR_2D = { red: 80, green: 80, blue: 80 },
|
||||
WINDOW_BACKGROUND_ALPHA_2D = 0.7,
|
||||
windowHeight = 0,
|
||||
usersPane2D,
|
||||
usersHeading2D,
|
||||
USERS_FONT_2D = { size: 14 },
|
||||
usersLineHeight,
|
||||
usersLineSpacing,
|
||||
windowPane2D,
|
||||
windowHeading2D,
|
||||
VISIBILITY_SPACER_2D = 12, // Space between list of users and visibility controls
|
||||
visibilityHeading2D,
|
||||
VISIBILITY_RADIO_SPACE = 16,
|
||||
visibilityControls2D,
|
||||
windowHeight,
|
||||
windowTextHeight,
|
||||
windowLineSpacing,
|
||||
windowLineHeight, // = windowTextHeight + windowLineSpacing
|
||||
|
||||
usersOnline, // Raw data
|
||||
linesOfUsers, // Array of indexes pointing into usersOnline
|
||||
usersOnline, // Raw users data
|
||||
linesOfUsers = [], // Array of indexes pointing into usersOnline
|
||||
|
||||
API_URL = "https://metaverse.highfidelity.io/api/v1/users?status=online",
|
||||
HTTP_GET_TIMEOUT = 60000, // ms = 1 minute
|
||||
HTTP_GET_TIMEOUT = 60000, // ms = 1 minute
|
||||
usersRequest,
|
||||
processUsers,
|
||||
usersTimedOut,
|
||||
pollUsersTimedOut,
|
||||
usersTimer = null,
|
||||
UPDATE_TIMEOUT = 5000, // ms = 5s
|
||||
USERS_UPDATE_TIMEOUT = 5000, // ms = 5s
|
||||
|
||||
myVisibility,
|
||||
VISIBILITY_VALUES = ["all", "friends", "none"],
|
||||
visibilityInterval,
|
||||
VISIBILITY_POLL_INTERVAL = 5000, // ms = 5s
|
||||
|
||||
MENU_NAME = "Tools",
|
||||
MENU_ITEM = "Users Online",
|
||||
|
@ -43,48 +53,62 @@ var usersWindow = (function () {
|
|||
|
||||
isVisible = true,
|
||||
|
||||
viewportHeight;
|
||||
viewportHeight,
|
||||
|
||||
function onMousePressEvent(event) {
|
||||
var clickedOverlay,
|
||||
numLinesBefore,
|
||||
overlayX,
|
||||
overlayY,
|
||||
minY,
|
||||
maxY,
|
||||
lineClicked;
|
||||
HIFI_PUBLIC_BUCKET = "http://s3.amazonaws.com/hifi-public/",
|
||||
RADIO_BUTTON_SVG = HIFI_PUBLIC_BUCKET + "images/radio-button.svg",
|
||||
RADIO_BUTTON_SVG_DIAMETER = 14,
|
||||
RADIO_BUTTON_DISPLAY_SCALE = 0.7, // 1.0 = windowTextHeight
|
||||
radioButtonDiameter;
|
||||
|
||||
if (!isVisible) {
|
||||
return;
|
||||
}
|
||||
function calculateWindowHeight() {
|
||||
// Reserve 5 lines for window heading plus visibility heading and controls
|
||||
// Subtract windowLineSpacing for both end of user list and end of controls
|
||||
windowHeight = (linesOfUsers.length > 0 ? linesOfUsers.length + 5 : 5) * windowLineHeight
|
||||
- 2 * windowLineSpacing + VISIBILITY_SPACER_2D + 2 * WINDOW_MARGIN_2D;
|
||||
}
|
||||
|
||||
clickedOverlay = Overlays.getOverlayAtPoint({ x: event.x, y: event.y });
|
||||
if (clickedOverlay === usersPane2D) {
|
||||
function updateOverlayPositions() {
|
||||
var i,
|
||||
y;
|
||||
|
||||
overlayX = event.x - WINDOW_MARGIN_2D;
|
||||
overlayY = event.y - viewportHeight + windowHeight - WINDOW_MARGIN_2D - (usersLineHeight + usersLineSpacing);
|
||||
|
||||
numLinesBefore = Math.floor(overlayY / (usersLineHeight + usersLineSpacing));
|
||||
minY = numLinesBefore * (usersLineHeight + usersLineSpacing);
|
||||
maxY = minY + usersLineHeight;
|
||||
|
||||
lineClicked = -1;
|
||||
if (minY <= overlayY && overlayY <= maxY) {
|
||||
lineClicked = numLinesBefore;
|
||||
}
|
||||
|
||||
if (0 <= lineClicked && lineClicked < linesOfUsers.length
|
||||
&& overlayX <= usersOnline[linesOfUsers[lineClicked]].usernameWidth) {
|
||||
//print("Go to " + usersOnline[linesOfUsers[lineClicked]].username);
|
||||
location.goToUser(usersOnline[linesOfUsers[lineClicked]].username);
|
||||
}
|
||||
Overlays.editOverlay(windowPane2D, {
|
||||
y: viewportHeight - windowHeight
|
||||
});
|
||||
Overlays.editOverlay(windowHeading2D, {
|
||||
y: viewportHeight - windowHeight + WINDOW_MARGIN_2D
|
||||
});
|
||||
Overlays.editOverlay(visibilityHeading2D, {
|
||||
y: viewportHeight - 4 * windowLineHeight + windowLineSpacing - WINDOW_MARGIN_2D
|
||||
});
|
||||
for (i = 0; i < visibilityControls2D.length; i += 1) {
|
||||
y = viewportHeight - (3 - i) * windowLineHeight + windowLineSpacing - WINDOW_MARGIN_2D;
|
||||
Overlays.editOverlay(visibilityControls2D[i].radioOverlay, { y: y });
|
||||
Overlays.editOverlay(visibilityControls2D[i].textOverlay, { y: y });
|
||||
}
|
||||
}
|
||||
|
||||
function updateWindow() {
|
||||
function updateVisibilityControls() {
|
||||
var i,
|
||||
color;
|
||||
|
||||
for (i = 0; i < visibilityControls2D.length; i += 1) {
|
||||
color = visibilityControls2D[i].selected ? WINDOW_FOREGROUND_COLOR_2D : WINDOW_HEADING_COLOR_2D;
|
||||
Overlays.editOverlay(visibilityControls2D[i].radioOverlay, {
|
||||
color: color,
|
||||
subImage: { y: visibilityControls2D[i].selected ? 0 : RADIO_BUTTON_SVG_DIAMETER }
|
||||
});
|
||||
Overlays.editOverlay(visibilityControls2D[i].textOverlay, { color: color });
|
||||
}
|
||||
}
|
||||
|
||||
function updateUsersDisplay() {
|
||||
var displayText = "",
|
||||
myUsername,
|
||||
user,
|
||||
userText,
|
||||
textWidth,
|
||||
maxTextWidth,
|
||||
i;
|
||||
|
||||
myUsername = GlobalServices.username;
|
||||
|
@ -92,32 +116,53 @@ var usersWindow = (function () {
|
|||
for (i = 0; i < usersOnline.length; i += 1) {
|
||||
user = usersOnline[i];
|
||||
if (user.username !== myUsername && user.online) {
|
||||
usersOnline[i].usernameWidth = Overlays.textSize(usersPane2D, user.username).width;
|
||||
userText = user.username;
|
||||
if (user.location.root) {
|
||||
userText += " @ " + user.location.root.name;
|
||||
}
|
||||
textWidth = Overlays.textSize(windowPane2D, userText).width;
|
||||
|
||||
maxTextWidth = WINDOW_WIDTH_2D - 2 * WINDOW_MARGIN_2D;
|
||||
if (textWidth > maxTextWidth) {
|
||||
// Trim and append "..." to fit window width
|
||||
maxTextWidth = maxTextWidth - Overlays.textSize(windowPane2D, "...").width;
|
||||
while (textWidth > maxTextWidth) {
|
||||
userText = userText.slice(0, -1);
|
||||
textWidth = Overlays.textSize(windowPane2D, userText).width;
|
||||
}
|
||||
userText += "...";
|
||||
textWidth = Overlays.textSize(windowPane2D, userText).width;
|
||||
}
|
||||
|
||||
usersOnline[i].textWidth = textWidth;
|
||||
linesOfUsers.push(i);
|
||||
displayText += "\n" + user.username;
|
||||
displayText += "\n" + userText;
|
||||
}
|
||||
}
|
||||
|
||||
windowHeight = (linesOfUsers.length > 0 ? linesOfUsers.length + 1 : 1) * (usersLineHeight + usersLineSpacing)
|
||||
- usersLineSpacing + 2 * WINDOW_MARGIN_2D; // First or only line is for heading
|
||||
displayText = displayText.slice(1); // Remove leading "\n".
|
||||
|
||||
Overlays.editOverlay(usersPane2D, {
|
||||
calculateWindowHeight();
|
||||
|
||||
Overlays.editOverlay(windowPane2D, {
|
||||
y: viewportHeight - windowHeight,
|
||||
height: windowHeight,
|
||||
text: displayText
|
||||
});
|
||||
|
||||
Overlays.editOverlay(usersHeading2D, {
|
||||
Overlays.editOverlay(windowHeading2D, {
|
||||
y: viewportHeight - windowHeight + WINDOW_MARGIN_2D,
|
||||
text: linesOfUsers.length > 0 ? "Online" : "No users online"
|
||||
text: linesOfUsers.length > 0 ? "Users online" : "No users online"
|
||||
});
|
||||
|
||||
updateOverlayPositions();
|
||||
}
|
||||
|
||||
function requestUsers() {
|
||||
function pollUsers() {
|
||||
usersRequest = new XMLHttpRequest();
|
||||
usersRequest.open("GET", API_URL, true);
|
||||
usersRequest.timeout = HTTP_GET_TIMEOUT;
|
||||
usersRequest.ontimeout = usersTimedOut;
|
||||
usersRequest.ontimeout = pollUsersTimedOut;
|
||||
usersRequest.onreadystatechange = processUsers;
|
||||
usersRequest.send();
|
||||
}
|
||||
|
@ -130,46 +175,62 @@ var usersWindow = (function () {
|
|||
response = JSON.parse(usersRequest.responseText);
|
||||
if (response.status !== "success") {
|
||||
print("Error: Request for users status returned status = " + response.status);
|
||||
usersTimer = Script.setTimeout(requestUsers, HTTP_GET_TIMEOUT); // Try again after a longer delay.
|
||||
usersTimer = Script.setTimeout(pollUsers, HTTP_GET_TIMEOUT); // Try again after a longer delay.
|
||||
return;
|
||||
}
|
||||
if (!response.hasOwnProperty("data") || !response.data.hasOwnProperty("users")) {
|
||||
print("Error: Request for users status returned invalid data");
|
||||
usersTimer = Script.setTimeout(requestUsers, HTTP_GET_TIMEOUT); // Try again after a longer delay.
|
||||
usersTimer = Script.setTimeout(pollUsers, HTTP_GET_TIMEOUT); // Try again after a longer delay.
|
||||
return;
|
||||
}
|
||||
|
||||
usersOnline = response.data.users;
|
||||
updateWindow();
|
||||
updateUsersDisplay();
|
||||
} else {
|
||||
print("Error: Request for users status returned " + usersRequest.status + " " + usersRequest.statusText);
|
||||
usersTimer = Script.setTimeout(requestUsers, HTTP_GET_TIMEOUT); // Try again after a longer delay.
|
||||
usersTimer = Script.setTimeout(pollUsers, HTTP_GET_TIMEOUT); // Try again after a longer delay.
|
||||
return;
|
||||
}
|
||||
|
||||
usersTimer = Script.setTimeout(requestUsers, UPDATE_TIMEOUT); // Update after finished processing.
|
||||
usersTimer = Script.setTimeout(pollUsers, USERS_UPDATE_TIMEOUT); // Update after finished processing.
|
||||
}
|
||||
};
|
||||
|
||||
usersTimedOut = function () {
|
||||
pollUsersTimedOut = function () {
|
||||
print("Error: Request for users status timed out");
|
||||
usersTimer = Script.setTimeout(requestUsers, HTTP_GET_TIMEOUT); // Try again after a longer delay.
|
||||
usersTimer = Script.setTimeout(pollUsers, HTTP_GET_TIMEOUT); // Try again after a longer delay.
|
||||
};
|
||||
|
||||
function pollVisibility() {
|
||||
var currentVisibility = myVisibility;
|
||||
|
||||
myVisibility = GlobalServices.findableBy;
|
||||
if (myVisibility !== currentVisibility) {
|
||||
updateVisibilityControls();
|
||||
}
|
||||
}
|
||||
|
||||
function setVisible(visible) {
|
||||
var i;
|
||||
|
||||
isVisible = visible;
|
||||
|
||||
if (isVisible) {
|
||||
if (usersTimer === null) {
|
||||
requestUsers();
|
||||
pollUsers();
|
||||
}
|
||||
} else {
|
||||
Script.clearTimeout(usersTimer);
|
||||
usersTimer = null;
|
||||
}
|
||||
|
||||
Overlays.editOverlay(usersPane2D, { visible: isVisible });
|
||||
Overlays.editOverlay(usersHeading2D, { visible: isVisible });
|
||||
Overlays.editOverlay(windowPane2D, { visible: isVisible });
|
||||
Overlays.editOverlay(windowHeading2D, { visible: isVisible });
|
||||
Overlays.editOverlay(visibilityHeading2D, { visible: isVisible });
|
||||
for (i = 0; i < visibilityControls2D.length; i += 1) {
|
||||
Overlays.editOverlay(visibilityControls2D[i].radioOverlay, { visible: isVisible });
|
||||
Overlays.editOverlay(visibilityControls2D[i].textOverlay, { visible: isVisible });
|
||||
}
|
||||
}
|
||||
|
||||
function onMenuItemEvent(event) {
|
||||
|
@ -178,48 +239,194 @@ var usersWindow = (function () {
|
|||
}
|
||||
}
|
||||
|
||||
function update() {
|
||||
function onMousePressEvent(event) {
|
||||
var clickedOverlay,
|
||||
numLinesBefore,
|
||||
overlayX,
|
||||
overlayY,
|
||||
minY,
|
||||
maxY,
|
||||
lineClicked,
|
||||
i,
|
||||
visibilityChanged;
|
||||
|
||||
if (!isVisible) {
|
||||
return;
|
||||
}
|
||||
|
||||
clickedOverlay = Overlays.getOverlayAtPoint({ x: event.x, y: event.y });
|
||||
|
||||
if (clickedOverlay === windowPane2D) {
|
||||
|
||||
overlayX = event.x - WINDOW_MARGIN_2D;
|
||||
overlayY = event.y - viewportHeight + windowHeight - WINDOW_MARGIN_2D - windowLineHeight;
|
||||
|
||||
numLinesBefore = Math.floor(overlayY / windowLineHeight);
|
||||
minY = numLinesBefore * windowLineHeight;
|
||||
maxY = minY + windowTextHeight;
|
||||
|
||||
lineClicked = -1;
|
||||
if (minY <= overlayY && overlayY <= maxY) {
|
||||
lineClicked = numLinesBefore;
|
||||
}
|
||||
|
||||
if (0 <= lineClicked && lineClicked < linesOfUsers.length
|
||||
&& 0 <= overlayX && overlayX <= usersOnline[linesOfUsers[lineClicked]].textWidth) {
|
||||
//print("Go to " + usersOnline[linesOfUsers[lineClicked]].username);
|
||||
location.goToUser(usersOnline[linesOfUsers[lineClicked]].username);
|
||||
}
|
||||
}
|
||||
|
||||
visibilityChanged = false;
|
||||
for (i = 0; i < visibilityControls2D.length; i += 1) {
|
||||
// Don't need to test radioOverlay if it us under textOverlay.
|
||||
if (clickedOverlay === visibilityControls2D[i].textOverlay && event.x <= visibilityControls2D[i].optionWidth) {
|
||||
GlobalServices.findableBy = VISIBILITY_VALUES[i];
|
||||
visibilityChanged = true;
|
||||
}
|
||||
}
|
||||
if (visibilityChanged) {
|
||||
for (i = 0; i < visibilityControls2D.length; i += 1) {
|
||||
// Don't need to handle radioOverlay if it us under textOverlay.
|
||||
visibilityControls2D[i].selected = clickedOverlay === visibilityControls2D[i].textOverlay;
|
||||
}
|
||||
updateVisibilityControls();
|
||||
}
|
||||
}
|
||||
|
||||
function onScriptUpdate() {
|
||||
var oldViewportHeight = viewportHeight;
|
||||
|
||||
viewportHeight = Controller.getViewportDimensions().y;
|
||||
Overlays.editOverlay(usersPane2D, {
|
||||
y: viewportHeight - windowHeight
|
||||
});
|
||||
Overlays.editOverlay(usersHeading2D, {
|
||||
y: viewportHeight - windowHeight + WINDOW_MARGIN_2D
|
||||
});
|
||||
if (viewportHeight !== oldViewportHeight) {
|
||||
updateOverlayPositions();
|
||||
}
|
||||
}
|
||||
|
||||
function setUp() {
|
||||
usersPane2D = Overlays.addOverlay("text", {
|
||||
bounds: { x: 0, y: 0, width: WINDOW_WIDTH_2D, height: 0 },
|
||||
topMargin: WINDOW_MARGIN_2D,
|
||||
var textSizeOverlay,
|
||||
optionText;
|
||||
|
||||
textSizeOverlay = Overlays.addOverlay("text", { font: WINDOW_FONT_2D, visible: false });
|
||||
windowTextHeight = Math.floor(Overlays.textSize(textSizeOverlay, "1").height);
|
||||
windowLineSpacing = Math.floor(Overlays.textSize(textSizeOverlay, "1\n2").height - 2 * windowTextHeight);
|
||||
windowLineHeight = windowTextHeight + windowLineSpacing;
|
||||
radioButtonDiameter = RADIO_BUTTON_DISPLAY_SCALE * windowTextHeight;
|
||||
Overlays.deleteOverlay(textSizeOverlay);
|
||||
|
||||
calculateWindowHeight();
|
||||
|
||||
viewportHeight = Controller.getViewportDimensions().y;
|
||||
|
||||
windowPane2D = Overlays.addOverlay("text", {
|
||||
x: 0,
|
||||
y: viewportHeight, // Start up off-screen
|
||||
width: WINDOW_WIDTH_2D,
|
||||
height: windowHeight,
|
||||
topMargin: WINDOW_MARGIN_2D + windowLineHeight,
|
||||
leftMargin: WINDOW_MARGIN_2D,
|
||||
color: WINDOW_FOREGROUND_COLOR_2D,
|
||||
alpha: WINDOW_FOREGROUND_ALPHA_2D,
|
||||
backgroundColor: WINDOW_BACKGROUND_COLOR_2D,
|
||||
backgroundAlpha: WINDOW_BACKGROUND_ALPHA_2D,
|
||||
text: "",
|
||||
font: USERS_FONT_2D,
|
||||
font: WINDOW_FONT_2D,
|
||||
visible: isVisible
|
||||
});
|
||||
|
||||
usersHeading2D = Overlays.addOverlay("text", {
|
||||
windowHeading2D = Overlays.addOverlay("text", {
|
||||
x: WINDOW_MARGIN_2D,
|
||||
y: viewportHeight,
|
||||
width: WINDOW_WIDTH_2D - 2 * WINDOW_MARGIN_2D,
|
||||
height: USERS_FONT_2D.size,
|
||||
height: windowTextHeight,
|
||||
topMargin: 0,
|
||||
leftMargin: 0,
|
||||
color: WINDOW_HEADING_COLOR_2D,
|
||||
alpha: WINDOW_HEADING_ALPHA_2D,
|
||||
backgroundAlpha: 0.0,
|
||||
text: "No users online",
|
||||
font: USERS_FONT_2D,
|
||||
font: WINDOW_FONT_2D,
|
||||
visible: isVisible
|
||||
});
|
||||
|
||||
viewportHeight = Controller.getViewportDimensions().y;
|
||||
visibilityHeading2D = Overlays.addOverlay("text", {
|
||||
x: WINDOW_MARGIN_2D,
|
||||
y: viewportHeight,
|
||||
width: WINDOW_WIDTH_2D - 2 * WINDOW_MARGIN_2D,
|
||||
height: windowTextHeight,
|
||||
topMargin: 0,
|
||||
leftMargin: 0,
|
||||
color: WINDOW_HEADING_COLOR_2D,
|
||||
alpha: WINDOW_HEADING_ALPHA_2D,
|
||||
backgroundAlpha: 0.0,
|
||||
text: "I am visible to:",
|
||||
font: WINDOW_FONT_2D,
|
||||
visible: isVisible
|
||||
});
|
||||
|
||||
usersLineHeight = Math.floor(Overlays.textSize(usersPane2D, "1").height);
|
||||
usersLineSpacing = Math.floor(Overlays.textSize(usersPane2D, "1\n2").height - 2 * usersLineHeight);
|
||||
myVisibility = GlobalServices.findableBy;
|
||||
if (!/^(all|friends|none)$/.test(myVisibility)) {
|
||||
print("Error: Unrecognized findableBy value");
|
||||
myVisibility = "";
|
||||
}
|
||||
|
||||
optionText = "everyone";
|
||||
visibilityControls2D = [{
|
||||
radioOverlay: Overlays.addOverlay("image", { // Create first so that it is under textOverlay.
|
||||
x: WINDOW_MARGIN_2D,
|
||||
y: viewportHeight,
|
||||
width: radioButtonDiameter,
|
||||
height: radioButtonDiameter,
|
||||
imageURL: RADIO_BUTTON_SVG,
|
||||
subImage: {
|
||||
x: 0,
|
||||
y: RADIO_BUTTON_SVG_DIAMETER, // Off
|
||||
width: RADIO_BUTTON_SVG_DIAMETER,
|
||||
height: RADIO_BUTTON_SVG_DIAMETER
|
||||
},
|
||||
color: WINDOW_HEADING_COLOR_2D,
|
||||
alpha: WINDOW_FOREGROUND_ALPHA_2D
|
||||
}),
|
||||
textOverlay: Overlays.addOverlay("text", {
|
||||
x: WINDOW_MARGIN_2D,
|
||||
y: viewportHeight,
|
||||
width: WINDOW_WIDTH_2D - 2 * WINDOW_MARGIN_2D,
|
||||
height: windowTextHeight,
|
||||
topMargin: 0,
|
||||
leftMargin: VISIBILITY_RADIO_SPACE,
|
||||
color: WINDOW_HEADING_COLOR_2D,
|
||||
alpha: WINDOW_FOREGROUND_ALPHA_2D,
|
||||
backgroundAlpha: 0.0,
|
||||
text: optionText,
|
||||
font: WINDOW_FONT_2D,
|
||||
visible: isVisible
|
||||
}),
|
||||
selected: myVisibility === VISIBILITY_VALUES[0]
|
||||
}];
|
||||
visibilityControls2D[0].optionWidth = WINDOW_MARGIN_2D + VISIBILITY_RADIO_SPACE
|
||||
+ Overlays.textSize(visibilityControls2D[0].textOverlay, optionText).width;
|
||||
|
||||
optionText = "my friends";
|
||||
visibilityControls2D[1] = {
|
||||
radioOverlay: Overlays.cloneOverlay(visibilityControls2D[0].radioOverlay),
|
||||
textOverlay: Overlays.cloneOverlay(visibilityControls2D[0].textOverlay),
|
||||
selected: myVisibility === VISIBILITY_VALUES[1]
|
||||
};
|
||||
Overlays.editOverlay(visibilityControls2D[1].textOverlay, { text: optionText });
|
||||
visibilityControls2D[1].optionWidth = WINDOW_MARGIN_2D + VISIBILITY_RADIO_SPACE
|
||||
+ Overlays.textSize(visibilityControls2D[1].textOverlay, optionText).width;
|
||||
|
||||
optionText = "no one";
|
||||
visibilityControls2D[2] = {
|
||||
radioOverlay: Overlays.cloneOverlay(visibilityControls2D[0].radioOverlay),
|
||||
textOverlay: Overlays.cloneOverlay(visibilityControls2D[0].textOverlay),
|
||||
selected: myVisibility === VISIBILITY_VALUES[2]
|
||||
};
|
||||
Overlays.editOverlay(visibilityControls2D[2].textOverlay, { text: optionText });
|
||||
visibilityControls2D[2].optionWidth = WINDOW_MARGIN_2D + VISIBILITY_RADIO_SPACE
|
||||
+ Overlays.textSize(visibilityControls2D[2].textOverlay, optionText).width;
|
||||
|
||||
updateVisibilityControls();
|
||||
|
||||
Controller.mousePressEvent.connect(onMousePressEvent);
|
||||
|
||||
|
@ -232,17 +439,28 @@ var usersWindow = (function () {
|
|||
});
|
||||
Menu.menuItemEvent.connect(onMenuItemEvent);
|
||||
|
||||
Script.update.connect(update);
|
||||
Script.update.connect(onScriptUpdate);
|
||||
|
||||
visibilityInterval = Script.setInterval(pollVisibility, VISIBILITY_POLL_INTERVAL);
|
||||
|
||||
pollUsers();
|
||||
|
||||
requestUsers();
|
||||
}
|
||||
|
||||
function tearDown() {
|
||||
var i;
|
||||
|
||||
Menu.removeMenuItem(MENU_NAME, MENU_ITEM);
|
||||
|
||||
Script.clearInterval(visibilityInterval);
|
||||
Script.clearTimeout(usersTimer);
|
||||
Overlays.deleteOverlay(usersHeading2D);
|
||||
Overlays.deleteOverlay(usersPane2D);
|
||||
Overlays.deleteOverlay(windowPane2D);
|
||||
Overlays.deleteOverlay(windowHeading2D);
|
||||
Overlays.deleteOverlay(visibilityHeading2D);
|
||||
for (i = 0; i <= visibilityControls2D.length; i += 1) {
|
||||
Overlays.deleteOverlay(visibilityControls2D[i].textOverlay);
|
||||
Overlays.deleteOverlay(visibilityControls2D[i].radioOverlay);
|
||||
}
|
||||
}
|
||||
|
||||
setUp();
|
||||
|
|
|
@ -220,15 +220,17 @@ else (APPLE)
|
|||
find_package(GLEW REQUIRED)
|
||||
target_include_directories(${TARGET_NAME} PRIVATE ${GLEW_INCLUDE_DIRS})
|
||||
|
||||
target_link_libraries(${TARGET_NAME} ${GLEW_LIBRARIES} ${NSIGHT_LIBRARIES} wsock32.lib opengl32.lib Winmm.lib)
|
||||
target_link_libraries(${TARGET_NAME} ${GLEW_LIBRARIES} wsock32.lib opengl32.lib Winmm.lib)
|
||||
|
||||
# try to find the Nsight package and add it to the build if we find it
|
||||
find_package(NSIGHT)
|
||||
if (NSIGHT_FOUND)
|
||||
include_directories(${NSIGHT_INCLUDE_DIRS})
|
||||
add_definitions(-DNSIGHT_FOUND)
|
||||
target_link_libraries(${TARGET_NAME} "${NSIGHT_LIBRARIES}")
|
||||
endif ()
|
||||
if (USE_NSIGHT)
|
||||
# try to find the Nsight package and add it to the build if we find it
|
||||
find_package(NSIGHT)
|
||||
if (NSIGHT_FOUND)
|
||||
include_directories(${NSIGHT_INCLUDE_DIRS})
|
||||
add_definitions(-DNSIGHT_FOUND)
|
||||
target_link_libraries(${TARGET_NAME} "${NSIGHT_LIBRARIES}")
|
||||
endif ()
|
||||
endif()
|
||||
|
||||
endif()
|
||||
endif (APPLE)
|
||||
|
|
|
@ -573,10 +573,6 @@ void Application::cleanupBeforeQuit() {
|
|||
_settingsThread.quit();
|
||||
saveSettings();
|
||||
_window->saveGeometry();
|
||||
|
||||
// TODO: now that this is in cleanupBeforeQuit do we really need it to stop and force
|
||||
// an event loop to send the packet?
|
||||
UserActivityLogger::getInstance().close();
|
||||
|
||||
// let the avatar mixer know we're out
|
||||
MyAvatar::sendKillAvatar();
|
||||
|
@ -877,6 +873,10 @@ void Application::controlledBroadcastToNodes(const QByteArray& packet, const Nod
|
|||
}
|
||||
}
|
||||
|
||||
void Application::importSVOFromURL(QUrl url) {
|
||||
emit svoImportRequested(url.url());
|
||||
}
|
||||
|
||||
bool Application::event(QEvent* event) {
|
||||
|
||||
// handle custom URL
|
||||
|
@ -889,7 +889,7 @@ bool Application::event(QEvent* event) {
|
|||
if (!url.isEmpty()) {
|
||||
if (url.scheme() == HIFI_URL_SCHEME) {
|
||||
DependencyManager::get<AddressManager>()->handleLookupString(fileEvent->url().toString());
|
||||
} else if (url.url().toLower().endsWith(SVO_EXTENSION)) {
|
||||
} else if (url.path().toLower().endsWith(SVO_EXTENSION)) {
|
||||
emit svoImportRequested(url.url());
|
||||
}
|
||||
}
|
||||
|
@ -1455,10 +1455,11 @@ void Application::dropEvent(QDropEvent *event) {
|
|||
QString snapshotPath;
|
||||
const QMimeData *mimeData = event->mimeData();
|
||||
foreach (QUrl url, mimeData->urls()) {
|
||||
if (url.url().toLower().endsWith(SNAPSHOT_EXTENSION)) {
|
||||
auto lower = url.path().toLower();
|
||||
if (lower.endsWith(SNAPSHOT_EXTENSION)) {
|
||||
snapshotPath = url.toLocalFile();
|
||||
break;
|
||||
} else if (url.url().toLower().endsWith(SVO_EXTENSION)) {
|
||||
} else if (lower.endsWith(SVO_EXTENSION)) {
|
||||
emit svoImportRequested(url.url());
|
||||
event->acceptProposedAction();
|
||||
return;
|
||||
|
@ -2136,10 +2137,12 @@ void Application::updateCursor(float deltaTime) {
|
|||
}
|
||||
|
||||
void Application::updateCursorVisibility() {
|
||||
if (!_cursorVisible || Menu::getInstance()->isOptionChecked(MenuOption::EnableVRMode)) {
|
||||
_glWidget->setCursor(Qt::BlankCursor);
|
||||
if (!_cursorVisible ||
|
||||
Menu::getInstance()->isOptionChecked(MenuOption::EnableVRMode) ||
|
||||
Menu::getInstance()->isOptionChecked(MenuOption::Enable3DTVMode)) {
|
||||
_window->setCursor(Qt::BlankCursor);
|
||||
} else {
|
||||
_glWidget->unsetCursor();
|
||||
_window->unsetCursor();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -3109,17 +3112,25 @@ void Application::renderRearViewMirror(const QRect& region, bool billboard) {
|
|||
_myAvatar->getOrientation() * glm::vec3(0.0f, 0.0f, -1.0f) * MIRROR_REARVIEW_BODY_DISTANCE * _myAvatar->getScale());
|
||||
|
||||
} else { // HEAD zoom level
|
||||
_mirrorCamera.setFieldOfView(MIRROR_FIELD_OF_VIEW); // degrees
|
||||
if (_myAvatar->getSkeletonModel().isActive() && _myAvatar->getHead()->getFaceModel().isActive()) {
|
||||
// as a hack until we have a better way of dealing with coordinate precision issues, reposition the
|
||||
// face/body so that the average eye position lies at the origin
|
||||
eyeRelativeCamera = true;
|
||||
_mirrorCamera.setPosition(_myAvatar->getOrientation() * glm::vec3(0.0f, 0.0f, -1.0f) * MIRROR_REARVIEW_DISTANCE * _myAvatar->getScale());
|
||||
// FIXME note that the positioing of the camera relative to the avatar can suffer limited
|
||||
// precision as the user's position moves further away from the origin. Thus at
|
||||
// /1e7,1e7,1e7 (well outside the buildable volume) the mirror camera veers and sways
|
||||
// wildly as you rotate your avatar because the floating point values are becoming
|
||||
// larger, squeezing out the available digits of precision you have available at the
|
||||
// human scale for camera positioning.
|
||||
|
||||
} else {
|
||||
_mirrorCamera.setPosition(_myAvatar->getHead()->getEyePosition() +
|
||||
_myAvatar->getOrientation() * glm::vec3(0.0f, 0.0f, -1.0f) * MIRROR_REARVIEW_DISTANCE * _myAvatar->getScale());
|
||||
}
|
||||
// Previously there was a hack to correct this using the mechanism of repositioning
|
||||
// the avatar at the origin of the world for the purposes of rendering the mirror,
|
||||
// but it resulted in failing to render the avatar's head model in the mirror view
|
||||
// when in first person mode. Presumably this was because of some missed culling logic
|
||||
// that was not accounted for in the hack.
|
||||
|
||||
// This was removed in commit 71e59cfa88c6563749594e25494102fe01db38e9 but could be further
|
||||
// investigated in order to adapt the technique while fixing the head rendering issue,
|
||||
// but the complexity of the hack suggests that a better approach
|
||||
_mirrorCamera.setFieldOfView(MIRROR_FIELD_OF_VIEW); // degrees
|
||||
_mirrorCamera.setPosition(_myAvatar->getHead()->getEyePosition() +
|
||||
_myAvatar->getOrientation() * glm::vec3(0.0f, 0.0f, -1.0f) * MIRROR_REARVIEW_DISTANCE * _myAvatar->getScale());
|
||||
}
|
||||
_mirrorCamera.setAspectRatio((float)region.width() / region.height());
|
||||
|
||||
|
@ -3146,58 +3157,7 @@ void Application::renderRearViewMirror(const QRect& region, bool billboard) {
|
|||
|
||||
// render rear mirror view
|
||||
glPushMatrix();
|
||||
if (eyeRelativeCamera) {
|
||||
// save absolute translations
|
||||
glm::vec3 absoluteSkeletonTranslation = _myAvatar->getSkeletonModel().getTranslation();
|
||||
glm::vec3 absoluteFaceTranslation = _myAvatar->getHead()->getFaceModel().getTranslation();
|
||||
|
||||
// get the neck position so we can translate the face relative to it
|
||||
glm::vec3 neckPosition;
|
||||
_myAvatar->getSkeletonModel().setTranslation(glm::vec3());
|
||||
_myAvatar->getSkeletonModel().getNeckPosition(neckPosition);
|
||||
|
||||
// get the eye position relative to the body
|
||||
glm::vec3 eyePosition = _myAvatar->getHead()->getEyePosition();
|
||||
float eyeHeight = eyePosition.y - _myAvatar->getPosition().y;
|
||||
|
||||
// set the translation of the face relative to the neck position
|
||||
_myAvatar->getHead()->getFaceModel().setTranslation(neckPosition - glm::vec3(0, eyeHeight, 0));
|
||||
|
||||
// translate the neck relative to the face
|
||||
_myAvatar->getSkeletonModel().setTranslation(_myAvatar->getHead()->getFaceModel().getTranslation() -
|
||||
neckPosition);
|
||||
|
||||
// update the attachments to match
|
||||
QVector<glm::vec3> absoluteAttachmentTranslations;
|
||||
glm::vec3 delta = _myAvatar->getSkeletonModel().getTranslation() - absoluteSkeletonTranslation;
|
||||
foreach (Model* attachment, _myAvatar->getAttachmentModels()) {
|
||||
absoluteAttachmentTranslations.append(attachment->getTranslation());
|
||||
attachment->setTranslation(attachment->getTranslation() + delta);
|
||||
}
|
||||
|
||||
// and lo, even the shadow matrices
|
||||
glm::mat4 savedShadowMatrices[CASCADED_SHADOW_MATRIX_COUNT];
|
||||
for (int i = 0; i < CASCADED_SHADOW_MATRIX_COUNT; i++) {
|
||||
savedShadowMatrices[i] = _shadowMatrices[i];
|
||||
_shadowMatrices[i] = glm::transpose(glm::transpose(_shadowMatrices[i]) * glm::translate(-delta));
|
||||
}
|
||||
|
||||
displaySide(_mirrorCamera, true);
|
||||
|
||||
// restore absolute translations
|
||||
_myAvatar->getSkeletonModel().setTranslation(absoluteSkeletonTranslation);
|
||||
_myAvatar->getHead()->getFaceModel().setTranslation(absoluteFaceTranslation);
|
||||
for (int i = 0; i < absoluteAttachmentTranslations.size(); i++) {
|
||||
_myAvatar->getAttachmentModels().at(i)->setTranslation(absoluteAttachmentTranslations.at(i));
|
||||
}
|
||||
|
||||
// restore the shadow matrices
|
||||
for (int i = 0; i < CASCADED_SHADOW_MATRIX_COUNT; i++) {
|
||||
_shadowMatrices[i] = savedShadowMatrices[i];
|
||||
}
|
||||
} else {
|
||||
displaySide(_mirrorCamera, true);
|
||||
}
|
||||
displaySide(_mirrorCamera, true);
|
||||
glPopMatrix();
|
||||
|
||||
if (!billboard) {
|
||||
|
|
|
@ -216,6 +216,8 @@ public:
|
|||
float getFieldOfView() { return _fieldOfView.get(); }
|
||||
void setFieldOfView(float fov) { _fieldOfView.set(fov); }
|
||||
|
||||
void importSVOFromURL(QUrl url);
|
||||
|
||||
NodeToOctreeSceneStats* getOcteeSceneStats() { return &_octreeServerSceneStats; }
|
||||
void lockOctreeSceneStats() { _octreeSceneStatsLock.lockForRead(); }
|
||||
void unlockOctreeSceneStats() { _octreeSceneStatsLock.unlock(); }
|
||||
|
|
|
@ -170,7 +170,7 @@ void GLCanvas::wheelEvent(QWheelEvent* event) {
|
|||
void GLCanvas::dragEnterEvent(QDragEnterEvent* event) {
|
||||
const QMimeData *mimeData = event->mimeData();
|
||||
foreach (QUrl url, mimeData->urls()) {
|
||||
auto lower = url.url().toLower();
|
||||
auto lower = url.path().toLower();
|
||||
if (lower.endsWith(SNAPSHOT_EXTENSION) || lower.endsWith(SVO_EXTENSION)) {
|
||||
event->acceptProposedAction();
|
||||
break;
|
||||
|
|
|
@ -697,6 +697,12 @@ void Menu::removeAction(QMenu* menu, const QString& actionName) {
|
|||
}
|
||||
|
||||
void Menu::setIsOptionChecked(const QString& menuOption, bool isChecked) {
|
||||
if (thread() != QThread::currentThread()) {
|
||||
QMetaObject::invokeMethod(Menu::getInstance(), "setIsOptionChecked", Qt::BlockingQueuedConnection,
|
||||
Q_ARG(const QString&, menuOption),
|
||||
Q_ARG(bool, isChecked));
|
||||
return;
|
||||
}
|
||||
QAction* menu = _actionHash.value(menuOption);
|
||||
if (menu) {
|
||||
menu->setChecked(isChecked);
|
||||
|
|
|
@ -107,13 +107,16 @@ void TV3DManager::display(Camera& whichCamera) {
|
|||
const bool displayOverlays = Menu::getInstance()->isOptionChecked(MenuOption::UserInterface);
|
||||
|
||||
DependencyManager::get<GlowEffect>()->prepare();
|
||||
|
||||
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
|
||||
|
||||
glEnable(GL_SCISSOR_TEST);
|
||||
// render left side view
|
||||
glViewport(portalX, portalY, portalW, portalH);
|
||||
glScissor(portalX, portalY, portalW, portalH);
|
||||
|
||||
Camera eyeCamera;
|
||||
eyeCamera.setRotation(whichCamera.getRotation());
|
||||
eyeCamera.setPosition(whichCamera.getPosition());
|
||||
|
||||
glPushMatrix();
|
||||
{
|
||||
|
@ -129,7 +132,8 @@ void TV3DManager::display(Camera& whichCamera) {
|
|||
|
||||
glMatrixMode(GL_MODELVIEW);
|
||||
glLoadIdentity();
|
||||
Application::getInstance()->displaySide(whichCamera, false, RenderArgs::STEREO_LEFT);
|
||||
eyeCamera.setEyeOffsetPosition(glm::vec3(-_activeEye->modelTranslation,0,0));
|
||||
Application::getInstance()->displaySide(eyeCamera, false, RenderArgs::MONO);
|
||||
|
||||
if (displayOverlays) {
|
||||
applicationOverlay.displayOverlayTexture3DTV(whichCamera, _aspect, fov);
|
||||
|
@ -150,7 +154,7 @@ void TV3DManager::display(Camera& whichCamera) {
|
|||
_activeEye = &_rightEye;
|
||||
glMatrixMode(GL_PROJECTION);
|
||||
glLoadIdentity(); // reset projection matrix
|
||||
glFrustum(_rightEye.left, _rightEye.right, _rightEye.bottom, _rightEye.top, nearZ, farZ); // set left view frustum
|
||||
glFrustum(_rightEye.left, _rightEye.right, _rightEye.bottom, _rightEye.top, nearZ, farZ); // set right view frustum
|
||||
GLfloat p[4][4];
|
||||
glGetFloatv(GL_PROJECTION_MATRIX, &(p[0][0]));
|
||||
GLfloat cotangent = p[1][1];
|
||||
|
@ -159,7 +163,8 @@ void TV3DManager::display(Camera& whichCamera) {
|
|||
|
||||
glMatrixMode(GL_MODELVIEW);
|
||||
glLoadIdentity();
|
||||
Application::getInstance()->displaySide(whichCamera, false, RenderArgs::STEREO_RIGHT);
|
||||
eyeCamera.setEyeOffsetPosition(glm::vec3(-_activeEye->modelTranslation,0,0));
|
||||
Application::getInstance()->displaySide(eyeCamera, false, RenderArgs::MONO);
|
||||
|
||||
if (displayOverlays) {
|
||||
applicationOverlay.displayOverlayTexture3DTV(whichCamera, _aspect, fov);
|
||||
|
|
|
@ -18,8 +18,10 @@
|
|||
#include <QListWidget>
|
||||
|
||||
#include "Application.h"
|
||||
#include "WindowScriptingInterface.h"
|
||||
#include "ui/DataWebPage.h"
|
||||
#include "MainWindow.h"
|
||||
#include "WebWindowClass.h"
|
||||
#include "WindowScriptingInterface.h"
|
||||
|
||||
ScriptEventBridge::ScriptEventBridge(QObject* parent) : QObject(parent) {
|
||||
}
|
||||
|
@ -32,27 +34,47 @@ void ScriptEventBridge::emitScriptEvent(const QString& data) {
|
|||
emit scriptEventReceived(data);
|
||||
}
|
||||
|
||||
|
||||
WebWindowClass::WebWindowClass(const QString& title, const QString& url, int width, int height)
|
||||
WebWindowClass::WebWindowClass(const QString& title, const QString& url, int width, int height, bool isToolWindow)
|
||||
: QObject(NULL),
|
||||
_eventBridge(new ScriptEventBridge(this)) {
|
||||
_eventBridge(new ScriptEventBridge(this)),
|
||||
_isToolWindow(isToolWindow) {
|
||||
|
||||
ToolWindow* toolWindow = Application::getInstance()->getToolWindow();
|
||||
if (_isToolWindow) {
|
||||
ToolWindow* toolWindow = Application::getInstance()->getToolWindow();
|
||||
|
||||
_dockWidget = new QDockWidget(title, toolWindow);
|
||||
_dockWidget->setFeatures(QDockWidget::DockWidgetMovable);
|
||||
auto dockWidget = new QDockWidget(title, toolWindow);
|
||||
dockWidget->setFeatures(QDockWidget::DockWidgetMovable);
|
||||
|
||||
_webView = new QWebView(_dockWidget);
|
||||
_webView = new QWebView(dockWidget);
|
||||
addEventBridgeToWindowObject();
|
||||
|
||||
dockWidget->setWidget(_webView);
|
||||
|
||||
toolWindow->addDockWidget(Qt::RightDockWidgetArea, dockWidget);
|
||||
|
||||
_windowWidget = dockWidget;
|
||||
} else {
|
||||
_windowWidget = new QWidget(Application::getInstance()->getWindow(), Qt::Window);
|
||||
_windowWidget->setMinimumSize(width, height);
|
||||
|
||||
auto layout = new QVBoxLayout(_windowWidget);
|
||||
layout->setContentsMargins(0, 0, 0, 0);
|
||||
_windowWidget->setLayout(layout);
|
||||
|
||||
_webView = new QWebView(_windowWidget);
|
||||
|
||||
layout->addWidget(_webView);
|
||||
|
||||
addEventBridgeToWindowObject();
|
||||
}
|
||||
|
||||
_webView->setPage(new DataWebPage());
|
||||
_webView->setUrl(url);
|
||||
addEventBridgeToWindowObject();
|
||||
|
||||
_dockWidget->setWidget(_webView);
|
||||
|
||||
toolWindow->addDockWidget(Qt::RightDockWidgetArea, _dockWidget);
|
||||
|
||||
connect(this, &WebWindowClass::destroyed, _windowWidget, &QWidget::deleteLater);
|
||||
connect(_webView->page()->mainFrame(), &QWebFrame::javaScriptWindowObjectCleared,
|
||||
this, &WebWindowClass::addEventBridgeToWindowObject);
|
||||
connect(this, &WebWindowClass::destroyed, _dockWidget, &QWidget::deleteLater);
|
||||
}
|
||||
|
||||
WebWindowClass::~WebWindowClass() {
|
||||
|
@ -64,10 +86,14 @@ void WebWindowClass::addEventBridgeToWindowObject() {
|
|||
|
||||
void WebWindowClass::setVisible(bool visible) {
|
||||
if (visible) {
|
||||
QMetaObject::invokeMethod(
|
||||
Application::getInstance()->getToolWindow(), "setVisible", Qt::BlockingQueuedConnection, Q_ARG(bool, visible));
|
||||
if (_isToolWindow) {
|
||||
QMetaObject::invokeMethod(
|
||||
Application::getInstance()->getToolWindow(), "setVisible", Qt::BlockingQueuedConnection, Q_ARG(bool, visible));
|
||||
} else {
|
||||
QMetaObject::invokeMethod(_windowWidget, "raise", Qt::BlockingQueuedConnection);
|
||||
}
|
||||
}
|
||||
QMetaObject::invokeMethod(_dockWidget, "setVisible", Qt::BlockingQueuedConnection, Q_ARG(bool, visible));
|
||||
QMetaObject::invokeMethod(_windowWidget, "setVisible", Qt::BlockingQueuedConnection, Q_ARG(bool, visible));
|
||||
}
|
||||
|
||||
QScriptValue WebWindowClass::constructor(QScriptContext* context, QScriptEngine* engine) {
|
||||
|
@ -78,7 +104,8 @@ QScriptValue WebWindowClass::constructor(QScriptContext* context, QScriptEngine*
|
|||
Q_ARG(const QString&, file),
|
||||
Q_ARG(QString, context->argument(1).toString()),
|
||||
Q_ARG(int, context->argument(2).toInteger()),
|
||||
Q_ARG(int, context->argument(3).toInteger()));
|
||||
Q_ARG(int, context->argument(3).toInteger()),
|
||||
Q_ARG(bool, context->argument(4).toBool()));
|
||||
|
||||
connect(engine, &QScriptEngine::destroyed, retVal, &WebWindowClass::deleteLater);
|
||||
|
||||
|
|
|
@ -35,7 +35,7 @@ class WebWindowClass : public QObject {
|
|||
Q_OBJECT
|
||||
Q_PROPERTY(QObject* eventBridge READ getEventBridge)
|
||||
public:
|
||||
WebWindowClass(const QString& title, const QString& url, int width, int height);
|
||||
WebWindowClass(const QString& title, const QString& url, int width, int height, bool isToolWindow = false);
|
||||
~WebWindowClass();
|
||||
|
||||
static QScriptValue constructor(QScriptContext* context, QScriptEngine* engine);
|
||||
|
@ -46,9 +46,10 @@ public slots:
|
|||
void addEventBridgeToWindowObject();
|
||||
|
||||
private:
|
||||
QDockWidget* _dockWidget;
|
||||
QWidget* _windowWidget;
|
||||
QWebView* _webView;
|
||||
ScriptEventBridge* _eventBridge;
|
||||
bool _isToolWindow;
|
||||
};
|
||||
|
||||
#endif
|
||||
|
|
|
@ -35,8 +35,8 @@ WindowScriptingInterface::WindowScriptingInterface() :
|
|||
connect(Application::getInstance(), &Application::svoImportRequested, this, &WindowScriptingInterface::svoImportRequested);
|
||||
}
|
||||
|
||||
WebWindowClass* WindowScriptingInterface::doCreateWebWindow(const QString& title, const QString& url, int width, int height) {
|
||||
return new WebWindowClass(title, url, width, height);
|
||||
WebWindowClass* WindowScriptingInterface::doCreateWebWindow(const QString& title, const QString& url, int width, int height, bool isToolWindow) {
|
||||
return new WebWindowClass(title, url, width, height, isToolWindow);
|
||||
}
|
||||
|
||||
QScriptValue WindowScriptingInterface::hasFocus() {
|
||||
|
@ -44,8 +44,13 @@ QScriptValue WindowScriptingInterface::hasFocus() {
|
|||
}
|
||||
|
||||
void WindowScriptingInterface::setFocus() {
|
||||
Application::getInstance()->getWindow()->activateWindow();
|
||||
Application::getInstance()->getWindow()->setFocus();
|
||||
auto window = Application::getInstance()->getWindow();
|
||||
window->activateWindow();
|
||||
window->setFocus();
|
||||
}
|
||||
|
||||
void WindowScriptingInterface::raiseMainWindow() {
|
||||
Application::getInstance()->getWindow()->raise();
|
||||
}
|
||||
|
||||
void WindowScriptingInterface::setCursorVisible(bool visible) {
|
||||
|
|
|
@ -43,6 +43,7 @@ public slots:
|
|||
void setCursorVisible(bool visible);
|
||||
QScriptValue hasFocus();
|
||||
void setFocus();
|
||||
void raiseMainWindow();
|
||||
QScriptValue alert(const QString& message = "");
|
||||
QScriptValue confirm(const QString& message = "");
|
||||
QScriptValue form(const QString& title, QScriptValue array);
|
||||
|
@ -83,7 +84,7 @@ private slots:
|
|||
void nonBlockingFormAccepted() { _nonBlockingFormActive = false; _formResult = QDialog::Accepted; emit nonBlockingFormClosed(); }
|
||||
void nonBlockingFormRejected() { _nonBlockingFormActive = false; _formResult = QDialog::Rejected; emit nonBlockingFormClosed(); }
|
||||
|
||||
WebWindowClass* doCreateWebWindow(const QString& title, const QString& url, int width, int height);
|
||||
WebWindowClass* doCreateWebWindow(const QString& title, const QString& url, int width, int height, bool isToolWindow);
|
||||
|
||||
private:
|
||||
QString jsRegExp2QtRegExp(QString string);
|
||||
|
|
|
@ -11,6 +11,7 @@
|
|||
|
||||
#include <qnetworkrequest.h>
|
||||
|
||||
#include "Application.h"
|
||||
#include <AddressManager.h>
|
||||
#include <OAuthNetworkAccessManager.h>
|
||||
|
||||
|
@ -21,7 +22,7 @@ DataWebPage::DataWebPage(QObject* parent) :
|
|||
{
|
||||
// use an OAuthNetworkAccessManager instead of regular QNetworkAccessManager so our requests are authed
|
||||
setNetworkAccessManager(OAuthNetworkAccessManager::getInstance());
|
||||
|
||||
|
||||
// give the page an empty stylesheet
|
||||
settings()->setUserStyleSheetUrl(QUrl());
|
||||
}
|
||||
|
@ -31,8 +32,12 @@ void DataWebPage::javaScriptConsoleMessage(const QString& message, int lineNumbe
|
|||
}
|
||||
|
||||
bool DataWebPage::acceptNavigationRequest(QWebFrame* frame, const QNetworkRequest& request, QWebPage::NavigationType type) {
|
||||
|
||||
|
||||
if (!request.url().toString().startsWith(HIFI_URL_SCHEME)) {
|
||||
if (request.url().path().toLower().endsWith(SVO_EXTENSION)) {
|
||||
Application::getInstance()->importSVOFromURL(request.url());
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
} else {
|
||||
// this is a hifi URL - have the AddressManager handle it
|
||||
|
@ -40,4 +45,8 @@ bool DataWebPage::acceptNavigationRequest(QWebFrame* frame, const QNetworkReques
|
|||
Qt::AutoConnection, Q_ARG(const QString&, request.url().toString()));
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
QString DataWebPage::userAgentForUrl(const QUrl& url) const {
|
||||
return HIGH_FIDELITY_USER_AGENT;
|
||||
}
|
||||
|
|
|
@ -20,6 +20,7 @@ public:
|
|||
protected:
|
||||
void javaScriptConsoleMessage(const QString & message, int lineNumber, const QString & sourceID);
|
||||
bool acceptNavigationRequest(QWebFrame* frame, const QNetworkRequest& request, QWebPage::NavigationType type);
|
||||
virtual QString userAgentForUrl(const QUrl& url) const;
|
||||
};
|
||||
|
||||
#endif // hifi_DataWebPage_h
|
||||
#endif // hifi_DataWebPage_h
|
||||
|
|
|
@ -22,7 +22,9 @@ ToolWindow::ToolWindow(QWidget* parent) :
|
|||
_hasShown(false),
|
||||
_lastGeometry() {
|
||||
|
||||
# ifndef Q_OS_LINUX
|
||||
setDockOptions(QMainWindow::ForceTabbedDocks);
|
||||
# endif
|
||||
Application::getInstance()->installEventFilter(this);
|
||||
}
|
||||
|
||||
|
@ -53,6 +55,7 @@ bool ToolWindow::event(QEvent* event) {
|
|||
}
|
||||
|
||||
bool ToolWindow::eventFilter(QObject* sender, QEvent* event) {
|
||||
# ifndef Q_OS_LINUX
|
||||
switch (event->type()) {
|
||||
case QEvent::WindowStateChange:
|
||||
if (Application::getInstance()->getWindow()->isMinimized()) {
|
||||
|
@ -77,7 +80,7 @@ bool ToolWindow::eventFilter(QObject* sender, QEvent* event) {
|
|||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
# endif
|
||||
return false;
|
||||
}
|
||||
|
||||
|
|
|
@ -58,6 +58,7 @@ void BillboardOverlay::render(RenderArgs* args) {
|
|||
// rotate about vertical to face the camera
|
||||
rotation = Application::getInstance()->getCamera()->getRotation();
|
||||
rotation *= glm::angleAxis(glm::pi<float>(), glm::vec3(0.0f, 1.0f, 0.0f));
|
||||
rotation *= getRotation();
|
||||
} else {
|
||||
rotation = getRotation();
|
||||
}
|
||||
|
|
|
@ -35,6 +35,25 @@ bool EntityScriptingInterface::canAdjustLocks() {
|
|||
}
|
||||
|
||||
|
||||
void EntityScriptingInterface::setEntityTree(EntityTree* modelTree) {
|
||||
if (_entityTree) {
|
||||
disconnect(_entityTree, &EntityTree::addingEntity, this, &EntityScriptingInterface::addingEntity);
|
||||
disconnect(_entityTree, &EntityTree::deletingEntity, this, &EntityScriptingInterface::deletingEntity);
|
||||
disconnect(_entityTree, &EntityTree::changingEntityID, this, &EntityScriptingInterface::changingEntityID);
|
||||
disconnect(_entityTree, &EntityTree::clearingEntities, this, &EntityScriptingInterface::clearingEntities);
|
||||
}
|
||||
|
||||
_entityTree = modelTree;
|
||||
|
||||
if (_entityTree) {
|
||||
connect(_entityTree, &EntityTree::addingEntity, this, &EntityScriptingInterface::addingEntity);
|
||||
connect(_entityTree, &EntityTree::deletingEntity, this, &EntityScriptingInterface::deletingEntity);
|
||||
connect(_entityTree, &EntityTree::changingEntityID, this, &EntityScriptingInterface::changingEntityID);
|
||||
connect(_entityTree, &EntityTree::clearingEntities, this, &EntityScriptingInterface::clearingEntities);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
EntityItemID EntityScriptingInterface::addEntity(const EntityItemProperties& properties) {
|
||||
|
||||
// The application will keep track of creatorTokenID
|
||||
|
@ -185,8 +204,7 @@ EntityItemID EntityScriptingInterface::findClosestEntity(const glm::vec3& center
|
|||
const EntityItem* closestEntity = _entityTree->findClosestEntity(center, radius);
|
||||
_entityTree->unlock();
|
||||
if (closestEntity) {
|
||||
result.id = closestEntity->getID();
|
||||
result.isKnownID = true;
|
||||
result = closestEntity->getEntityItemID();
|
||||
}
|
||||
}
|
||||
return result;
|
||||
|
@ -208,10 +226,25 @@ QVector<EntityItemID> EntityScriptingInterface::findEntities(const glm::vec3& ce
|
|||
QVector<const EntityItem*> entities;
|
||||
_entityTree->findEntities(center, radius, entities);
|
||||
_entityTree->unlock();
|
||||
|
||||
|
||||
foreach (const EntityItem* entity, entities) {
|
||||
EntityItemID thisEntityItemID(entity->getID(), UNKNOWN_ENTITY_TOKEN, true);
|
||||
result << thisEntityItemID;
|
||||
result << entity->getEntityItemID();
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
QVector<EntityItemID> EntityScriptingInterface::findEntitiesInBox(const glm::vec3& corner, const glm::vec3& dimensions) const {
|
||||
QVector<EntityItemID> result;
|
||||
if (_entityTree) {
|
||||
_entityTree->lockForRead();
|
||||
AABox box(corner, dimensions);
|
||||
QVector<EntityItem*> entities;
|
||||
_entityTree->findEntities(box, entities);
|
||||
_entityTree->unlock();
|
||||
|
||||
foreach (const EntityItem* entity, entities) {
|
||||
result << entity->getEntityItemID();
|
||||
}
|
||||
}
|
||||
return result;
|
||||
|
|
|
@ -58,7 +58,7 @@ public:
|
|||
virtual NodeType_t getServerNodeType() const { return NodeType::EntityServer; }
|
||||
virtual OctreeEditPacketSender* createPacketSender() { return new EntityEditPacketSender(); }
|
||||
|
||||
void setEntityTree(EntityTree* modelTree) { _entityTree = modelTree; }
|
||||
void setEntityTree(EntityTree* modelTree);
|
||||
EntityTree* getEntityTree(EntityTree*) { return _entityTree; }
|
||||
|
||||
public slots:
|
||||
|
@ -87,10 +87,14 @@ public slots:
|
|||
/// will return a EntityItemID.isKnownID = false if no models are in the radius
|
||||
/// this function will not find any models in script engine contexts which don't have access to models
|
||||
Q_INVOKABLE EntityItemID findClosestEntity(const glm::vec3& center, float radius) const;
|
||||
|
||||
|
||||
/// finds models within the search sphere specified by the center point and radius
|
||||
/// this function will not find any models in script engine contexts which don't have access to models
|
||||
Q_INVOKABLE QVector<EntityItemID> findEntities(const glm::vec3& center, float radius) const;
|
||||
|
||||
/// finds models within the search sphere specified by the center point and radius
|
||||
/// this function will not find any models in script engine contexts which don't have access to models
|
||||
Q_INVOKABLE QVector<EntityItemID> findEntitiesInBox(const glm::vec3& corner, const glm::vec3& dimensions) const;
|
||||
|
||||
/// If the scripting context has visible entities, this will determine a ray intersection, the results
|
||||
/// may be inaccurate if the engine is unable to access the visible entities, in which case result.accurate
|
||||
|
@ -129,6 +133,11 @@ signals:
|
|||
void enterEntity(const EntityItemID& entityItemID);
|
||||
void leaveEntity(const EntityItemID& entityItemID);
|
||||
|
||||
void deletingEntity(const EntityItemID& entityID);
|
||||
void addingEntity(const EntityItemID& entityID);
|
||||
void changingEntityID(const EntityItemID& oldEntityID, const EntityItemID& newEntityID);
|
||||
void clearingEntities();
|
||||
|
||||
private:
|
||||
void queueEntityMessage(PacketType packetType, EntityItemID entityID, const EntityItemProperties& properties);
|
||||
|
||||
|
|
|
@ -37,6 +37,8 @@ EntityTreeElement* EntityTree::createNewElement(unsigned char * octalCode) {
|
|||
}
|
||||
|
||||
void EntityTree::eraseAllOctreeElements(bool createNewRoot) {
|
||||
emit clearingEntities();
|
||||
|
||||
// this would be a good place to clean up our entities...
|
||||
if (_simulation) {
|
||||
_simulation->lock();
|
||||
|
@ -234,21 +236,28 @@ void EntityTree::setSimulation(EntitySimulation* simulation) {
|
|||
_simulation = simulation;
|
||||
}
|
||||
|
||||
void EntityTree::deleteEntity(const EntityItemID& entityID, bool force) {
|
||||
void EntityTree::deleteEntity(const EntityItemID& entityID, bool force, bool ignoreWarnings) {
|
||||
EntityTreeElement* containingElement = getContainingElement(entityID);
|
||||
if (!containingElement) {
|
||||
qDebug() << "UNEXPECTED!!!! EntityTree::deleteEntity() entityID doesn't exist!!! entityID=" << entityID;
|
||||
if (!ignoreWarnings) {
|
||||
qDebug() << "UNEXPECTED!!!! EntityTree::deleteEntity() entityID doesn't exist!!! entityID=" << entityID;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
EntityItem* existingEntity = containingElement->getEntityWithEntityItemID(entityID);
|
||||
if (!existingEntity) {
|
||||
qDebug() << "UNEXPECTED!!!! don't call EntityTree::deleteEntity() on entity items that don't exist. entityID=" << entityID;
|
||||
if (!ignoreWarnings) {
|
||||
qDebug() << "UNEXPECTED!!!! don't call EntityTree::deleteEntity() on entity items that don't exist. "
|
||||
"entityID=" << entityID;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (existingEntity->getLocked() && !force) {
|
||||
qDebug() << "ERROR! EntityTree::deleteEntity() trying to delete locked entity. entityID=" << entityID;
|
||||
if (!ignoreWarnings) {
|
||||
qDebug() << "ERROR! EntityTree::deleteEntity() trying to delete locked entity. entityID=" << entityID;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -261,24 +270,31 @@ void EntityTree::deleteEntity(const EntityItemID& entityID, bool force) {
|
|||
_isDirty = true;
|
||||
}
|
||||
|
||||
void EntityTree::deleteEntities(QSet<EntityItemID> entityIDs, bool force) {
|
||||
void EntityTree::deleteEntities(QSet<EntityItemID> entityIDs, bool force, bool ignoreWarnings) {
|
||||
// NOTE: callers must lock the tree before using this method
|
||||
DeleteEntityOperator theOperator(this);
|
||||
foreach(const EntityItemID& entityID, entityIDs) {
|
||||
EntityTreeElement* containingElement = getContainingElement(entityID);
|
||||
if (!containingElement) {
|
||||
qDebug() << "UNEXPECTED!!!! EntityTree::deleteEntities() entityID doesn't exist!!! entityID=" << entityID;
|
||||
if (!ignoreWarnings) {
|
||||
qDebug() << "UNEXPECTED!!!! EntityTree::deleteEntities() entityID doesn't exist!!! entityID=" << entityID;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
EntityItem* existingEntity = containingElement->getEntityWithEntityItemID(entityID);
|
||||
if (!existingEntity) {
|
||||
qDebug() << "UNEXPECTED!!!! don't call EntityTree::deleteEntities() on entity items that don't exist. entityID=" << entityID;
|
||||
if (!ignoreWarnings) {
|
||||
qDebug() << "UNEXPECTED!!!! don't call EntityTree::deleteEntities() on entity items that don't exist. "
|
||||
"entityID=" << entityID;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
if (existingEntity->getLocked() && !force) {
|
||||
qDebug() << "ERROR! EntityTree::deleteEntities() trying to delete locked entity. entityID=" << entityID;
|
||||
if (!ignoreWarnings) {
|
||||
qDebug() << "ERROR! EntityTree::deleteEntities() trying to delete locked entity. entityID=" << entityID;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
|
@ -526,6 +542,35 @@ void EntityTree::findEntities(const AACube& cube, QVector<EntityItem*>& foundEnt
|
|||
foundEntities.swap(args._foundEntities);
|
||||
}
|
||||
|
||||
class FindEntitiesInBoxArgs {
|
||||
public:
|
||||
FindEntitiesInBoxArgs(const AABox& box)
|
||||
: _box(box), _foundEntities() {
|
||||
}
|
||||
|
||||
AABox _box;
|
||||
QVector<EntityItem*> _foundEntities;
|
||||
};
|
||||
|
||||
bool EntityTree::findInBoxOperation(OctreeElement* element, void* extraData) {
|
||||
FindEntitiesInBoxArgs* args = static_cast<FindEntitiesInBoxArgs*>(extraData);
|
||||
if (element->getAACube().touches(args->_box)) {
|
||||
EntityTreeElement* entityTreeElement = static_cast<EntityTreeElement*>(element);
|
||||
entityTreeElement->getEntities(args->_box, args->_foundEntities);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// NOTE: assumes caller has handled locking
|
||||
void EntityTree::findEntities(const AABox& box, QVector<EntityItem*>& foundEntities) {
|
||||
FindEntitiesInBoxArgs args(box);
|
||||
// NOTE: This should use recursion, since this is a spatial operation
|
||||
recurseTreeWithOperation(findInBoxOperation, &args);
|
||||
// swap the two lists of entity pointers instead of copy
|
||||
foundEntities.swap(args._foundEntities);
|
||||
}
|
||||
|
||||
EntityItem* EntityTree::findEntityByID(const QUuid& id) {
|
||||
EntityItemID entityID(id);
|
||||
return findEntityByEntityItemID(entityID);
|
||||
|
@ -847,10 +892,9 @@ int EntityTree::processEraseMessage(const QByteArray& dataByteArray, const Share
|
|||
EntityItemID entityItemID(entityID);
|
||||
entityItemIDsToDelete << entityItemID;
|
||||
}
|
||||
deleteEntities(entityItemIDsToDelete);
|
||||
deleteEntities(entityItemIDsToDelete, true, true);
|
||||
}
|
||||
unlock();
|
||||
|
||||
return processedBytes;
|
||||
}
|
||||
|
||||
|
@ -887,7 +931,7 @@ int EntityTree::processEraseMessageDetails(const QByteArray& dataByteArray, cons
|
|||
EntityItemID entityItemID(entityID);
|
||||
entityItemIDsToDelete << entityItemID;
|
||||
}
|
||||
deleteEntities(entityItemIDsToDelete);
|
||||
deleteEntities(entityItemIDsToDelete, true, true);
|
||||
}
|
||||
return processedBytes;
|
||||
}
|
||||
|
|
|
@ -92,8 +92,8 @@ public:
|
|||
// use this method if you have a pointer to the entity (avoid an extra entity lookup)
|
||||
bool updateEntity(EntityItem* entity, const EntityItemProperties& properties, bool allowLockChange);
|
||||
|
||||
void deleteEntity(const EntityItemID& entityID, bool force = false);
|
||||
void deleteEntities(QSet<EntityItemID> entityIDs, bool force = false);
|
||||
void deleteEntity(const EntityItemID& entityID, bool force = false, bool ignoreWarnings = false);
|
||||
void deleteEntities(QSet<EntityItemID> entityIDs, bool force = false, bool ignoreWarnings = false);
|
||||
void removeEntityFromSimulation(EntityItem* entity);
|
||||
|
||||
/// \param position point of query in world-frame (meters)
|
||||
|
@ -111,12 +111,18 @@ public:
|
|||
/// \param foundEntities[out] vector of const EntityItem*
|
||||
/// \remark Side effect: any initial contents in foundEntities will be lost
|
||||
void findEntities(const glm::vec3& center, float radius, QVector<const EntityItem*>& foundEntities);
|
||||
|
||||
|
||||
/// finds all entities that touch a cube
|
||||
/// \param cube the query cube in world-frame (meters)
|
||||
/// \param foundEntities[out] vector of non-const EntityItem*
|
||||
/// \remark Side effect: any initial contents in entities will be lost
|
||||
void findEntities(const AACube& cube, QVector<EntityItem*>& foundEntities);
|
||||
|
||||
/// finds all entities that touch a box
|
||||
/// \param box the query box in world-frame (meters)
|
||||
/// \param foundEntities[out] vector of non-const EntityItem*
|
||||
/// \remark Side effect: any initial contents in entities will be lost
|
||||
void findEntities(const AABox& box, QVector<EntityItem*>& foundEntities);
|
||||
|
||||
void addNewlyCreatedHook(NewlyCreatedEntityHook* hook);
|
||||
void removeNewlyCreatedHook(NewlyCreatedEntityHook* hook);
|
||||
|
@ -163,6 +169,7 @@ signals:
|
|||
void addingEntity(const EntityItemID& entityID);
|
||||
void entityScriptChanging(const EntityItemID& entityItemID);
|
||||
void changingEntityID(const EntityItemID& oldEntityID, const EntityItemID& newEntityID);
|
||||
void clearingEntities();
|
||||
|
||||
private:
|
||||
|
||||
|
@ -172,6 +179,7 @@ private:
|
|||
static bool findNearPointOperation(OctreeElement* element, void* extraData);
|
||||
static bool findInSphereOperation(OctreeElement* element, void* extraData);
|
||||
static bool findInCubeOperation(OctreeElement* element, void* extraData);
|
||||
static bool findInBoxOperation(OctreeElement* element, void* extraData);
|
||||
static bool sendEntitiesOperation(OctreeElement* element, void* extraData);
|
||||
|
||||
void notifyNewlyCreatedEntity(const EntityItem& newEntity, const SharedNodePointer& senderNode);
|
||||
|
|
|
@ -111,6 +111,8 @@ int LightEntityItem::readEntitySubclassDataFromBuffer(const unsigned char* data,
|
|||
|
||||
READ_ENTITY_PROPERTY(PROP_EXPONENT, float, _exponent);
|
||||
READ_ENTITY_PROPERTY(PROP_CUTOFF, float, _cutoff);
|
||||
|
||||
(void) ignoredAttenuation; // suppress compiler warning
|
||||
} else {
|
||||
READ_ENTITY_PROPERTY(PROP_IS_SPOTLIGHT, bool, _isSpotlight);
|
||||
READ_ENTITY_PROPERTY_COLOR(PROP_COLOR, _color);
|
||||
|
|
|
@ -31,6 +31,7 @@ public:
|
|||
};
|
||||
int nextToken();
|
||||
const QByteArray& getDatum() const { return _datum; }
|
||||
bool isNextTokenFloat();
|
||||
void skipLine() { _device->readLine(); }
|
||||
void pushBackToken(int token) { _pushedBackToken = token; }
|
||||
void ungetChar(char ch) { _device->ungetChar(ch); }
|
||||
|
@ -91,14 +92,32 @@ int OBJTokenizer::nextToken() {
|
|||
return NO_TOKEN;
|
||||
}
|
||||
|
||||
bool OBJTokenizer::isNextTokenFloat() {
|
||||
if (nextToken() != OBJTokenizer::DATUM_TOKEN) {
|
||||
return false;
|
||||
}
|
||||
QByteArray token = getDatum();
|
||||
pushBackToken(OBJTokenizer::DATUM_TOKEN);
|
||||
bool ok;
|
||||
token.toFloat(&ok);
|
||||
return ok;
|
||||
}
|
||||
|
||||
bool parseOBJGroup(OBJTokenizer &tokenizer, const QVariantHash& mapping, FBXGeometry &geometry) {
|
||||
bool parseOBJGroup(OBJTokenizer &tokenizer, const QVariantHash& mapping,
|
||||
FBXGeometry &geometry, QVector<glm::vec3>& faceNormals, QVector<int>& faceNormalIndexes) {
|
||||
FBXMesh &mesh = geometry.meshes[0];
|
||||
mesh.parts.append(FBXMeshPart());
|
||||
FBXMeshPart &meshPart = mesh.parts.last();
|
||||
bool sawG = false;
|
||||
bool result = true;
|
||||
|
||||
meshPart.diffuseColor = glm::vec3(1, 1, 1);
|
||||
meshPart.specularColor = glm::vec3(1, 1, 1);
|
||||
meshPart.emissiveColor = glm::vec3(0, 0, 0);
|
||||
meshPart.emissiveParams = glm::vec2(0, 1);
|
||||
meshPart.shininess = 40;
|
||||
meshPart.opacity = 1;
|
||||
|
||||
meshPart.materialID = QString("dontknow") + QString::number(mesh.parts.count());
|
||||
meshPart.opacity = 1.0;
|
||||
meshPart._material = model::MaterialPointer(new model::Material());
|
||||
|
@ -131,11 +150,27 @@ bool parseOBJGroup(OBJTokenizer &tokenizer, const QVariantHash& mapping, FBXGeom
|
|||
break;
|
||||
}
|
||||
float x = std::stof(tokenizer.getDatum().data());
|
||||
// notice the order of z and y -- in OBJ files, up is the 3rd value
|
||||
|
||||
if (tokenizer.nextToken() != OBJTokenizer::DATUM_TOKEN) {
|
||||
break;
|
||||
}
|
||||
float y = std::stof(tokenizer.getDatum().data());
|
||||
|
||||
if (tokenizer.nextToken() != OBJTokenizer::DATUM_TOKEN) {
|
||||
break;
|
||||
}
|
||||
float z = std::stof(tokenizer.getDatum().data());
|
||||
|
||||
while (tokenizer.isNextTokenFloat()) {
|
||||
// the spec(s) get(s) vague here. might be w, might be a color... chop it off.
|
||||
tokenizer.nextToken();
|
||||
}
|
||||
mesh.vertices.append(glm::vec3(x, y, z));
|
||||
} else if (token == "vn") {
|
||||
if (tokenizer.nextToken() != OBJTokenizer::DATUM_TOKEN) {
|
||||
break;
|
||||
}
|
||||
float x = std::stof(tokenizer.getDatum().data());
|
||||
if (tokenizer.nextToken() != OBJTokenizer::DATUM_TOKEN) {
|
||||
break;
|
||||
}
|
||||
|
@ -143,35 +178,74 @@ bool parseOBJGroup(OBJTokenizer &tokenizer, const QVariantHash& mapping, FBXGeom
|
|||
if (tokenizer.nextToken() != OBJTokenizer::DATUM_TOKEN) {
|
||||
break;
|
||||
}
|
||||
// the spec gets vague here. might be w, might be a color... chop it off.
|
||||
tokenizer.skipLine();
|
||||
mesh.vertices.append(glm::vec3(x, y, z));
|
||||
mesh.colors.append(glm::vec3(1, 1, 1));
|
||||
float z = std::stof(tokenizer.getDatum().data());
|
||||
|
||||
while (tokenizer.isNextTokenFloat()) {
|
||||
// the spec gets vague here. might be w
|
||||
tokenizer.nextToken();
|
||||
}
|
||||
faceNormals.append(glm::vec3(x, y, z));
|
||||
} else if (token == "f") {
|
||||
// a face can have 3 or more vertices
|
||||
QVector<int> indices;
|
||||
QVector<int> normalIndices;
|
||||
while (true) {
|
||||
if (tokenizer.nextToken() != OBJTokenizer::DATUM_TOKEN) { goto done; }
|
||||
try {
|
||||
int vertexIndex = std::stoi(tokenizer.getDatum().data());
|
||||
// negative indexes count backward from the current end of the vertex list
|
||||
vertexIndex = (vertexIndex >= 0 ? vertexIndex : mesh.vertices.count() + vertexIndex + 1);
|
||||
// obj index is 1 based
|
||||
assert(vertexIndex >= 1);
|
||||
indices.append(vertexIndex - 1);
|
||||
if (tokenizer.nextToken() != OBJTokenizer::DATUM_TOKEN) {
|
||||
if (indices.count() == 0) {
|
||||
goto done;
|
||||
}
|
||||
break;
|
||||
}
|
||||
catch(const std::exception& e) {
|
||||
// wasn't a number, but it back.
|
||||
// faces can be:
|
||||
// vertex-index
|
||||
// vertex-index/texture-index
|
||||
// vertex-index/texture-index/surface-normal-index
|
||||
|
||||
QByteArray token = tokenizer.getDatum();
|
||||
QList<QByteArray> parts = token.split('/');
|
||||
assert(parts.count() >= 1);
|
||||
assert(parts.count() <= 3);
|
||||
QByteArray vertIndexBA = parts[ 0 ];
|
||||
|
||||
bool ok;
|
||||
int vertexIndex = vertIndexBA.toInt(&ok);
|
||||
if (!ok) {
|
||||
// it wasn't #/#/#, put it back and exit this loop.
|
||||
tokenizer.pushBackToken(OBJTokenizer::DATUM_TOKEN);
|
||||
break;
|
||||
}
|
||||
|
||||
// if (parts.count() > 1) {
|
||||
// QByteArray textureIndexBA = parts[ 1 ];
|
||||
// }
|
||||
|
||||
if (parts.count() > 2) {
|
||||
QByteArray normalIndexBA = parts[ 2 ];
|
||||
bool ok;
|
||||
int normalIndex = normalIndexBA.toInt(&ok);
|
||||
if (ok) {
|
||||
normalIndices.append(normalIndex - 1);
|
||||
}
|
||||
}
|
||||
|
||||
// negative indexes count backward from the current end of the vertex list
|
||||
vertexIndex = (vertexIndex >= 0 ? vertexIndex : mesh.vertices.count() + vertexIndex + 1);
|
||||
// obj index is 1 based
|
||||
assert(vertexIndex >= 1);
|
||||
indices.append(vertexIndex - 1);
|
||||
}
|
||||
|
||||
if (indices.count() == 3) {
|
||||
// flip these around (because of the y/z swap above) so our triangles face outward
|
||||
meshPart.triangleIndices.append(indices[0]);
|
||||
meshPart.triangleIndices.append(indices[2]);
|
||||
meshPart.triangleIndices.append(indices[1]);
|
||||
meshPart.triangleIndices.append(indices[2]);
|
||||
if (normalIndices.count() == 3) {
|
||||
faceNormalIndexes.append(normalIndices[0]);
|
||||
faceNormalIndexes.append(normalIndices[1]);
|
||||
faceNormalIndexes.append(normalIndices[2]);
|
||||
} else {
|
||||
// hmm.
|
||||
}
|
||||
} else if (indices.count() == 4) {
|
||||
meshPart.quadIndices << indices;
|
||||
} else {
|
||||
|
@ -205,6 +279,10 @@ FBXGeometry readOBJ(const QByteArray& model, const QVariantHash& mapping) {
|
|||
FBXGeometry readOBJ(QIODevice* device, const QVariantHash& mapping) {
|
||||
FBXGeometry geometry;
|
||||
OBJTokenizer tokenizer(device);
|
||||
QVector<int> faceNormalIndexes;
|
||||
QVector<glm::vec3> faceNormals;
|
||||
|
||||
faceNormalIndexes.clear();
|
||||
|
||||
geometry.meshExtents.reset();
|
||||
geometry.meshes.append(FBXMesh());
|
||||
|
@ -216,7 +294,7 @@ FBXGeometry readOBJ(QIODevice* device, const QVariantHash& mapping) {
|
|||
// add a new meshPart to the geometry's single mesh.
|
||||
bool success = true;
|
||||
while (success) {
|
||||
success = parseOBJGroup(tokenizer, mapping, geometry);
|
||||
success = parseOBJGroup(tokenizer, mapping, geometry, faceNormals, faceNormalIndexes);
|
||||
}
|
||||
|
||||
FBXMesh &mesh = geometry.meshes[0];
|
||||
|
@ -229,31 +307,34 @@ FBXGeometry readOBJ(QIODevice* device, const QVariantHash& mapping) {
|
|||
|
||||
geometry.joints.resize(1);
|
||||
geometry.joints[0].isFree = false;
|
||||
// geometry.joints[0].freeLineage;
|
||||
geometry.joints[0].parentIndex = -1;
|
||||
geometry.joints[0].distanceToParent = 0;
|
||||
geometry.joints[0].boneRadius = 0;
|
||||
geometry.joints[0].translation = glm::vec3(0, 0, 0);
|
||||
// geometry.joints[0].preTransform = ;
|
||||
geometry.joints[0].preRotation = glm::quat(0, 0, 0, 1);
|
||||
geometry.joints[0].rotation = glm::quat(0, 0, 0, 1);
|
||||
geometry.joints[0].postRotation = glm::quat(0, 0, 0, 1);
|
||||
// geometry.joints[0].postTransform = ;
|
||||
// geometry.joints[0].transform = ;
|
||||
geometry.joints[0].rotationMin = glm::vec3(0, 0, 0);
|
||||
geometry.joints[0].rotationMax = glm::vec3(0, 0, 0);
|
||||
geometry.joints[0].inverseDefaultRotation = glm::quat(0, 0, 0, 1);
|
||||
geometry.joints[0].inverseBindRotation = glm::quat(0, 0, 0, 1);
|
||||
// geometry.joints[0].bindTransform = ;
|
||||
geometry.joints[0].name = "OBJ";
|
||||
geometry.joints[0].shapePosition = glm::vec3(0, 0, 0);
|
||||
geometry.joints[0].shapeRotation = glm::quat(0, 0, 0, 1);
|
||||
geometry.joints[0].shapeType = SPHERE_SHAPE;
|
||||
geometry.joints[0].isSkeletonJoint = false;
|
||||
geometry.joints[0].isSkeletonJoint = true;
|
||||
|
||||
geometry.jointIndices["x"] = 1;
|
||||
|
||||
FBXCluster cluster;
|
||||
cluster.jointIndex = 0;
|
||||
cluster.inverseBindMatrix = glm::mat4(1, 0, 0, 0,
|
||||
0, 1, 0, 0,
|
||||
0, 0, 1, 0,
|
||||
0, 0, 0, 1);
|
||||
mesh.clusters.append(cluster);
|
||||
|
||||
// The OBJ format has normals for faces. The FBXGeometry structure has normals for points.
|
||||
// run through all the faces, look-up (or determine) a normal and set the normal for the points
|
||||
// that make up each face.
|
||||
QVector<glm::vec3> pointNormalsSums;
|
||||
|
||||
// add bogus normal data for this mesh
|
||||
mesh.normals.fill(glm::vec3(0,0,0), mesh.vertices.count());
|
||||
mesh.tangents.fill(glm::vec3(0,0,0), mesh.vertices.count());
|
||||
pointNormalsSums.fill(glm::vec3(0,0,0), mesh.vertices.count());
|
||||
|
||||
foreach (FBXMeshPart meshPart, mesh.parts) {
|
||||
int triCount = meshPart.triangleIndices.count() / 3;
|
||||
|
@ -262,21 +343,43 @@ FBXGeometry readOBJ(QIODevice* device, const QVariantHash& mapping) {
|
|||
int p1Index = meshPart.triangleIndices[i*3+1];
|
||||
int p2Index = meshPart.triangleIndices[i*3+2];
|
||||
|
||||
glm::vec3 p0 = mesh.vertices[p0Index];
|
||||
glm::vec3 p1 = mesh.vertices[p1Index];
|
||||
glm::vec3 p2 = mesh.vertices[p2Index];
|
||||
assert(p0Index < mesh.vertices.count());
|
||||
assert(p1Index < mesh.vertices.count());
|
||||
assert(p2Index < mesh.vertices.count());
|
||||
|
||||
glm::vec3 n = glm::cross(p1 - p0, p2 - p0);
|
||||
glm::vec3 t = glm::cross(p2 - p0, n);
|
||||
glm::vec3 n0, n1, n2;
|
||||
if (i < faceNormalIndexes.count()) {
|
||||
int n0Index = faceNormalIndexes[i*3];
|
||||
int n1Index = faceNormalIndexes[i*3+1];
|
||||
int n2Index = faceNormalIndexes[i*3+2];
|
||||
n0 = faceNormals[n0Index];
|
||||
n1 = faceNormals[n1Index];
|
||||
n2 = faceNormals[n2Index];
|
||||
} else {
|
||||
// We didn't read normals, add bogus normal data for this face
|
||||
glm::vec3 p0 = mesh.vertices[p0Index];
|
||||
glm::vec3 p1 = mesh.vertices[p1Index];
|
||||
glm::vec3 p2 = mesh.vertices[p2Index];
|
||||
n0 = glm::cross(p1 - p0, p2 - p0);
|
||||
n1 = n0;
|
||||
n2 = n0;
|
||||
}
|
||||
|
||||
mesh.normals[p0Index] = n;
|
||||
mesh.normals[p1Index] = n;
|
||||
mesh.normals[p2Index] = n;
|
||||
|
||||
mesh.tangents[p0Index] = t;
|
||||
mesh.tangents[p1Index] = t;
|
||||
mesh.tangents[p2Index] = t;
|
||||
// we sum up the normal for each point and then divide by the count to get an average
|
||||
pointNormalsSums[p0Index] += n0;
|
||||
pointNormalsSums[p1Index] += n1;
|
||||
pointNormalsSums[p2Index] += n2;
|
||||
}
|
||||
|
||||
int vertCount = mesh.vertices.count();
|
||||
for (int i = 0; i < vertCount; i++) {
|
||||
float length = glm::length(pointNormalsSums[i]);
|
||||
if (length > FLT_EPSILON) {
|
||||
mesh.normals[i] = glm::normalize(pointNormalsSums[i]);
|
||||
}
|
||||
}
|
||||
|
||||
// XXX do same normal calculation for quadCount
|
||||
}
|
||||
}
|
||||
catch(const std::exception& e) {
|
||||
|
@ -285,3 +388,77 @@ FBXGeometry readOBJ(QIODevice* device, const QVariantHash& mapping) {
|
|||
|
||||
return geometry;
|
||||
}
|
||||
|
||||
|
||||
|
||||
void fbxDebugDump(const FBXGeometry& fbxgeo) {
|
||||
qDebug() << "---------------- fbxGeometry ----------------";
|
||||
qDebug() << " hasSkeletonJoints =" << fbxgeo.hasSkeletonJoints;
|
||||
qDebug() << " offset =" << fbxgeo.offset;
|
||||
qDebug() << " attachments.count() = " << fbxgeo.attachments.count();
|
||||
qDebug() << " meshes.count() =" << fbxgeo.meshes.count();
|
||||
foreach (FBXMesh mesh, fbxgeo.meshes) {
|
||||
qDebug() << " vertices.count() =" << mesh.vertices.count();
|
||||
qDebug() << " normals.count() =" << mesh.normals.count();
|
||||
if (mesh.normals.count() == mesh.vertices.count()) {
|
||||
for (int i = 0; i < mesh.normals.count(); i++) {
|
||||
qDebug() << " " << mesh.vertices[ i ] << mesh.normals[ i ];
|
||||
}
|
||||
}
|
||||
qDebug() << " tangents.count() =" << mesh.tangents.count();
|
||||
qDebug() << " colors.count() =" << mesh.colors.count();
|
||||
qDebug() << " texCoords.count() =" << mesh.texCoords.count();
|
||||
qDebug() << " texCoords1.count() =" << mesh.texCoords1.count();
|
||||
qDebug() << " clusterIndices.count() =" << mesh.clusterIndices.count();
|
||||
qDebug() << " clusterWeights.count() =" << mesh.clusterWeights.count();
|
||||
qDebug() << " meshExtents =" << mesh.meshExtents;
|
||||
qDebug() << " modelTransform =" << mesh.modelTransform;
|
||||
qDebug() << " parts.count() =" << mesh.parts.count();
|
||||
foreach (FBXMeshPart meshPart, mesh.parts) {
|
||||
qDebug() << " quadIndices.count() =" << meshPart.quadIndices.count();
|
||||
qDebug() << " triangleIndices.count() =" << meshPart.triangleIndices.count();
|
||||
qDebug() << " diffuseColor =" << meshPart.diffuseColor;
|
||||
qDebug() << " specularColor =" << meshPart.specularColor;
|
||||
qDebug() << " emissiveColor =" << meshPart.emissiveColor;
|
||||
qDebug() << " emissiveParams =" << meshPart.emissiveParams;
|
||||
qDebug() << " shininess =" << meshPart.shininess;
|
||||
qDebug() << " opacity =" << meshPart.opacity;
|
||||
qDebug() << " materialID =" << meshPart.materialID;
|
||||
}
|
||||
qDebug() << " clusters.count() =" << mesh.clusters.count();
|
||||
foreach (FBXCluster cluster, mesh.clusters) {
|
||||
qDebug() << " jointIndex =" << cluster.jointIndex;
|
||||
qDebug() << " inverseBindMatrix =" << cluster.inverseBindMatrix;
|
||||
}
|
||||
}
|
||||
|
||||
qDebug() << " jointIndices =" << fbxgeo.jointIndices;
|
||||
qDebug() << " joints.count() =" << fbxgeo.joints.count();
|
||||
|
||||
foreach (FBXJoint joint, fbxgeo.joints) {
|
||||
qDebug() << " isFree =" << joint.isFree;
|
||||
qDebug() << " freeLineage" << joint.freeLineage;
|
||||
qDebug() << " parentIndex" << joint.parentIndex;
|
||||
qDebug() << " distanceToParent" << joint.distanceToParent;
|
||||
qDebug() << " boneRadius" << joint.boneRadius;
|
||||
qDebug() << " translation" << joint.translation;
|
||||
qDebug() << " preTransform" << joint.preTransform;
|
||||
qDebug() << " preRotation" << joint.preRotation;
|
||||
qDebug() << " rotation" << joint.rotation;
|
||||
qDebug() << " postRotation" << joint.postRotation;
|
||||
qDebug() << " postTransform" << joint.postTransform;
|
||||
qDebug() << " transform" << joint.transform;
|
||||
qDebug() << " rotationMin" << joint.rotationMin;
|
||||
qDebug() << " rotationMax" << joint.rotationMax;
|
||||
qDebug() << " inverseDefaultRotation" << joint.inverseDefaultRotation;
|
||||
qDebug() << " inverseBindRotation" << joint.inverseBindRotation;
|
||||
qDebug() << " bindTransform" << joint.bindTransform;
|
||||
qDebug() << " name" << joint.name;
|
||||
qDebug() << " shapePosition" << joint.shapePosition;
|
||||
qDebug() << " shapeRotation" << joint.shapeRotation;
|
||||
qDebug() << " shapeType" << joint.shapeType;
|
||||
qDebug() << " isSkeletonJoint" << joint.isSkeletonJoint;
|
||||
}
|
||||
|
||||
qDebug() << "\n";
|
||||
}
|
||||
|
|
|
@ -4,3 +4,4 @@
|
|||
|
||||
FBXGeometry readOBJ(const QByteArray& model, const QVariantHash& mapping);
|
||||
FBXGeometry readOBJ(QIODevice* device, const QVariantHash& mapping);
|
||||
void fbxDebugDump(const FBXGeometry& fbxgeo);
|
||||
|
|
|
@ -19,13 +19,15 @@ elseif (WIN32)
|
|||
|
||||
target_link_libraries(${TARGET_NAME} ${GLEW_LIBRARIES} opengl32.lib)
|
||||
|
||||
# try to find the Nsight package and add it to the build if we find it
|
||||
find_package(NSIGHT)
|
||||
if (NSIGHT_FOUND)
|
||||
include_directories(${NSIGHT_INCLUDE_DIRS})
|
||||
add_definitions(-DNSIGHT_FOUND)
|
||||
target_link_libraries(${TARGET_NAME} "${NSIGHT_LIBRARIES}")
|
||||
endif ()
|
||||
if (USE_NSIGHT)
|
||||
# try to find the Nsight package and add it to the build if we find it
|
||||
find_package(NSIGHT)
|
||||
if (NSIGHT_FOUND)
|
||||
include_directories(${NSIGHT_INCLUDE_DIRS})
|
||||
add_definitions(-DNSIGHT_FOUND)
|
||||
target_link_libraries(${TARGET_NAME} "${NSIGHT_LIBRARIES}")
|
||||
endif ()
|
||||
endif()
|
||||
elseif (ANDROID)
|
||||
target_link_libraries(${TARGET_NAME} "-lGLESv3" "-lEGL")
|
||||
else ()
|
||||
|
|
|
@ -25,14 +25,14 @@
|
|||
|
||||
#if defined(NSIGHT_FOUND)
|
||||
#include "nvToolsExt.h"
|
||||
class ProfileRange {
|
||||
public:
|
||||
ProfileRange(const char *name) {
|
||||
nvtxRangePush(name);
|
||||
}
|
||||
~ProfileRange() {
|
||||
nvtxRangePop();
|
||||
}
|
||||
class ProfileRange {
|
||||
public:
|
||||
ProfileRange(const char *name) {
|
||||
nvtxRangePush(name);
|
||||
}
|
||||
~ProfileRange() {
|
||||
nvtxRangePop();
|
||||
}
|
||||
};
|
||||
|
||||
#define PROFILE_RANGE(name) ProfileRange profileRangeThis(name);
|
||||
|
@ -114,17 +114,17 @@ public:
|
|||
// For now, instead of calling the raw glCall, use the equivalent call on the batch so the call is beeing recorded
|
||||
// THe implementation of these functions is in GLBackend.cpp
|
||||
|
||||
void _glEnable(GLenum cap);
|
||||
void _glDisable(GLenum cap);
|
||||
|
||||
void _glEnable(GLenum cap);
|
||||
void _glDisable(GLenum cap);
|
||||
|
||||
void _glEnableClientState(GLenum array);
|
||||
void _glDisableClientState(GLenum array);
|
||||
|
||||
void _glCullFace(GLenum mode);
|
||||
void _glAlphaFunc(GLenum func, GLclampf ref);
|
||||
|
||||
void _glDepthFunc(GLenum func);
|
||||
void _glDepthMask(GLboolean flag);
|
||||
void _glDepthFunc(GLenum func);
|
||||
void _glDepthMask(GLboolean flag);
|
||||
void _glDepthRange(GLclampd zNear, GLclampd zFar);
|
||||
|
||||
void _glBindBuffer(GLenum target, GLuint buffer);
|
||||
|
@ -138,14 +138,14 @@ public:
|
|||
void _glUniform1f(GLint location, GLfloat v0);
|
||||
void _glUniform2f(GLint location, GLfloat v0, GLfloat v1);
|
||||
void _glUniform4fv(GLint location, GLsizei count, const GLfloat* value);
|
||||
void _glUniformMatrix4fv(GLint location, GLsizei count, GLboolean transpose, const GLfloat* value);
|
||||
void _glUniformMatrix4fv(GLint location, GLsizei count, GLboolean transpose, const GLfloat* value);
|
||||
|
||||
void _glDrawArrays(GLenum mode, GLint first, GLsizei count);
|
||||
void _glDrawArrays(GLenum mode, GLint first, GLsizei count);
|
||||
void _glDrawRangeElements(GLenum mode, GLuint start, GLuint end, GLsizei count, GLenum type, const void *indices);
|
||||
|
||||
void _glColorPointer(GLint size, GLenum type, GLsizei stride, const void *pointer);
|
||||
void _glNormalPointer(GLenum type, GLsizei stride, const void *pointer);
|
||||
void _glTexCoordPointer(GLint size, GLenum type, GLsizei stride, const void *pointer);
|
||||
|
||||
void _glColorPointer(GLint size, GLenum type, GLsizei stride, const void *pointer);
|
||||
void _glNormalPointer(GLenum type, GLsizei stride, const void *pointer);
|
||||
void _glTexCoordPointer(GLint size, GLenum type, GLsizei stride, const void *pointer);
|
||||
void _glVertexPointer(GLint size, GLenum type, GLsizei stride, const void *pointer);
|
||||
|
||||
void _glVertexAttribPointer(GLuint index, GLint size, GLenum type, GLboolean normalized, GLsizei stride, const void *pointer);
|
||||
|
@ -175,44 +175,44 @@ public:
|
|||
// TODO: As long as we have gl calls explicitely issued from interface
|
||||
// code, we need to be able to record and batch these calls. THe long
|
||||
// term strategy is to get rid of any GL calls in favor of the HIFI GPU API
|
||||
COMMAND_glEnable,
|
||||
COMMAND_glDisable,
|
||||
|
||||
COMMAND_glEnableClientState,
|
||||
COMMAND_glDisableClientState,
|
||||
|
||||
COMMAND_glCullFace,
|
||||
COMMAND_glAlphaFunc,
|
||||
|
||||
COMMAND_glDepthFunc,
|
||||
COMMAND_glDepthMask,
|
||||
COMMAND_glDepthRange,
|
||||
|
||||
COMMAND_glBindBuffer,
|
||||
|
||||
COMMAND_glBindTexture,
|
||||
COMMAND_glActiveTexture,
|
||||
|
||||
COMMAND_glDrawBuffers,
|
||||
|
||||
COMMAND_glUseProgram,
|
||||
COMMAND_glUniform1f,
|
||||
COMMAND_glUniform2f,
|
||||
COMMAND_glUniform4fv,
|
||||
COMMAND_glUniformMatrix4fv,
|
||||
|
||||
COMMAND_glDrawArrays,
|
||||
COMMAND_glDrawRangeElements,
|
||||
|
||||
COMMAND_glColorPointer,
|
||||
COMMAND_glNormalPointer,
|
||||
COMMAND_glTexCoordPointer,
|
||||
COMMAND_glVertexPointer,
|
||||
|
||||
COMMAND_glVertexAttribPointer,
|
||||
COMMAND_glEnableVertexAttribArray,
|
||||
COMMAND_glDisableVertexAttribArray,
|
||||
|
||||
COMMAND_glEnable,
|
||||
COMMAND_glDisable,
|
||||
|
||||
COMMAND_glEnableClientState,
|
||||
COMMAND_glDisableClientState,
|
||||
|
||||
COMMAND_glCullFace,
|
||||
COMMAND_glAlphaFunc,
|
||||
|
||||
COMMAND_glDepthFunc,
|
||||
COMMAND_glDepthMask,
|
||||
COMMAND_glDepthRange,
|
||||
|
||||
COMMAND_glBindBuffer,
|
||||
|
||||
COMMAND_glBindTexture,
|
||||
COMMAND_glActiveTexture,
|
||||
|
||||
COMMAND_glDrawBuffers,
|
||||
|
||||
COMMAND_glUseProgram,
|
||||
COMMAND_glUniform1f,
|
||||
COMMAND_glUniform2f,
|
||||
COMMAND_glUniform4fv,
|
||||
COMMAND_glUniformMatrix4fv,
|
||||
|
||||
COMMAND_glDrawArrays,
|
||||
COMMAND_glDrawRangeElements,
|
||||
|
||||
COMMAND_glColorPointer,
|
||||
COMMAND_glNormalPointer,
|
||||
COMMAND_glTexCoordPointer,
|
||||
COMMAND_glVertexPointer,
|
||||
|
||||
COMMAND_glVertexAttribPointer,
|
||||
COMMAND_glEnableVertexAttribArray,
|
||||
COMMAND_glDisableVertexAttribArray,
|
||||
|
||||
COMMAND_glColor4f,
|
||||
|
||||
NUM_COMMANDS,
|
||||
|
|
|
@ -19,7 +19,6 @@
|
|||
#include <QtCore/QUrlQuery>
|
||||
#include <QtNetwork/QHttpMultiPart>
|
||||
#include <QtNetwork/QNetworkRequest>
|
||||
#include <QEventLoop>
|
||||
#include <qthread.h>
|
||||
|
||||
#include <SettingHandle.h>
|
||||
|
@ -300,8 +299,6 @@ void AccountManager::processReply() {
|
|||
passErrorToCallback(requestReply);
|
||||
}
|
||||
delete requestReply;
|
||||
|
||||
emit replyFinished();
|
||||
}
|
||||
|
||||
void AccountManager::passSuccessToCallback(QNetworkReply* requestReply) {
|
||||
|
@ -342,15 +339,6 @@ void AccountManager::passErrorToCallback(QNetworkReply* requestReply) {
|
|||
}
|
||||
}
|
||||
|
||||
void AccountManager::waitForAllPendingReplies() {
|
||||
while (_pendingCallbackMap.size() > 0) {
|
||||
QEventLoop loop;
|
||||
QObject::connect(this, &AccountManager::replyFinished, &loop, &QEventLoop::quit);
|
||||
loop.exec();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void AccountManager::persistAccountToSettings() {
|
||||
if (_shouldPersistToSettingsFile) {
|
||||
// store this access token into the local settings
|
||||
|
|
|
@ -72,8 +72,6 @@ public:
|
|||
void requestProfile();
|
||||
|
||||
DataServerAccountInfo& getAccountInfo() { return _accountInfo; }
|
||||
|
||||
void waitForAllPendingReplies();
|
||||
|
||||
public slots:
|
||||
void requestAccessToken(const QString& login, const QString& password);
|
||||
|
@ -95,8 +93,6 @@ signals:
|
|||
void loginFailed();
|
||||
void logoutComplete();
|
||||
void balanceChanged(qint64 newBalance);
|
||||
void replyFinished();
|
||||
|
||||
private slots:
|
||||
void processReply();
|
||||
void handleKeypairGenerationError();
|
||||
|
|
|
@ -27,8 +27,7 @@
|
|||
const QString ADDRESS_MANAGER_SETTINGS_GROUP = "AddressManager";
|
||||
const QString SETTINGS_CURRENT_ADDRESS_KEY = "address";
|
||||
|
||||
Setting::Handle<QUrl> currentAddressHandle(QStringList() << ADDRESS_MANAGER_SETTINGS_GROUP << "address");
|
||||
|
||||
Setting::Handle<QUrl> currentAddressHandle(QStringList() << ADDRESS_MANAGER_SETTINGS_GROUP << "address", DEFAULT_HIFI_ADDRESS);
|
||||
|
||||
AddressManager::AddressManager() :
|
||||
_rootPlaceName(),
|
||||
|
|
|
@ -14,6 +14,7 @@
|
|||
#include <QThreadStorage>
|
||||
|
||||
#include "AccountManager.h"
|
||||
#include "LimitedNodeList.h"
|
||||
#include "SharedUtil.h"
|
||||
|
||||
#include "OAuthNetworkAccessManager.h"
|
||||
|
@ -32,7 +33,7 @@ QNetworkReply* OAuthNetworkAccessManager::createRequest(QNetworkAccessManager::O
|
|||
QIODevice* outgoingData) {
|
||||
AccountManager& accountManager = AccountManager::getInstance();
|
||||
|
||||
if (accountManager.hasValidAccessToken()) {
|
||||
if (accountManager.hasValidAccessToken() && req.url().host() == DEFAULT_NODE_AUTH_URL.host()) {
|
||||
QNetworkRequest authenticatedRequest(req);
|
||||
authenticatedRequest.setHeader(QNetworkRequest::UserAgentHeader, HIGH_FIDELITY_USER_AGENT);
|
||||
authenticatedRequest.setRawHeader(ACCESS_TOKEN_AUTHORIZATION_HEADER,
|
||||
|
|
|
@ -224,8 +224,12 @@ void Resource::refresh() {
|
|||
}
|
||||
if (_reply) {
|
||||
ResourceCache::requestCompleted(this);
|
||||
delete _reply;
|
||||
_reply->disconnect(this);
|
||||
_replyTimer->disconnect(this);
|
||||
_reply->deleteLater();
|
||||
_reply = nullptr;
|
||||
_replyTimer->deleteLater();
|
||||
_replyTimer = nullptr;
|
||||
}
|
||||
init();
|
||||
_request.setAttribute(QNetworkRequest::CacheLoadControlAttribute, QNetworkRequest::AlwaysNetwork);
|
||||
|
@ -296,9 +300,9 @@ void Resource::handleDownloadProgress(qint64 bytesReceived, qint64 bytesTotal) {
|
|||
return;
|
||||
}
|
||||
_reply->disconnect(this);
|
||||
_replyTimer->disconnect(this);
|
||||
QNetworkReply* reply = _reply;
|
||||
_reply = nullptr;
|
||||
_replyTimer->disconnect(this);
|
||||
_replyTimer->deleteLater();
|
||||
_replyTimer = nullptr;
|
||||
ResourceCache::requestCompleted(this);
|
||||
|
@ -328,6 +332,10 @@ void Resource::maybeRefresh() {
|
|||
// We don't need to update, return
|
||||
return;
|
||||
}
|
||||
} else if (!variant.isValid() || !variant.canConvert<QDateTime>() ||
|
||||
!variant.value<QDateTime>().isValid() || variant.value<QDateTime>().isNull()) {
|
||||
qDebug() << "Cannot determine when" << _url.fileName() << "was modified last, cached version might be outdated";
|
||||
return;
|
||||
}
|
||||
qDebug() << "Loaded" << _url.fileName() << "from the disk cache but the network version is newer, refreshing.";
|
||||
refresh();
|
||||
|
@ -351,10 +359,19 @@ void Resource::makeRequest() {
|
|||
} else {
|
||||
if (Q_LIKELY(NetworkAccessManager::getInstance().cache())) {
|
||||
QNetworkCacheMetaData metaData = NetworkAccessManager::getInstance().cache()->metaData(_url);
|
||||
bool needUpdate = false;
|
||||
if (metaData.expirationDate().isNull() || metaData.expirationDate() <= QDateTime::currentDateTime()) {
|
||||
// If the expiration date is NULL or in the past,
|
||||
// put one far enough away that it won't be an issue.
|
||||
metaData.setExpirationDate(QDateTime::currentDateTime().addYears(100));
|
||||
needUpdate = true;
|
||||
}
|
||||
if (metaData.lastModified().isNull()) {
|
||||
// If the lastModified date is NULL, set it to now.
|
||||
metaData.setLastModified(QDateTime::currentDateTime());
|
||||
needUpdate = true;
|
||||
}
|
||||
if (needUpdate) {
|
||||
NetworkAccessManager::getInstance().cache()->updateMetaData(metaData);
|
||||
}
|
||||
}
|
||||
|
@ -369,9 +386,9 @@ void Resource::makeRequest() {
|
|||
|
||||
void Resource::handleReplyError(QNetworkReply::NetworkError error, QDebug debug) {
|
||||
_reply->disconnect(this);
|
||||
_replyTimer->disconnect(this);
|
||||
_reply->deleteLater();
|
||||
_reply = nullptr;
|
||||
_replyTimer->disconnect(this);
|
||||
_replyTimer->deleteLater();
|
||||
_replyTimer = nullptr;
|
||||
ResourceCache::requestCompleted(this);
|
||||
|
|
|
@ -61,7 +61,7 @@ void UserActivityLogger::logAction(QString action, QJsonObject details, JSONCall
|
|||
params.errorCallbackReceiver = this;
|
||||
params.errorCallbackMethod = "requestError";
|
||||
}
|
||||
|
||||
|
||||
accountManager.authenticatedRequest(USER_ACTIVITY_URL,
|
||||
QNetworkAccessManager::PostOperation,
|
||||
params,
|
||||
|
@ -86,13 +86,6 @@ void UserActivityLogger::launch(QString applicationVersion) {
|
|||
logAction(ACTION_NAME, actionDetails);
|
||||
}
|
||||
|
||||
void UserActivityLogger::close() {
|
||||
const QString ACTION_NAME = "close";
|
||||
logAction(ACTION_NAME, QJsonObject());
|
||||
|
||||
AccountManager::getInstance().waitForAllPendingReplies();
|
||||
}
|
||||
|
||||
void UserActivityLogger::changedDisplayName(QString displayName) {
|
||||
const QString ACTION_NAME = "changed_display_name";
|
||||
QJsonObject actionDetails;
|
||||
|
|
|
@ -30,7 +30,7 @@ public slots:
|
|||
void logAction(QString action, QJsonObject details = QJsonObject(), JSONCallbackParameters params = JSONCallbackParameters());
|
||||
|
||||
void launch(QString applicationVersion);
|
||||
void close();
|
||||
|
||||
void changedDisplayName(QString displayName);
|
||||
void changedModel(QString typeOfModel, QString modelURL);
|
||||
void changedDomain(QString domainURL);
|
||||
|
|
|
@ -21,7 +21,7 @@ ShapeManager::~ShapeManager() {
|
|||
int numShapes = _shapeMap.size();
|
||||
for (int i = 0; i < numShapes; ++i) {
|
||||
ShapeReference* shapeRef = _shapeMap.getAtIndex(i);
|
||||
delete shapeRef->_shape;
|
||||
delete shapeRef->shape;
|
||||
}
|
||||
_shapeMap.clear();
|
||||
}
|
||||
|
@ -40,26 +40,27 @@ btCollisionShape* ShapeManager::getShape(const ShapeInfo& info) {
|
|||
DoubleHashKey key = info.getHash();
|
||||
ShapeReference* shapeRef = _shapeMap.find(key);
|
||||
if (shapeRef) {
|
||||
shapeRef->_refCount++;
|
||||
return shapeRef->_shape;
|
||||
shapeRef->refCount++;
|
||||
return shapeRef->shape;
|
||||
}
|
||||
btCollisionShape* shape = ShapeInfoUtil::createShapeFromInfo(info);
|
||||
if (shape) {
|
||||
ShapeReference newRef;
|
||||
newRef._refCount = 1;
|
||||
newRef._shape = shape;
|
||||
newRef.refCount = 1;
|
||||
newRef.shape = shape;
|
||||
newRef.key = key;
|
||||
_shapeMap.insert(key, newRef);
|
||||
}
|
||||
return shape;
|
||||
}
|
||||
|
||||
bool ShapeManager::releaseShape(const ShapeInfo& info) {
|
||||
DoubleHashKey key = info.getHash();
|
||||
// private helper method
|
||||
bool ShapeManager::releaseShape(const DoubleHashKey& key) {
|
||||
ShapeReference* shapeRef = _shapeMap.find(key);
|
||||
if (shapeRef) {
|
||||
if (shapeRef->_refCount > 0) {
|
||||
shapeRef->_refCount--;
|
||||
if (shapeRef->_refCount == 0) {
|
||||
if (shapeRef->refCount > 0) {
|
||||
shapeRef->refCount--;
|
||||
if (shapeRef->refCount == 0) {
|
||||
_pendingGarbage.push_back(key);
|
||||
const int MAX_GARBAGE_CAPACITY = 127;
|
||||
if (_pendingGarbage.size() > MAX_GARBAGE_CAPACITY) {
|
||||
|
@ -78,10 +79,19 @@ bool ShapeManager::releaseShape(const ShapeInfo& info) {
|
|||
return false;
|
||||
}
|
||||
|
||||
bool ShapeManager::releaseShape(const ShapeInfo& info) {
|
||||
return releaseShape(info.getHash());
|
||||
}
|
||||
|
||||
bool ShapeManager::releaseShape(const btCollisionShape* shape) {
|
||||
ShapeInfo info;
|
||||
ShapeInfoUtil::collectInfoFromShape(shape, info);
|
||||
return releaseShape(info);
|
||||
int numShapes = _shapeMap.size();
|
||||
for (int i = 0; i < numShapes; ++i) {
|
||||
ShapeReference* shapeRef = _shapeMap.getAtIndex(i);
|
||||
if (shape == shapeRef->shape) {
|
||||
return releaseShape(shapeRef->key);
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void ShapeManager::collectGarbage() {
|
||||
|
@ -89,8 +99,8 @@ void ShapeManager::collectGarbage() {
|
|||
for (int i = 0; i < numShapes; ++i) {
|
||||
DoubleHashKey& key = _pendingGarbage[i];
|
||||
ShapeReference* shapeRef = _shapeMap.find(key);
|
||||
if (shapeRef && shapeRef->_refCount == 0) {
|
||||
delete shapeRef->_shape;
|
||||
if (shapeRef && shapeRef->refCount == 0) {
|
||||
delete shapeRef->shape;
|
||||
_shapeMap.remove(key);
|
||||
}
|
||||
}
|
||||
|
@ -101,7 +111,29 @@ int ShapeManager::getNumReferences(const ShapeInfo& info) const {
|
|||
DoubleHashKey key = info.getHash();
|
||||
const ShapeReference* shapeRef = _shapeMap.find(key);
|
||||
if (shapeRef) {
|
||||
return shapeRef->_refCount;
|
||||
return shapeRef->refCount;
|
||||
}
|
||||
return -1;
|
||||
return 0;
|
||||
}
|
||||
|
||||
int ShapeManager::getNumReferences(const btCollisionShape* shape) const {
|
||||
int numShapes = _shapeMap.size();
|
||||
for (int i = 0; i < numShapes; ++i) {
|
||||
const ShapeReference* shapeRef = _shapeMap.getAtIndex(i);
|
||||
if (shape == shapeRef->shape) {
|
||||
return shapeRef->refCount;
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
bool ShapeManager::hasShape(const btCollisionShape* shape) const {
|
||||
int numShapes = _shapeMap.size();
|
||||
for (int i = 0; i < numShapes; ++i) {
|
||||
const ShapeReference* shapeRef = _shapeMap.getAtIndex(i);
|
||||
if (shape == shapeRef->shape) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
|
|
@ -38,12 +38,17 @@ public:
|
|||
// validation methods
|
||||
int getNumShapes() const { return _shapeMap.size(); }
|
||||
int getNumReferences(const ShapeInfo& info) const;
|
||||
int getNumReferences(const btCollisionShape* shape) const;
|
||||
bool hasShape(const btCollisionShape* shape) const;
|
||||
|
||||
private:
|
||||
bool releaseShape(const DoubleHashKey& key);
|
||||
|
||||
struct ShapeReference {
|
||||
int _refCount;
|
||||
btCollisionShape* _shape;
|
||||
ShapeReference() : _refCount(0), _shape(NULL) {}
|
||||
int refCount;
|
||||
btCollisionShape* shape;
|
||||
DoubleHashKey key;
|
||||
ShapeReference() : refCount(0), shape(NULL) {}
|
||||
};
|
||||
|
||||
btHashMap<DoubleHashKey, ShapeReference> _shapeMap;
|
||||
|
|
|
@ -68,7 +68,7 @@ static const quint64 USECS_PER_SECOND = USECS_PER_MSEC * MSECS_PER_SECOND;
|
|||
const int BITS_IN_BYTE = 8;
|
||||
|
||||
// Use a custom User-Agent to avoid ModSecurity filtering, e.g. by hosting providers.
|
||||
const QByteArray HIGH_FIDELITY_USER_AGENT = "Mozilla/5.0 (HighFidelity)";
|
||||
const QByteArray HIGH_FIDELITY_USER_AGENT = "Mozilla/5.0 (HighFidelityInterface)";
|
||||
|
||||
quint64 usecTimestampNow(bool wantDebug = false);
|
||||
void usecTimestampNowForceClockSkew(int clockSkew);
|
||||
|
|
|
@ -21,10 +21,8 @@ void ShapeManagerTests::testShapeAccounting() {
|
|||
ShapeInfo info;
|
||||
info.setBox(glm::vec3(1.0f, 1.0f, 1.0f));
|
||||
|
||||
// NOTE: ShapeManager returns -1 as refcount when the shape is unknown,
|
||||
// which is distinct from "known but with zero references"
|
||||
int numReferences = shapeManager.getNumReferences(info);
|
||||
if (numReferences != -1) {
|
||||
if (numReferences != 0) {
|
||||
std::cout << __FILE__ << ":" << __LINE__
|
||||
<< " ERROR: expected ignorant ShapeManager after initialization" << std::endl;
|
||||
}
|
||||
|
@ -104,8 +102,7 @@ void ShapeManagerTests::testShapeAccounting() {
|
|||
if (shapeManager.getNumShapes() != 0) {
|
||||
std::cout << __FILE__ << ":" << __LINE__ << " ERROR: expected zero shapes after release" << std::endl;
|
||||
}
|
||||
numReferences = shapeManager.getNumReferences(info);
|
||||
if (numReferences != -1) {
|
||||
if (shapeManager.hasShape(shape)) {
|
||||
std::cout << __FILE__ << ":" << __LINE__
|
||||
<< " ERROR: expected ignorant ShapeManager after garbage collection" << std::endl;
|
||||
}
|
||||
|
@ -122,33 +119,64 @@ void ShapeManagerTests::testShapeAccounting() {
|
|||
void ShapeManagerTests::addManyShapes() {
|
||||
ShapeManager shapeManager;
|
||||
|
||||
QVector<btCollisionShape*> shapes;
|
||||
|
||||
int numSizes = 100;
|
||||
float startSize = 1.0f;
|
||||
float endSize = 99.0f;
|
||||
float deltaSize = (endSize - startSize) / (float)numSizes;
|
||||
ShapeInfo info;
|
||||
for (int i = 0; i < numSizes; ++i) {
|
||||
// make a sphere
|
||||
float s = startSize + (float)i * deltaSize;
|
||||
glm::vec3 scale(s, 1.23f + s, s - 0.573f);
|
||||
info.setBox(0.5f * scale);
|
||||
btCollisionShape* shape = shapeManager.getShape(info);
|
||||
shapes.push_back(shape);
|
||||
if (!shape) {
|
||||
std::cout << __FILE__ << ":" << __LINE__
|
||||
<< " ERROR: i = " << i << " null box shape for scale = " << scale << std::endl;
|
||||
}
|
||||
|
||||
// make a box
|
||||
float radius = 0.5f * s;
|
||||
info.setSphere(radius);
|
||||
shape = shapeManager.getShape(info);
|
||||
shapes.push_back(shape);
|
||||
if (!shape) {
|
||||
std::cout << __FILE__ << ":" << __LINE__
|
||||
<< " ERROR: i = " << i << " null sphere shape for radius = " << radius << std::endl;
|
||||
}
|
||||
}
|
||||
|
||||
// verify shape count
|
||||
int numShapes = shapeManager.getNumShapes();
|
||||
if (numShapes != 2 * numSizes) {
|
||||
std::cout << __FILE__ << ":" << __LINE__
|
||||
<< " ERROR: expected numShapes = " << numSizes << " but found numShapes = " << numShapes << std::endl;
|
||||
}
|
||||
|
||||
// release each shape by pointer
|
||||
for (int i = 0; i < numShapes; ++i) {
|
||||
btCollisionShape* shape = shapes[i];
|
||||
bool success = shapeManager.releaseShape(shape);
|
||||
if (!success) {
|
||||
std::cout << __FILE__ << ":" << __LINE__
|
||||
<< " ERROR: failed to release shape index " << i << std::endl;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// verify zero references
|
||||
for (int i = 0; i < numShapes; ++i) {
|
||||
btCollisionShape* shape = shapes[i];
|
||||
int numReferences = shapeManager.getNumReferences(shape);
|
||||
if (numReferences != 0) {
|
||||
std::cout << __FILE__ << ":" << __LINE__
|
||||
<< " ERROR: expected zero references for shape " << i
|
||||
<< " but refCount = " << numReferences << std::endl;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void ShapeManagerTests::addBoxShape() {
|
||||
|
|
|
@ -50,15 +50,13 @@ bool writeOBJ(QString outFileName, QVector<QVector<VHACD::IVHACD::ConvexHull>>&
|
|||
for (unsigned int i = 0; i < hull.m_nPoints; i++) {
|
||||
out << "v ";
|
||||
out << formatFloat(hull.m_points[i*3]) << " ";
|
||||
// swap y and z because up is 3rd value in OBJ
|
||||
out << formatFloat(hull.m_points[i*3+2]) << " ";
|
||||
out << formatFloat(hull.m_points[i*3+1]) << "\n";
|
||||
out << formatFloat(hull.m_points[i*3+1]) << " ";
|
||||
out << formatFloat(hull.m_points[i*3+2]) << "\n";
|
||||
}
|
||||
for (unsigned int i = 0; i < hull.m_nTriangles; i++) {
|
||||
out << "f ";
|
||||
// change order to flip normal (due to swapping y and z, above)
|
||||
out << hull.m_triangles[i*3+1] + 1 + pointStartOffset << " ";
|
||||
out << hull.m_triangles[i*3] + 1 + pointStartOffset << " ";
|
||||
out << hull.m_triangles[i*3+1] + 1 + pointStartOffset << " ";
|
||||
out << hull.m_triangles[i*3+2] + 1 + pointStartOffset << "\n";
|
||||
}
|
||||
out << "\n";
|
||||
|
|
Loading…
Reference in a new issue