Merge branch 'master' into 20422

This commit is contained in:
David Rowe 2015-04-01 14:56:48 -07:00
commit 96e888864a
28 changed files with 680 additions and 1050 deletions

View file

@ -4,6 +4,7 @@
//
// Created by Brad Hefta-Gaub on 12/31/13.
// Modified by Philip on 3/3/14
// Modified by Thijs Wenker on 3/31/15
// Copyright 2013 High Fidelity, Inc.
//
// This is an example script that turns the hydra controllers and mouse into a entity gun.
@ -66,7 +67,7 @@ var impactSound = SoundCache.getSound(HIFI_PUBLIC_BUCKET + "sounds/Guns/BulletIm
var targetHitSound = SoundCache.getSound(HIFI_PUBLIC_BUCKET + "sounds/Space%20Invaders/hit.raw");
var targetLaunchSound = SoundCache.getSound(HIFI_PUBLIC_BUCKET + "sounds/Space%20Invaders/shoot.raw");
var gunModel = "http://public.highfidelity.io/models/attachments/HaloGun.fst";
var gunModel = "https://s3.amazonaws.com/hifi-public/cozza13/gun/m1911-handgun+1.fbx?v=4";
var audioOptions = {
volume: 0.9
@ -90,44 +91,49 @@ var score = 0;
var bulletID = false;
var targetID = false;
// Create a reticle image in center of screen
// Create overlay buttons and reticle
var BUTTON_SIZE = 32;
var PADDING = 3;
var NUM_BUTTONS = 3;
var screenSize = Controller.getViewportDimensions();
var startX = screenSize.x / 2 - (NUM_BUTTONS * (BUTTON_SIZE + PADDING)) / 2;
var reticle = Overlays.addOverlay("image", {
x: screenSize.x / 2 - 16,
y: screenSize.y / 2 - 16,
width: 32,
height: 32,
imageURL: HIFI_PUBLIC_BUCKET + "images/billiardsReticle.png",
color: { red: 255, green: 255, blue: 255},
x: screenSize.x / 2 - (BUTTON_SIZE / 2),
y: screenSize.y / 2 - (BUTTON_SIZE / 2),
width: BUTTON_SIZE,
height: BUTTON_SIZE,
imageURL: HIFI_PUBLIC_BUCKET + "images/gun/crosshairs.svg",
alpha: 1
});
var offButton = Overlays.addOverlay("image", {
x: screenSize.x - 48,
y: 96,
width: 32,
height: 32,
imageURL: HIFI_PUBLIC_BUCKET + "images/close.png",
color: { red: 255, green: 255, blue: 255},
x: startX,
y: screenSize.y - (BUTTON_SIZE + PADDING),
width: BUTTON_SIZE,
height: BUTTON_SIZE,
imageURL: HIFI_PUBLIC_BUCKET + "images/gun/close.svg",
alpha: 1
});
startX += BUTTON_SIZE + PADDING;
var platformButton = Overlays.addOverlay("image", {
x: screenSize.x - 48,
y: 130,
width: 32,
height: 32,
imageURL: HIFI_PUBLIC_BUCKET + "images/city.png",
color: { red: 255, green: 255, blue: 255},
x: startX,
y: screenSize.y - (BUTTON_SIZE + PADDING),
width: BUTTON_SIZE,
height: BUTTON_SIZE,
imageURL: HIFI_PUBLIC_BUCKET + "images/gun/platform-targets.svg",
alpha: 1
});
startX += BUTTON_SIZE + PADDING;
var gridButton = Overlays.addOverlay("image", {
x: screenSize.x - 48,
y: 164,
width: 32,
height: 32,
imageURL: HIFI_PUBLIC_BUCKET + "images/blocks.png",
color: { red: 255, green: 255, blue: 255},
x: startX,
y: screenSize.y - (BUTTON_SIZE + PADDING),
width: BUTTON_SIZE,
height: BUTTON_SIZE,
imageURL: HIFI_PUBLIC_BUCKET + "images/gun/floating-targets.svg",
alpha: 1
});
@ -163,7 +169,7 @@ function shootBullet(position, velocity, grenade) {
{ type: "Sphere",
position: position,
dimensions: { x: bSize, y: bSize, z: bSize },
color: { red: 255, green: 0, blue: 0 },
color: { red: 0, green: 0, blue: 0 },
velocity: bVelocity,
lifetime: BULLET_LIFETIME,
gravity: { x: 0, y: bGravity, z: 0 },
@ -260,6 +266,7 @@ function makeGrid(type, scale, size) {
}
}
}
function makePlatform(gravity, scale, size) {
var separation = scale * 2;
var pos = Vec3.sum(Camera.getPosition(), Vec3.multiply(10.0 * scale * separation, Quat.getFront(Camera.getOrientation())));
@ -282,7 +289,7 @@ function makePlatform(gravity, scale, size) {
z: pos.z - (separation * size / 2.0) + z * separation },
dimensions: dimensions,
color: { red: Math.random() * 255, green: Math.random() * 255, blue: Math.random() * 255 },
velocity: { x: 0, y: 0, z: 0 },
velocity: { x: 0, y: 0.05, z: 0 },
gravity: { x: 0, y: gravity, z: 0 },
lifetime: TARGET_LIFE,
damping: 0.1,
@ -297,7 +304,7 @@ function makePlatform(gravity, scale, size) {
type: "Box",
position: { x: pos.x, y: pos.y - separation / 2.0, z: pos.z },
dimensions: { x: 2.0 * separation * size, y: separation / 2.0, z: 2.0 * separation * size },
color: { red: 128, green: 128, blue: 128 },
color: { red: 100, green: 100, blue: 100 },
lifetime: TARGET_LIFE
});
@ -372,8 +379,8 @@ function takeFiringPose() {
}
}
MyAvatar.attach(gunModel, "RightHand", {x:0.02, y: 0.11, z: 0.04}, Quat.fromPitchYawRollDegrees(-0, -160, -79), 0.20);
MyAvatar.attach(gunModel, "LeftHand", {x:-0.02, y: 0.11, z: 0.04}, Quat.fromPitchYawRollDegrees(0, 0, 79), 0.20);
MyAvatar.attach(gunModel, "RightHand", {x:0.04, y: 0.22, z: 0.02}, Quat.fromPitchYawRollDegrees(-172, -85, 79), 0.40);
MyAvatar.attach(gunModel, "LeftHand", {x:-0.04, y: 0.22, z: 0.02}, Quat.fromPitchYawRollDegrees(-172, 85, -79), 0.40);
// Give a bit of time to load before playing sound
Script.setTimeout(playLoadSound, 2000);

View file

@ -14,7 +14,6 @@ Script.load("selectAudioDevice.js");
Script.load("controllers/hydra/hydraMove.js");
Script.load("headMove.js");
Script.load("inspect.js");
Script.load("lobby.js");
Script.load("notifications.js");
Script.load("look.js");
Script.load("users.js");

View file

@ -26,20 +26,24 @@ var rollSound = SoundCache.getSound(HIFI_PUBLIC_BUCKET + "sounds/dice/diceRoll.w
var INSUFFICIENT_PERMISSIONS_ERROR_MSG = "You do not have the necessary permissions to create new objects."
var screenSize = Controller.getViewportDimensions();
var BUTTON_SIZE = 32;
var PADDING = 3;
var offButton = Overlays.addOverlay("image", {
x: screenSize.x - 48,
y: 96,
width: 32,
height: 32,
x: screenSize.x / 2 - BUTTON_SIZE,
y: screenSize.y- (BUTTON_SIZE + PADDING),
width: BUTTON_SIZE,
height: BUTTON_SIZE,
imageURL: HIFI_PUBLIC_BUCKET + "images/close.png",
color: { red: 255, green: 255, blue: 255},
alpha: 1
});
var diceButton = Overlays.addOverlay("image", {
x: screenSize.x - 48,
y: 130,
width: 32,
height: 32,
x: screenSize.x / 2 + PADDING,
y: screenSize.y - (BUTTON_SIZE + PADDING),
width: BUTTON_SIZE,
height: BUTTON_SIZE,
imageURL: HIFI_PUBLIC_BUCKET + "images/die.png",
color: { red: 255, green: 255, blue: 255},
alpha: 1
@ -50,6 +54,7 @@ var LIFETIME = 300;
// NOTE: angularVelocity is in radians/sec
var MAX_ANGULAR_SPEED = Math.PI;
function shootDice(position, velocity) {
if (!Entities.canRez()) {
Window.alert(INSUFFICIENT_PERMISSIONS_ERROR_MSG);
@ -100,7 +105,7 @@ function mousePressEvent(event) {
var clickedText = false;
var clickedOverlay = Overlays.getOverlayAtPoint({x: event.x, y: event.y});
if (clickedOverlay == offButton) {
deleteDice();
Script.stop();
} else if (clickedOverlay == diceButton) {
var HOW_HARD = 2.0;
var position = Vec3.sum(Camera.getPosition(), Quat.getFront(Camera.getOrientation()));

View file

@ -16,8 +16,6 @@ HIFI_PUBLIC_BUCKET = "http://s3.amazonaws.com/hifi-public/";
Script.include([
"libraries/stringHelpers.js",
"libraries/dataviewHelpers.js",
"libraries/httpMultiPart.js",
"libraries/modelUploader.js",
"libraries/toolBars.js",
"libraries/progressDialog.js",

View file

@ -8,27 +8,82 @@
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
var intensity = 1.0;
var day = 0.0;
var hour = 12.0;
var longitude = 115.0;
var latitude = 31.0;
var stageOrientation = Quat.fromPitchYawRollDegrees(0.0, 180.0, 0.0);
Script.include("../../utilities/tools/cookies.js");
Scene.setStageDayTime(hour);
Scene.setStageOrientation(stageOrientation);
Scene.setStageLocation(longitude, latitude, 0.0);
/*
function ticktack() {
hour += 0.1;
//Scene.setSunIntensity(Math.cos(time));
if (hour > 24.0) {
hour = 0.0;
day++;
Scene.setStageYearTime(day);
var panel = new Panel(10, 400);
panel.newSlider("Origin Longitude", -180, 180,
function(value) { Scene.setStageLocation(value, Scene.getStageLocationLatitude(), Scene.getStageLocationAltitude()); },
function() { return Scene.getStageLocationLongitude(); },
function(value) { return value.toFixed(0) + " deg"; }
);
panel.newSlider("Origin Latitude", -90, 90,
function(value) { Scene.setStageLocation(Scene.getStageLocationLongitude(), value, Scene.getStageLocationAltitude()); },
function() { return Scene.getStageLocationLatitude(); },
function(value) { return value.toFixed(0) + " deg"; }
);
panel.newSlider("Origin Altitude", 0, 1000,
function(value) { Scene.setStageLocation(Scene.getStageLocationLongitude(), Scene.getStageLocationLatitude(), value); },
function() { return Scene.getStageLocationAltitude(); },
function(value) { return (value).toFixed(0) + " km"; }
);
panel.newSlider("Year Time", 0, 364,
function(value) { Scene.setStageYearTime(value); },
function() { return Scene.getStageYearTime(); },
function(value) {
var numDaysPerMonth = 365.0 / 12.0;
var monthly = (value / numDaysPerMonth);
var month = Math.floor(monthly);
return (month + 1).toFixed(0) + "/" + Math.ceil(0.5 + (monthly - month)*Math.ceil(numDaysPerMonth)).toFixed(0); }
);
panel.newSlider("Day Time", 0, 24,
function(value) { Scene.setStageDayTime(value); },
function() { return Scene.getStageDayTime(); },
function(value) {
var hour = Math.floor(value);
return (hour).toFixed(0) + ":" + ((value - hour)*60.0).toFixed(0);
}
Scene.setStageDayTime(hour);
}
);
Script.setInterval(ticktack, 41);
*/
var tickTackPeriod = 50;
var tickTackSpeed = 0.0;
panel.newSlider("Tick tack time", -1.0, 1.0,
function(value) { tickTackSpeed = value; },
function() { return tickTackSpeed; },
function(value) { return (value).toFixed(2); }
);
function runStageTime() {
if (tickTackSpeed != 0.0) {
var hour = panel.get("Day Time");
hour += tickTackSpeed;
panel.set("Day Time", hour);
if (hour >= 24.0) {
panel.set("Year Time", panel.get("Year Time") + 1);
} else if (hour < 0.0) {
panel.set("Year Time", panel.get("Year Time") - 1);
}
}
}
Script.setInterval(runStageTime, tickTackPeriod);
panel.newSlider("Light Intensity", 0.0, 5,
function(value) { Scene.setSunIntensity(value); },
function() { return Scene.getSunIntensity(); },
function(value) { return (value).toFixed(2); }
);
Controller.mouseMoveEvent.connect(function panelMouseMoveEvent(event) { return panel.mouseMoveEvent(event); });
Controller.mousePressEvent.connect( function panelMousePressEvent(event) { return panel.mousePressEvent(event); });
Controller.mouseReleaseEvent.connect(function(event) { return panel.mouseReleaseEvent(event); });
function scriptEnding() {
Menu.removeMenu("Developer > Scene");
panel.destroy();
}
Script.scriptEnding.connect(scriptEnding);

View file

@ -312,7 +312,7 @@
}
elTextText.value = properties.text;
elTextLineHeight.value = properties.lineHeight;
elTextLineHeight.value = properties.lineHeight.toFixed(4);
elTextTextColorRed.value = properties.textColor.red;
elTextTextColorGreen.value = properties.textColor.green;
elTextTextColorBlue.value = properties.textColor.blue;
@ -477,6 +477,28 @@
ev.initEvent("change", true, true);
document.activeElement.dispatchEvent(ev);
}
// For input and textarea elements, select all of the text on focus
// WebKit-based browsers, such as is used with QWebView, have a quirk
// where the mouseup event comes after the focus event, causing the
// text to be deselected immediately after selecting all of the text.
// To make this work we block the first mouseup event after the elements
// received focus. If we block all mouseup events the user will not
// be able to click within the selected text.
var els = document.querySelectorAll("input, textarea");
for (var i = 0; i < els.length; i++) {
var clicked = false;
els[i].onfocus = function() {
this.select();
clicked = false;
};
els[i].onmouseup = function(e) {
if (!clicked) {
e.preventDefault();
clicked = true;
}
};
}
}
</script>
</head>
@ -723,7 +745,7 @@
<div class="text-section property">
<div class="label">Line Height</div>
<div class="value">
<input class="coord" type='number' id="property-text-line-height"></input>
<input class="coord" type='number' id="property-text-line-height" min="0" step="0.005"></input>
</div>
</div>
<div class="text-section property">

View file

@ -1,693 +0,0 @@
//
// modelUploader.js
// examples/libraries
//
// Copyright 2014 High Fidelity, Inc.
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
modelUploader = (function () {
var that = {},
modelFile,
modelName,
modelURL,
modelCallback,
isProcessing,
fstBuffer,
fbxBuffer,
//svoBuffer,
mapping,
geometry,
API_URL = "https://metaverse.highfidelity.com/api/v1/models",
MODEL_URL = "http://public.highfidelity.com/models/content",
NAME_FIELD = "name",
SCALE_FIELD = "scale",
FILENAME_FIELD = "filename",
TEXDIR_FIELD = "texdir",
MAX_TEXTURE_SIZE = 1024;
function info(message) {
if (progressDialog.isOpen()) {
progressDialog.update(message);
} else {
progressDialog.open(message);
}
print(message);
}
function error(message) {
if (progressDialog.isOpen()) {
progressDialog.close();
}
print(message);
Window.alert(message);
}
function randomChar(length) {
var characters = "0123457689abcdefghijklmnopqrstuvwxyz",
string = "",
i;
for (i = 0; i < length; i += 1) {
string += characters[Math.floor(Math.random() * 36)];
}
return string;
}
function resetDataObjects() {
fstBuffer = null;
fbxBuffer = null;
//svoBuffer = null;
mapping = {};
geometry = {};
geometry.textures = [];
geometry.embedded = [];
}
function readFile(filename) {
var url = "file:///" + filename,
req = new XMLHttpRequest();
req.open("GET", url, false);
req.responseType = "arraybuffer";
req.send();
if (req.status !== 200) {
error("Could not read file: " + filename + " : " + req.statusText);
return null;
}
return {
filename: filename.fileName(),
buffer: req.response
};
}
function readMapping(buffer) {
var dv = new DataView(buffer.buffer),
lines,
line,
tokens,
i,
name,
value,
remainder,
existing;
mapping = {}; // { name : value | name : { value : [remainder] } }
lines = dv.string(0, dv.byteLength).split(/\r\n|\r|\n/);
for (i = 0; i < lines.length; i += 1) {
line = lines[i].trim();
if (line.length > 0 && line[0] !== "#") {
tokens = line.split(/\s*=\s*/);
if (tokens.length > 1) {
name = tokens[0];
value = tokens[1];
if (tokens.length > 2) {
remainder = tokens.slice(2, tokens.length).join(" = ");
} else {
remainder = null;
}
if (tokens.length === 2 && mapping[name] === undefined) {
mapping[name] = value;
} else {
if (mapping[name] === undefined) {
mapping[name] = {};
} else if (typeof mapping[name] !== "object") {
existing = mapping[name];
mapping[name] = { existing : null };
}
if (mapping[name][value] === undefined) {
mapping[name][value] = [];
}
mapping[name][value].push(remainder);
}
}
}
}
}
function writeMapping(buffer) {
var name,
value,
remainder,
i,
string = "";
for (name in mapping) {
if (mapping.hasOwnProperty(name)) {
if (typeof mapping[name] === "object") {
for (value in mapping[name]) {
if (mapping[name].hasOwnProperty(value)) {
remainder = mapping[name][value];
if (remainder === null) {
string += (name + " = " + value + "\n");
} else {
for (i = 0; i < remainder.length; i += 1) {
string += (name + " = " + value + " = " + remainder[i] + "\n");
}
}
}
}
} else {
string += (name + " = " + mapping[name] + "\n");
}
}
}
buffer.buffer = string.toArrayBuffer();
}
function readGeometry(fbxBuffer) {
var textures,
view,
index,
EOF,
previousNodeFilename;
// Reference:
// http://code.blender.org/index.php/2013/08/fbx-binary-file-format-specification/
textures = {};
view = new DataView(fbxBuffer.buffer);
EOF = false;
function parseBinaryFBX() {
var endOffset,
numProperties,
propertyListLength,
nameLength,
name,
filename;
endOffset = view.getUint32(index, true);
numProperties = view.getUint32(index + 4, true);
propertyListLength = view.getUint32(index + 8, true);
nameLength = view.getUint8(index + 12);
index += 13;
if (endOffset === 0) {
return;
}
if (endOffset < index || endOffset > view.byteLength) {
EOF = true;
return;
}
name = view.string(index, nameLength).toLowerCase();
index += nameLength;
if (name === "content" && previousNodeFilename !== "") {
// Blender 2.71 exporter "embeds" external textures as empty binary blobs so ignore these
if (propertyListLength > 5) {
geometry.embedded.push(previousNodeFilename);
}
}
if (name === "relativefilename") {
filename = view.string(index + 5, view.getUint32(index + 1, true)).fileName();
if (!textures.hasOwnProperty(filename)) {
textures[filename] = "";
geometry.textures.push(filename);
}
previousNodeFilename = filename;
} else {
previousNodeFilename = "";
}
index += (propertyListLength);
while (index < endOffset && !EOF) {
parseBinaryFBX();
}
}
function readTextFBX() {
var line,
view,
viewLength,
charCode,
charCodes,
numCharCodes,
filename,
relativeFilename = "",
MAX_CHAR_CODES = 250;
view = new Uint8Array(fbxBuffer.buffer);
viewLength = view.byteLength;
charCodes = [];
numCharCodes = 0;
for (index = 0; index < viewLength; index += 1) {
charCode = view[index];
if (charCode !== 9 && charCode !== 32) {
if (charCode === 10) { // EOL. Can ignore EOF.
line = String.fromCharCode.apply(String, charCodes).toLowerCase();
// For embedded textures, "Content:" line immediately follows "RelativeFilename:" line.
if (line.slice(0, 8) === "content:" && relativeFilename !== "") {
geometry.embedded.push(relativeFilename);
}
if (line.slice(0, 17) === "relativefilename:") {
filename = line.slice(line.indexOf("\""), line.lastIndexOf("\"") - line.length).fileName();
if (!textures.hasOwnProperty(filename)) {
textures[filename] = "";
geometry.textures.push(filename);
}
relativeFilename = filename;
} else {
relativeFilename = "";
}
charCodes = [];
numCharCodes = 0;
} else {
if (numCharCodes < MAX_CHAR_CODES) { // Only interested in start of line
charCodes.push(charCode);
numCharCodes += 1;
}
}
}
}
}
readTextFBX();
}
function readModel() {
var fbxFilename,
//svoFilename,
fileType;
info("Reading model file");
print("Model file: " + modelFile);
if (modelFile.toLowerCase().fileType() === "fst") {
fstBuffer = readFile(modelFile);
if (fstBuffer === null) {
return false;
}
readMapping(fstBuffer);
fileType = mapping[FILENAME_FIELD].toLowerCase().fileType();
if (mapping.hasOwnProperty(FILENAME_FIELD)) {
if (fileType === "fbx") {
fbxFilename = modelFile.path() + "\\" + mapping[FILENAME_FIELD];
//} else if (fileType === "svo") {
// svoFilename = modelFile.path() + "\\" + mapping[FILENAME_FIELD];
} else {
error("Unrecognized model type in FST file!");
return false;
}
} else {
error("Model file name not found in FST file!");
return false;
}
} else {
fstBuffer = {
filename: "Interface." + randomChar(6), // Simulate avatar model uploading behaviour
buffer: null
};
if (modelFile.toLowerCase().fileType() === "fbx") {
fbxFilename = modelFile;
mapping[FILENAME_FIELD] = modelFile.fileName();
//} else if (modelFile.toLowerCase().fileType() === "svo") {
// svoFilename = modelFile;
// mapping[FILENAME_FIELD] = modelFile.fileName();
} else {
error("Unrecognized file type: " + modelFile);
return false;
}
}
if (!isProcessing) { return false; }
if (fbxFilename) {
fbxBuffer = readFile(fbxFilename);
if (fbxBuffer === null) {
return false;
}
if (!isProcessing) { return false; }
readGeometry(fbxBuffer);
}
//if (svoFilename) {
// svoBuffer = readFile(svoFilename);
// if (svoBuffer === null) {
// return false;
// }
//}
// Add any missing basic mappings
if (!mapping.hasOwnProperty(NAME_FIELD)) {
mapping[NAME_FIELD] = modelFile.fileName().fileBase();
}
if (!mapping.hasOwnProperty(TEXDIR_FIELD)) {
mapping[TEXDIR_FIELD] = ".";
}
if (!mapping.hasOwnProperty(SCALE_FIELD)) {
mapping[SCALE_FIELD] = 1.0;
}
return true;
}
function setProperties() {
var form = [],
directory,
displayAs,
validateAs;
progressDialog.close();
print("Setting model properties");
form.push({ label: "Name:", value: mapping[NAME_FIELD] });
directory = modelFile.path() + "/" + mapping[TEXDIR_FIELD];
displayAs = new RegExp("^" + modelFile.path().regExpEscape() + "[\\\\\\\/](.*)");
validateAs = new RegExp("^" + modelFile.path().regExpEscape() + "([\\\\\\\/].*)?");
form.push({
label: "Texture directory:",
directory: modelFile.path() + "/" + mapping[TEXDIR_FIELD],
title: "Choose Texture Directory",
displayAs: displayAs,
validateAs: validateAs,
errorMessage: "Texture directory must be subdirectory of the model directory."
});
form.push({ button: "Cancel" });
if (!Window.form("Set Model Properties", form)) {
print("User cancelled uploading model");
return false;
}
mapping[NAME_FIELD] = form[0].value;
mapping[TEXDIR_FIELD] = form[1].directory.slice(modelFile.path().length + 1);
if (mapping[TEXDIR_FIELD] === "") {
mapping[TEXDIR_FIELD] = ".";
}
writeMapping(fstBuffer);
return true;
}
function createHttpMessage(callback) {
var multiparts = [],
lodCount,
lodFile,
lodBuffer,
textureBuffer,
textureSourceFormat,
textureTargetFormat,
embeddedTextures,
i;
info("Preparing to send model");
// Model name
if (mapping.hasOwnProperty(NAME_FIELD)) {
multiparts.push({
name : "model_name",
string : mapping[NAME_FIELD]
});
} else {
error("Model name is missing");
httpMultiPart.clear();
return;
}
// FST file
if (fstBuffer) {
multiparts.push({
name : "fst",
buffer: fstBuffer
});
}
// FBX file
if (fbxBuffer) {
multiparts.push({
name : "fbx",
buffer: fbxBuffer
});
}
// SVO file
//if (svoBuffer) {
// multiparts.push({
// name : "svo",
// buffer: svoBuffer
// });
//}
// LOD files
lodCount = 0;
for (lodFile in mapping.lod) {
if (mapping.lod.hasOwnProperty(lodFile)) {
lodBuffer = readFile(modelFile.path() + "\/" + lodFile);
if (lodBuffer === null) {
return;
}
multiparts.push({
name: "lod" + lodCount,
buffer: lodBuffer
});
lodCount += 1;
}
if (!isProcessing) { return; }
}
// Textures
embeddedTextures = "|" + geometry.embedded.join("|") + "|";
for (i = 0; i < geometry.textures.length; i += 1) {
if (embeddedTextures.indexOf("|" + geometry.textures[i].fileName() + "|") === -1) {
textureBuffer = readFile(modelFile.path() + "\/"
+ (mapping[TEXDIR_FIELD] !== "." ? mapping[TEXDIR_FIELD] + "\/" : "")
+ geometry.textures[i]);
if (textureBuffer === null) {
return;
}
textureSourceFormat = geometry.textures[i].fileType().toLowerCase();
textureTargetFormat = (textureSourceFormat === "jpg" ? "jpg" : "png");
textureBuffer.buffer =
textureBuffer.buffer.recodeImage(textureSourceFormat, textureTargetFormat, MAX_TEXTURE_SIZE);
textureBuffer.filename = textureBuffer.filename.slice(0, -textureSourceFormat.length) + textureTargetFormat;
multiparts.push({
name: "texture" + i,
buffer: textureBuffer
});
}
if (!isProcessing) { return; }
}
// Model category
multiparts.push({
name : "model_category",
string : "content"
});
// Create HTTP message
httpMultiPart.clear();
Script.setTimeout(function addMultipart() {
var multipart = multiparts.shift();
httpMultiPart.add(multipart);
if (!isProcessing) { return; }
if (multiparts.length > 0) {
Script.setTimeout(addMultipart, 25);
} else {
callback();
}
}, 25);
}
function sendToHighFidelity() {
var req,
uploadedChecks,
HTTP_GET_TIMEOUT = 60, // 1 minute
HTTP_SEND_TIMEOUT = 900, // 15 minutes
UPLOADED_CHECKS = 30,
CHECK_UPLOADED_TIMEOUT = 1, // 1 second
handleCheckUploadedResponses,
handleUploadModelResponses,
handleRequestUploadResponses;
function uploadTimedOut() {
error("Model upload failed: Internet request timed out!");
}
function debugResponse() {
print("req.errorCode = " + req.errorCode);
print("req.readyState = " + req.readyState);
print("req.status = " + req.status);
print("req.statusText = " + req.statusText);
print("req.responseType = " + req.responseType);
print("req.responseText = " + req.responseText);
print("req.response = " + req.response);
print("req.getAllResponseHeaders() = " + req.getAllResponseHeaders());
}
function checkUploaded() {
if (!isProcessing) { return; }
info("Checking uploaded model");
req = new XMLHttpRequest();
req.open("HEAD", modelURL, true);
req.timeout = HTTP_GET_TIMEOUT * 1000;
req.onreadystatechange = handleCheckUploadedResponses;
req.ontimeout = uploadTimedOut;
req.send();
}
handleCheckUploadedResponses = function () {
//debugResponse();
if (req.readyState === req.DONE) {
if (req.status === 200) {
// Note: Unlike avatar models, for content models we don't need to refresh texture cache.
print("Model uploaded: " + modelURL);
progressDialog.close();
if (Window.confirm("Your model has been uploaded as: " + modelURL + "\nDo you want to rez it?")) {
modelCallback(modelURL);
}
} else if (req.status === 404) {
if (uploadedChecks > 0) {
uploadedChecks -= 1;
Script.setTimeout(checkUploaded, CHECK_UPLOADED_TIMEOUT * 1000);
} else {
print("Error: " + req.status + " " + req.statusText);
error("We could not verify that your model was successfully uploaded but it may have been at: "
+ modelURL);
}
} else {
print("Error: " + req.status + " " + req.statusText);
error("There was a problem with your upload, please try again later.");
}
}
};
function uploadModel(method) {
var url;
if (!isProcessing) { return; }
req = new XMLHttpRequest();
if (method === "PUT") {
url = API_URL + "\/" + modelName;
req.open("PUT", url, true); //print("PUT " + url);
} else {
url = API_URL;
req.open("POST", url, true); //print("POST " + url);
}
req.setRequestHeader("Content-Type", "multipart/form-data; boundary=\"" + httpMultiPart.boundary() + "\"");
req.timeout = HTTP_SEND_TIMEOUT * 1000;
req.onreadystatechange = handleUploadModelResponses;
req.ontimeout = uploadTimedOut;
req.send(httpMultiPart.response().buffer);
}
handleUploadModelResponses = function () {
//debugResponse();
if (req.readyState === req.DONE) {
if (req.status === 200) {
uploadedChecks = UPLOADED_CHECKS;
checkUploaded();
} else {
print("Error: " + req.status + " " + req.statusText);
error("There was a problem with your upload, please try again later.");
}
}
};
function requestUpload() {
var url;
if (!isProcessing) { return; }
url = API_URL + "\/" + modelName; // XMLHttpRequest automatically handles authorization of API requests.
req = new XMLHttpRequest();
req.open("GET", url, true); //print("GET " + url);
req.responseType = "json";
req.timeout = HTTP_GET_TIMEOUT * 1000;
req.onreadystatechange = handleRequestUploadResponses;
req.ontimeout = uploadTimedOut;
req.send();
}
handleRequestUploadResponses = function () {
var response;
//debugResponse();
if (req.readyState === req.DONE) {
if (req.status === 200) {
if (req.responseType === "json") {
response = JSON.parse(req.responseText);
if (response.status === "success") {
if (response.exists === false) {
uploadModel("POST");
} else if (response.can_update === true) {
uploadModel("PUT");
} else {
error("This model file already exists and is owned by someone else!");
}
return;
}
}
} else {
print("Error: " + req.status + " " + req.statusText);
}
error("Model upload failed! Something went wrong at the data server.");
}
};
info("Sending model to High Fidelity");
requestUpload();
}
that.upload = function (file, callback) {
modelFile = file;
modelCallback = callback;
isProcessing = true;
progressDialog.onCancel = function () {
print("User cancelled uploading model");
isProcessing = false;
};
resetDataObjects();
if (readModel()) {
if (setProperties()) {
modelName = mapping[NAME_FIELD];
modelURL = MODEL_URL + "\/" + mapping[NAME_FIELD] + ".fst"; // All models are uploaded as an FST
createHttpMessage(sendToHighFidelity);
}
}
resetDataObjects();
};
return that;
}());

View file

@ -145,98 +145,3 @@ test("Test timeout", function() {
this.assertEquals(0, req.status, "status should be `0`");
this.assertEquals(4, req.errorCode, "4 is the timeout error code for QNetworkReply::NetworkError");
});
var localFile = Window.browse("Find defaultScripts.js file ...", "", "defaultScripts.js (defaultScripts.js)");
if (localFile !== null) {
localFile = "file:///" + localFile;
test("Test GET local file synchronously", function () {
var req = new XMLHttpRequest();
var statesVisited = [true, false, false, false, false]
req.onreadystatechange = function () {
statesVisited[req.readyState] = true;
};
req.open("GET", localFile, false);
req.send();
this.assertEquals(req.DONE, req.readyState, "readyState should be DONE");
this.assertEquals(200, req.status, "status should be `200`");
this.assertEquals("OK", req.statusText, "statusText should be `OK`");
this.assertEquals(0, req.errorCode);
this.assertNotEquals("", req.getAllResponseHeaders(), "headers should not be null");
this.assertContains("High Fidelity", req.response.substring(0, 100), "expected text not found in response")
for (var i = 0; i <= req.DONE; i++) {
this.assertEquals(true, statesVisited[i], i + " should be set");
}
});
test("Test GET nonexistent local file", function () {
var nonexistentFile = localFile.replace(".js", "NoExist.js");
var req = new XMLHttpRequest();
req.open("GET", nonexistentFile, false);
req.send();
this.assertEquals(req.DONE, req.readyState, "readyState should be DONE");
this.assertEquals(404, req.status, "status should be `404`");
this.assertEquals("Not Found", req.statusText, "statusText should be `Not Found`");
this.assertNotEquals(0, req.errorCode);
});
test("Test GET local file already open", function () {
// Can't open file exclusively in order to test.
});
test("Test GET local file with data not implemented", function () {
var req = new XMLHttpRequest();
req.open("GET", localFile, true);
req.send("data");
this.assertEquals(req.DONE, req.readyState, "readyState should be DONE");
this.assertEquals(501, req.status, "status should be `501`");
this.assertEquals("Not Implemented", req.statusText, "statusText should be `Not Implemented`");
this.assertNotEquals(0, req.errorCode);
});
test("Test GET local file asynchronously not implemented", function () {
var req = new XMLHttpRequest();
req.open("GET", localFile, true);
req.send();
this.assertEquals(req.DONE, req.readyState, "readyState should be DONE");
this.assertEquals(501, req.status, "status should be `501`");
this.assertEquals("Not Implemented", req.statusText, "statusText should be `Not Implemented`");
this.assertNotEquals(0, req.errorCode);
});
test("Test POST local file not implemented", function () {
var req = new XMLHttpRequest();
req.open("POST", localFile, false);
req.send();
this.assertEquals(req.DONE, req.readyState, "readyState should be DONE");
this.assertEquals(501, req.status, "status should be `501`");
this.assertEquals("Not Implemented", req.statusText, "statusText should be `Not Implemented`");
this.assertNotEquals(0, req.errorCode);
});
test("Test local file username and password not implemented", function () {
var req = new XMLHttpRequest();
req.open("GET", localFile, false, "username", "password");
req.send();
this.assertEquals(req.DONE, req.readyState, "readyState should be DONE");
this.assertEquals(501, req.status, "status should be `501`");
this.assertEquals("Not Implemented", req.statusText, "statusText should be `Not Implemented`");
this.assertNotEquals(0, req.errorCode);
});
} else {
print("Local file operation not tested");
}

View file

@ -0,0 +1,278 @@
//
// cookies.js
//
// version 1.0
//
// Created by Sam Gateau, 4/1/2015
// A simple ui panel that present a list of porperties and the proper widget to edit it
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
// The Slider class
Slider = function(x,y,width,thumbSize) {
this.thumb = Overlays.addOverlay("text", {
backgroundColor: { red: 255, green: 255, blue: 255 },
x: x,
y: y,
width: thumbSize,
height: thumbSize,
alpha: 1.0,
backgroundAlpha: 1.0,
visible: true
});
this.background = Overlays.addOverlay("text", {
backgroundColor: { red: 125, green: 125, blue: 255 },
x: x,
y: y,
width: width,
height: thumbSize,
alpha: 1.0,
backgroundAlpha: 0.5,
visible: true
});
this.thumbSize = thumbSize;
this.thumbHalfSize = 0.5 * thumbSize;
this.minThumbX = x + this.thumbHalfSize;
this.maxThumbX = x + width - this.thumbHalfSize;
this.thumbX = this.minThumbX;
this.minValue = 0.0;
this.maxValue = 1.0;
this.clickOffsetX = 0;
this.isMoving = false;
this.updateThumb = function() {
thumbTruePos = this.thumbX - 0.5 * this.thumbSize;
Overlays.editOverlay(this.thumb, { x: thumbTruePos } );
};
this.onMouseMoveEvent = function(event) {
if (this.isMoving) {
newThumbX = event.x - this.clickOffsetX;
if (newThumbX < this.minThumbX) {
newThumbX = this.minThumbX;
}
if (newThumbX > this.maxThumbX) {
newThumbX = this.maxThumbX;
}
this.thumbX = newThumbX;
this.updateThumb();
this.onValueChanged(this.getValue());
}
};
this.onMousePressEvent = function(event) {
this.isMoving = true;
var clickOffset = event.x - this.thumbX;
if ((clickOffset > -this.thumbHalfSize) && (clickOffset < this.thumbHalfSize)) {
this.clickOffsetX = clickOffset;
} else {
this.clickOffsetX = 0;
this.thumbX = event.x;
this.updateThumb();
this.onValueChanged(this.getValue());
}
};
this.onMouseReleaseEvent = function(event) {
this.isMoving = false;
};
// Public members:
this.setNormalizedValue = function(value) {
if (value < 0.0) {
this.thumbX = this.minThumbX;
} else if (value > 1.0) {
this.thumbX = this.maxThumbX;
} else {
this.thumbX = value * (this.maxThumbX - this.minThumbX) + this.minThumbX;
}
this.updateThumb();
};
this.getNormalizedValue = function() {
return (this.thumbX - this.minThumbX) / (this.maxThumbX - this.minThumbX);
};
this.setValue = function(value) {
var normValue = (value - this.minValue) / (this.maxValue - this.minValue);
this.setNormalizedValue(normValue);
};
this.getValue = function() {
return this.getNormalizedValue() * (this.maxValue - this.minValue) + this.minValue;
};
this.onValueChanged = function(value) {};
this.destroy = function() {
Overlays.deleteOverlay(this.background);
Overlays.deleteOverlay(this.thumb);
};
}
var textFontSize = 16;
function PanelItem(name, setter, getter, displayer, x, y, textWidth, valueWidth, height) {
this.name = name;
this.displayer = typeof displayer !== 'undefined' ? displayer : function(value) { return value.toFixed(2); };
var topMargin = (height - textFontSize);
this.title = Overlays.addOverlay("text", {
backgroundColor: { red: 255, green: 255, blue: 255 },
x: x,
y: y,
width: textWidth,
height: height,
alpha: 1.0,
backgroundAlpha: 0.5,
visible: true,
text: name,
font: {size: textFontSize},
topMargin: topMargin,
});
this.value = Overlays.addOverlay("text", {
backgroundColor: { red: 255, green: 255, blue: 255 },
x: x + textWidth,
y: y,
width: valueWidth,
height: height,
alpha: 1.0,
backgroundAlpha: 0.5,
visible: true,
text: this.displayer(getter()),
font: {size: textFontSize},
topMargin: topMargin
});
this.getter = getter;
this.setter = function(value) {
setter(value);
Overlays.editOverlay(this.value, {text: this.displayer(getter())});
if (this.widget) {
this.widget.setValue(value);
}
};
this.setterFromWidget = function(value) {
setter(value);
Overlays.editOverlay(this.value, {text: this.displayer(getter())});
};
this.widget = null;
this.destroy = function() {
Overlays.deleteOverlay(this.title);
Overlays.deleteOverlay(this.value);
if (this.widget != null) {
this.widget.destroy();
}
}
}
var textWidth = 180;
var valueWidth = 100;
var widgetWidth = 300;
var rawHeight = 20;
var rawYDelta = rawHeight * 1.5;
Panel = function(x, y) {
this.x = x;
this.y = y;
this.nextY = y;
this.widgetX = x + textWidth + valueWidth;
this.items = new Array();
this.activeWidget = null;
this.mouseMoveEvent = function(event) {
if (this.activeWidget) {
this.activeWidget.onMouseMoveEvent(event);
}
};
// we also handle click detection in our mousePressEvent()
this.mousePressEvent = function(event) {
// Make sure we quitted previous widget
if (this.activeWidget) {
this.activeWidget.onMouseReleaseEvent(event);
}
this.activeWidget = null;
var clickedOverlay = Overlays.getOverlayAtPoint({x: event.x, y: event.y});
// If the user clicked any of the slider background then...
for (var i in this.items) {
var widget = this.items[i].widget;
if (clickedOverlay == widget.background) {
this.activeWidget = widget;
this.activeWidget.onMousePressEvent(event);
// print("clicked... widget=" + i);
break;
}
}
};
this.mouseReleaseEvent = function(event) {
if (this.activeWidget) {
this.activeWidget.onMouseReleaseEvent(event);
}
this.activeWidget = null;
};
this.newSlider = function(name, minValue, maxValue, setValue, getValue, displayValue) {
var sliderItem = new PanelItem(name, setValue, getValue, displayValue, this.x, this.nextY, textWidth, valueWidth, rawHeight);
var slider = new Slider(this.widgetX, this.nextY, widgetWidth, rawHeight);
slider.minValue = minValue;
slider.maxValue = maxValue;
slider.onValueChanged = function(value) { sliderItem.setterFromWidget(value); };
sliderItem.widget = slider;
sliderItem.setter(getValue());
this.items[name] = sliderItem;
this.nextY += rawYDelta;
// print("created Item... slider=" + name);
};
this.destroy = function() {
for (var i in this.items) {
this.items[i].destroy();
}
}
this.set = function(name, value) {
var item = this.items[name];
if (item != null) {
return item.setter(value);
}
return null;
}
this.get = function(name) {
var item = this.items[name];
if (item != null) {
return item.getter();
}
return null;
}
};

View file

@ -256,7 +256,8 @@ bool setupEssentials(int& argc, char** argv) {
auto speechRecognizer = DependencyManager::set<SpeechRecognizer>();
#endif
auto discoverabilityManager = DependencyManager::set<DiscoverabilityManager>();
auto sceneScriptingInterface = DependencyManager::set<SceneScriptingInterface>();
return true;
}
@ -3587,6 +3588,8 @@ void Application::registerScriptEngineWithApplicationServices(ScriptEngine* scri
scriptEngine->registerFunction(hmdInterface, "getHUDLookAtPosition2D", HMDScriptingInterface::getHUDLookAtPosition2D, 0);
scriptEngine->registerFunction(hmdInterface, "getHUDLookAtPosition3D", HMDScriptingInterface::getHUDLookAtPosition3D, 0);
scriptEngine->registerGlobalObject("Scene", DependencyManager::get<SceneScriptingInterface>().data());
#ifdef HAVE_RTMIDI
scriptEngine->registerGlobalObject("MIDI", &MIDIManager::getInstance());
#endif
@ -3738,22 +3741,27 @@ bool Application::askToSetAvatarUrl(const QString& url) {
_myAvatar->setFaceModelURL(url);
UserActivityLogger::getInstance().changedModel("head", url);
_myAvatar->sendIdentityPacket();
emit faceURLChanged(url);
} else if (msgBox.clickedButton() == bodyButton) {
qDebug() << "Chose to use for body: " << url;
_myAvatar->setSkeletonModelURL(url);
// if the head is empty, reset it to the default head.
if (_myAvatar->getFaceModelURLString().isEmpty()) {
_myAvatar->setFaceModelURL(DEFAULT_HEAD_MODEL_URL);
emit faceURLChanged(DEFAULT_HEAD_MODEL_URL.toString());
UserActivityLogger::getInstance().changedModel("head", DEFAULT_HEAD_MODEL_URL.toString());
}
UserActivityLogger::getInstance().changedModel("skeleton", url);
_myAvatar->sendIdentityPacket();
emit skeletonURLChanged(url);
} else if (msgBox.clickedButton() == bodyAndHeadButton) {
qDebug() << "Chose to use for body + head: " << url;
_myAvatar->setFaceModelURL(QString());
_myAvatar->setSkeletonModelURL(url);
UserActivityLogger::getInstance().changedModel("skeleton", url);
_myAvatar->sendIdentityPacket();
emit faceURLChanged(QString());
emit skeletonURLChanged(url);
} else {
qDebug() << "Declined to use the avatar: " << url;
}

View file

@ -336,6 +336,9 @@ signals:
void checkBackgroundDownloads();
void domainConnectionRefused(const QString& reason);
void faceURLChanged(const QString& newValue);
void skeletonURLChanged(const QString& newValue);
public slots:
void domainChanged(const QString& domainHostname);
void updateWindowTitle();

View file

@ -54,6 +54,7 @@ WebWindowClass::WebWindowClass(const QString& title, const QString& url, int wid
_windowWidget = dockWidget;
} else {
_windowWidget = new QWidget(Application::getInstance()->getWindow(), Qt::Window);
_windowWidget->setWindowTitle(title);
_windowWidget->setMinimumSize(width, height);
@ -89,24 +90,24 @@ void WebWindowClass::setVisible(bool visible) {
if (visible) {
if (_isToolWindow) {
QMetaObject::invokeMethod(
Application::getInstance()->getToolWindow(), "setVisible", Qt::BlockingQueuedConnection, Q_ARG(bool, visible));
Application::getInstance()->getToolWindow(), "setVisible", Qt::AutoConnection, Q_ARG(bool, visible));
} else {
QMetaObject::invokeMethod(_windowWidget, "raise", Qt::BlockingQueuedConnection);
QMetaObject::invokeMethod(_windowWidget, "raise", Qt::AutoConnection);
}
}
QMetaObject::invokeMethod(_windowWidget, "setVisible", Qt::BlockingQueuedConnection, Q_ARG(bool, visible));
QMetaObject::invokeMethod(_windowWidget, "setVisible", Qt::AutoConnection, Q_ARG(bool, visible));
}
void WebWindowClass::setURL(const QString& url) {
if (QThread::currentThread() != thread()) {
QMetaObject::invokeMethod(this, "setURL", Qt::BlockingQueuedConnection, Q_ARG(QString, url));
QMetaObject::invokeMethod(this, "setURL", Qt::AutoConnection, Q_ARG(QString, url));
return;
}
_webView->setUrl(url);
}
void WebWindowClass::raise() {
QMetaObject::invokeMethod(_windowWidget, "raise", Qt::BlockingQueuedConnection);
QMetaObject::invokeMethod(_windowWidget, "raise", Qt::AutoConnection);
}
QScriptValue WebWindowClass::constructor(QScriptContext* context, QScriptEngine* engine) {

View file

@ -24,6 +24,7 @@
#include "LoginDialog.h"
#include "UIUtil.h"
const QString CREATE_ACCOUNT_URL = NetworkingConstants::METAVERSE_SERVER_URL.toString() + "/signup";
const QString FORGOT_PASSWORD_URL = NetworkingConstants::METAVERSE_SERVER_URL.toString() + "/users/password/new";
LoginDialog::LoginDialog(QWidget* parent) :
@ -45,6 +46,7 @@ LoginDialog::LoginDialog(QWidget* parent) :
this, &LoginDialog::close);
UIUtil::scaleWidgetFontSizes(this);
_ui->accountLabel->setText(_ui->accountLabel->text().arg(CREATE_ACCOUNT_URL, FORGOT_PASSWORD_URL));
// Initialize toggle connection
toggleQAction();

View file

@ -16,17 +16,18 @@
#include <avatar/AvatarManager.h>
#include <devices/Faceshift.h>
#include <devices/SixenseManager.h>
#include <NetworkingConstants.h>
#include "Application.h"
#include "MainWindow.h"
#include "LODManager.h"
#include "Menu.h"
#include "ModelsBrowser.h"
#include "PreferencesDialog.h"
#include "Snapshot.h"
#include "UserActivityLogger.h"
#include "UIUtil.h"
const int PREFERENCES_HEIGHT_PADDING = 20;
PreferencesDialog::PreferencesDialog(QWidget* parent) :
@ -46,6 +47,11 @@ PreferencesDialog::PreferencesDialog(QWidget* parent) :
connect(ui.buttonBrowseScriptsLocation, &QPushButton::clicked, this, &PreferencesDialog::openScriptsLocationBrowser);
connect(ui.buttonReloadDefaultScripts, &QPushButton::clicked,
Application::getInstance(), &Application::loadDefaultScripts);
connect(Application::getInstance(), &Application::faceURLChanged, this, &PreferencesDialog::faceURLChanged);
connect(Application::getInstance(), &Application::skeletonURLChanged, this, &PreferencesDialog::skeletonURLChanged);
// move dialog to left side
move(parentWidget()->geometry().topLeft());
setFixedHeight(parentWidget()->size().height() - PREFERENCES_HEIGHT_PADDING);
@ -53,9 +59,19 @@ PreferencesDialog::PreferencesDialog(QWidget* parent) :
UIUtil::scaleWidgetFontSizes(this);
}
void PreferencesDialog::faceURLChanged(const QString& newValue) {
ui.faceURLEdit->setText(newValue);
}
void PreferencesDialog::skeletonURLChanged(const QString& newValue) {
ui.skeletonURLEdit->setText(newValue);
}
void PreferencesDialog::accept() {
savePreferences();
close();
delete _marketplaceWindow;
_marketplaceWindow = NULL;
}
void PreferencesDialog::setHeadUrl(QString modelUrl) {
@ -67,15 +83,23 @@ void PreferencesDialog::setSkeletonUrl(QString modelUrl) {
}
void PreferencesDialog::openHeadModelBrowser() {
ModelsBrowser modelBrowser(FSTReader::HEAD_MODEL);
connect(&modelBrowser, &ModelsBrowser::selected, this, &PreferencesDialog::setHeadUrl);
modelBrowser.browse();
auto MARKETPLACE_URL = NetworkingConstants::METAVERSE_SERVER_URL.toString() + "/marketplace?category=avatars";
auto WIDTH = 900;
auto HEIGHT = 700;
if (!_marketplaceWindow) {
_marketplaceWindow = new WebWindowClass("Marketplace", MARKETPLACE_URL, WIDTH, HEIGHT, false);
}
_marketplaceWindow->setVisible(true);
}
void PreferencesDialog::openBodyModelBrowser() {
ModelsBrowser modelBrowser(FSTReader::HEAD_AND_BODY_MODEL);
connect(&modelBrowser, &ModelsBrowser::selected, this, &PreferencesDialog::setSkeletonUrl);
modelBrowser.browse();
auto MARKETPLACE_URL = NetworkingConstants::METAVERSE_SERVER_URL.toString() + "/marketplace?category=avatars";
auto WIDTH = 900;
auto HEIGHT = 700;
if (!_marketplaceWindow) {
_marketplaceWindow = new WebWindowClass("Marketplace", MARKETPLACE_URL, WIDTH, HEIGHT, false);
}
_marketplaceWindow->setVisible(true);
}
void PreferencesDialog::openSnapshotLocationBrowser() {
@ -192,11 +216,13 @@ void PreferencesDialog::savePreferences() {
UserActivityLogger::getInstance().changedDisplayName(displayNameStr);
shouldDispatchIdentityPacket = true;
}
auto AVATAR_FILE_EXTENSION = ".fst";
QUrl faceModelURL(ui.faceURLEdit->text());
QString faceModelURLString = faceModelURL.toString();
if (faceModelURLString != _faceURLString) {
if (faceModelURLString.isEmpty() || faceModelURLString.toLower().endsWith(".fst")) {
if (faceModelURLString.isEmpty() || faceModelURLString.toLower().contains(AVATAR_FILE_EXTENSION)) {
// change the faceModelURL in the profile, it will also update this user's BlendFace
myAvatar->setFaceModelURL(faceModelURL);
UserActivityLogger::getInstance().changedModel("head", faceModelURLString);
@ -209,7 +235,7 @@ void PreferencesDialog::savePreferences() {
QUrl skeletonModelURL(ui.skeletonURLEdit->text());
QString skeletonModelURLString = skeletonModelURL.toString();
if (skeletonModelURLString != _skeletonURLString) {
if (skeletonModelURLString.isEmpty() || skeletonModelURLString.toLower().endsWith(".fst")) {
if (skeletonModelURLString.isEmpty() || skeletonModelURLString.toLower().contains(AVATAR_FILE_EXTENSION)) {
// change the skeletonModelURL in the profile, it will also update this user's Body
myAvatar->setSkeletonModelURL(skeletonModelURL);
UserActivityLogger::getInstance().changedModel("skeleton", skeletonModelURLString);

View file

@ -17,6 +17,8 @@
#include <QDialog>
#include <QString>
#include "scripting/WebWindowClass.h"
class PreferencesDialog : public QDialog {
Q_OBJECT
@ -36,6 +38,8 @@ private:
QString _faceURLString;
QString _skeletonURLString;
QString _displayNameString;
WebWindowClass* _marketplaceWindow = NULL;
private slots:
void accept();
@ -43,6 +47,8 @@ private slots:
void setSkeletonUrl(QString modelUrl);
void openSnapshotLocationBrowser();
void openScriptsLocationBrowser();
void faceURLChanged(const QString& newValue);
void skeletonURLChanged(const QString& newValue);
};

View file

@ -447,7 +447,7 @@ border-radius: 4px; padding-top: 1px;</string>
</widget>
</item>
<item>
<widget class="QLabel" name="label">
<widget class="QLabel" name="accountLabel">
<property name="font">
<font>
<family>Helvetica,Arial,sans-serif</family>
@ -456,9 +456,12 @@ border-radius: 4px; padding-top: 1px;</string>
</property>
<property name="text">
<string>&lt;style type=&quot;text/css&quot;&gt;
a { text-decoration: none; color: #267077;}
a { text-decoration: none; color: #267077; margin:0;padding:0;}
#create {font-weight:bold;}
p {margin:5px 0;}
&lt;/style&gt;
&lt;a href=&quot;https://metaverse.highfidelity.com/password/new&quot;&gt;Recover password?&lt;/a&gt;</string>
&lt;p&gt;&lt;a id=&quot;create&quot; href=&quot;%1&quot;&gt;Create account&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;%2&quot;&gt;Recover password&lt;/a&gt;&lt;/p&gt;</string>
</property>
<property name="openExternalLinks">
<bool>true</bool>

View file

@ -21,9 +21,12 @@
#include "Shape.h"
QHash<QString, float> COMMENT_SCALE_HINTS;
class OBJTokenizer {
public:
OBJTokenizer(QIODevice* device) : _device(device), _pushedBackToken(-1) { }
OBJTokenizer(QIODevice* device);
enum SpecialToken {
NO_TOKEN = -1,
NO_PUSHBACKED_TOKEN = -1,
@ -46,6 +49,15 @@ private:
};
OBJTokenizer::OBJTokenizer(QIODevice* device) : _device(device), _pushedBackToken(-1) {
// This is a list of comments that exports use to hint at scaling
if (COMMENT_SCALE_HINTS.isEmpty()) {
COMMENT_SCALE_HINTS["This file uses centimeters as units"] = 1.0f / 100.0f;
COMMENT_SCALE_HINTS["This file uses millimeters as units"] = 1.0f / 1000.0f;
}
}
int OBJTokenizer::nextToken() {
if (_pushedBackToken != NO_PUSHBACKED_TOKEN) {
int token = _pushedBackToken;
@ -60,7 +72,7 @@ int OBJTokenizer::nextToken() {
}
switch (ch) {
case '#': {
_comment = _device->readLine(); // skip the comment
_comment = _device->readLine(); // stash comment for a future call to getComment
qDebug() << "COMMENT:" << _comment;
return COMMENT_TOKEN;
}
@ -136,11 +148,15 @@ bool parseOBJGroup(OBJTokenizer &tokenizer, const QVariantHash& mapping,
while (true) {
int tokenType = tokenizer.nextToken();
if (tokenType == OBJTokenizer::COMMENT_TOKEN) {
if (tokenizer.getComment().contains("This file uses centimeters as units")) {
scaleGuess = 1.0f / 100.0f;
}
if (tokenizer.getComment().contains("This file uses millimeters as units")) {
scaleGuess = 1.0f / 1000.0f;
// loop through the list of known comments which suggest a scaling factor.
// if we find one, save the scaling hint into scaleGuess
QString comment = tokenizer.getComment();
QHashIterator<QString, float> i(COMMENT_SCALE_HINTS);
while (i.hasNext()) {
i.next();
if (comment.contains(i.key())) {
scaleGuess = i.value();
}
}
continue;
}

View file

@ -92,8 +92,8 @@ void EarthSunModel::setSurfaceOrientation(const Quat& orientation) {
double moduloRange(double val, double minVal, double maxVal) {
double range = maxVal - minVal;
double rval = (val - minVal) / range;
double intval;
return modf(rval, &intval) * range + minVal;
rval = rval - floor(rval);
return rval * range + minVal;
}
const float MAX_LONGITUDE = 180.0f;

View file

@ -212,6 +212,9 @@ btVector3 CharacterController::perpindicularComponent(const btVector3& direction
}
const btVector3 LOCAL_UP_AXIS(0.0f, 1.0f, 0.0f);
const float DEFAULT_GRAVITY = 5.0f;
const float TERMINAL_VELOCITY = 55.0f;
const float JUMP_SPEED = 5.0f;
CharacterController::CharacterController(AvatarData* avatarData) {
assert(avatarData);
@ -226,9 +229,9 @@ CharacterController::CharacterController(AvatarData* avatarData) {
_velocityTimeInterval = 0.0f;
_verticalVelocity = 0.0f;
_verticalOffset = 0.0f;
_gravity = 5.0f; // slower than Earth's
_maxFallSpeed = 55.0f; // Terminal velocity of a sky diver in m/s.
_jumpSpeed = 5.0f;
_gravity = DEFAULT_GRAVITY; // slower than Earth's
_maxFallSpeed = TERMINAL_VELOCITY; // Terminal velocity of a sky diver in m/s.
_jumpSpeed = JUMP_SPEED;
_isOnGround = false;
_isJumping = false;
_isHovering = true;
@ -350,6 +353,7 @@ bool CharacterController::recoverFromPenetration(btCollisionWorld* collisionWorl
return penetration;
}
void CharacterController::scanDown(btCollisionWorld* world) {
// we test with downward raycast and if we don't find floor close enough then turn on "hover"
btKinematicClosestNotMeRayResultCallback callback(_ghostObject);

View file

@ -35,7 +35,7 @@ btCollisionShape* ShapeManager::getShape(const ShapeInfo& info) {
// Very small or large objects are not supported.
float diagonal = 4.0f * glm::length2(info.getHalfExtents());
const float MIN_SHAPE_DIAGONAL_SQUARED = 3.0e-4f; // 1 cm cube
const float MAX_SHAPE_DIAGONAL_SQUARED = 3.0e4f; // 100 m cube
const float MAX_SHAPE_DIAGONAL_SQUARED = 3.0e6f; // 1000 m cube
if (diagonal < MIN_SHAPE_DIAGONAL_SQUARED || diagonal > MAX_SHAPE_DIAGONAL_SQUARED) {
// qDebug() << "ShapeManager::getShape -- not making shape due to size" << diagonal;
return NULL;

View file

@ -20,20 +20,47 @@ void SceneScriptingInterface::setStageLocation(float longitude, float latitude,
_skyStage->setOriginLocation(longitude, latitude, altitude);
}
float SceneScriptingInterface::getStageLocationLongitude() const {
return _skyStage->getOriginLongitude();
}
float SceneScriptingInterface::getStageLocationLatitude() const {
return _skyStage->getOriginLatitude();
}
float SceneScriptingInterface::getStageLocationAltitude() const {
return _skyStage->getOriginSurfaceAltitude();
}
void SceneScriptingInterface::setStageDayTime(float hour) {
_skyStage->setDayTime(hour);
}
float SceneScriptingInterface::getStageDayTime() const {
return _skyStage->getDayTime();
}
void SceneScriptingInterface::setStageYearTime(int day) {
_skyStage->setYearTime(day);
}
int SceneScriptingInterface::getStageYearTime() const {
return _skyStage->getYearTime();
}
void SceneScriptingInterface::setSunColor(const glm::vec3& color) {
_skyStage->setSunColor(color);
}
const glm::vec3& SceneScriptingInterface::getSunColor() const {
return _skyStage->getSunColor();
}
void SceneScriptingInterface::setSunIntensity(float intensity) {
_skyStage->setSunIntensity(intensity);
}
float SceneScriptingInterface::getSunIntensity() const {
return _skyStage->getSunIntensity();
}
model::SunSkyStagePointer SceneScriptingInterface::getSkyStage() const {
return _skyStage;

View file

@ -24,12 +24,21 @@ class SceneScriptingInterface : public QObject, public Dependency {
public:
Q_INVOKABLE void setStageOrientation(const glm::quat& orientation);
Q_INVOKABLE void setStageLocation(float longitude, float latitude, float altitude);
Q_INVOKABLE float getStageLocationLongitude() const;
Q_INVOKABLE float getStageLocationLatitude() const;
Q_INVOKABLE float getStageLocationAltitude() const;
Q_INVOKABLE void setStageDayTime(float hour);
Q_INVOKABLE float getStageDayTime() const;
Q_INVOKABLE void setStageYearTime(int day);
Q_INVOKABLE int getStageYearTime() const;
Q_INVOKABLE void setSunColor(const glm::vec3& color);
Q_INVOKABLE const glm::vec3& getSunColor() const;
Q_INVOKABLE void setSunIntensity(float intensity);
Q_INVOKABLE float getSunIntensity() const;
model::SunSkyStagePointer getSkyStage() const;

View file

@ -305,8 +305,6 @@ void ScriptEngine::init() {
_isInitialized = true;
auto sceneScriptingInterface = DependencyManager::set<SceneScriptingInterface>();
auto entityScriptingInterface = DependencyManager::get<EntityScriptingInterface>();
entityScriptingInterface->init();
@ -350,7 +348,6 @@ void ScriptEngine::init() {
registerGlobalObject("Vec3", &_vec3Library);
registerGlobalObject("Uuid", &_uuidLibrary);
registerGlobalObject("AnimationCache", DependencyManager::get<AnimationCache>().data());
registerGlobalObject("Scene", DependencyManager::get<SceneScriptingInterface>().data());
// constants
globalObject().setProperty("TREE_SCALE", newVariant(QVariant(TREE_SCALE)));

View file

@ -13,15 +13,14 @@
//
#include <QEventLoop>
#include <QFile>
#include <qurlquery.h>
#include <AccountManager.h>
#include <NetworkAccessManager.h>
#include <NetworkingConstants.h>
#include <AccountManager.h>
#include "XMLHttpRequestClass.h"
#include "ScriptEngine.h"
#include "XMLHttpRequestClass.h"
const QString METAVERSE_API_URL = NetworkingConstants::METAVERSE_SERVER_URL.toString() + "/api/";
@ -42,7 +41,6 @@ XMLHttpRequestClass::XMLHttpRequestClass(QScriptEngine* engine) :
_onReadyStateChange(QScriptValue::NullValue),
_readyState(XMLHttpRequestClass::UNSENT),
_errorCode(QNetworkReply::NoError),
_file(NULL),
_timeout(0),
_timer(this),
_numRedirects(0) {
@ -63,22 +61,6 @@ QScriptValue XMLHttpRequestClass::getStatus() const {
if (_reply) {
return QScriptValue(_reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt());
}
if(_url.isLocalFile()) {
switch (_errorCode) {
case QNetworkReply::NoError:
return QScriptValue(200);
case QNetworkReply::ContentNotFoundError:
return QScriptValue(404);
case QNetworkReply::ContentConflictError:
return QScriptValue(409);
case QNetworkReply::TimeoutError:
return QScriptValue(408);
case QNetworkReply::ContentOperationNotPermittedError:
return QScriptValue(501);
default:
break;
}
}
return QScriptValue(0);
}
@ -86,22 +68,6 @@ QString XMLHttpRequestClass::getStatusText() const {
if (_reply) {
return _reply->attribute(QNetworkRequest::HttpReasonPhraseAttribute).toString();
}
if (_url.isLocalFile()) {
switch (_errorCode) {
case QNetworkReply::NoError:
return "OK";
case QNetworkReply::ContentNotFoundError:
return "Not Found";
case QNetworkReply::ContentConflictError:
return "Conflict";
case QNetworkReply::TimeoutError:
return "Timeout";
case QNetworkReply::ContentOperationNotPermittedError:
return "Not Implemented";
default:
break;
}
}
return "";
}
@ -147,13 +113,6 @@ QScriptValue XMLHttpRequestClass::getAllResponseHeaders() const {
}
return QString(headers.data());
}
if (_url.isLocalFile()) {
QString headers = QString("Content-Type: application/octet-stream\n");
headers.append("Content-Length: ");
headers.append(QString("%1").arg(_rawResponseData.length()));
headers.append("\n");
return headers;
}
return QScriptValue("");
}
@ -161,14 +120,6 @@ QScriptValue XMLHttpRequestClass::getResponseHeader(const QString& name) const {
if (_reply && _reply->hasRawHeader(name.toLatin1())) {
return QScriptValue(QString(_reply->rawHeader(name.toLatin1())));
}
if (_url.isLocalFile()) {
if (name.toLower() == "content-type") {
return QString("application/octet-stream");
}
if (name.toLower() == "content-length") {
return QString("%1").arg(_rawResponseData.length());
}
}
return QScriptValue::NullValue;
}
@ -188,47 +139,24 @@ void XMLHttpRequestClass::open(const QString& method, const QString& url, bool a
_url.setUrl(url);
_async = async;
if (_url.isLocalFile()) {
if (_method.toUpper() == "GET" && !_async && username.isEmpty() && password.isEmpty()) {
_file = new QFile(_url.toLocalFile());
if (!_file->exists()) {
qDebug() << "Can't find file " << _url.fileName();
abortRequest();
_errorCode = QNetworkReply::ContentNotFoundError;
setReadyState(DONE);
emit requestComplete();
} else if (!_file->open(QIODevice::ReadOnly)) {
qDebug() << "Can't open file " << _url.fileName();
abortRequest();
_errorCode = QNetworkReply::ContentConflictError;
setReadyState(DONE);
emit requestComplete();
} else {
setReadyState(OPENED);
}
} else {
notImplemented();
}
} else {
if (url.toLower().left(METAVERSE_API_URL.length()) == METAVERSE_API_URL) {
AccountManager& accountManager = AccountManager::getInstance();
if (url.toLower().left(METAVERSE_API_URL.length()) == METAVERSE_API_URL) {
AccountManager& accountManager = AccountManager::getInstance();
if (accountManager.hasValidAccessToken()) {
QUrlQuery urlQuery(_url.query());
urlQuery.addQueryItem("access_token", accountManager.getAccountInfo().getAccessToken().token);
_url.setQuery(urlQuery);
}
if (accountManager.hasValidAccessToken()) {
QUrlQuery urlQuery(_url.query());
urlQuery.addQueryItem("access_token", accountManager.getAccountInfo().getAccessToken().token);
_url.setQuery(urlQuery);
}
}
if (!username.isEmpty()) {
_url.setUserName(username);
}
if (!password.isEmpty()) {
_url.setPassword(password);
}
_request.setUrl(_url);
setReadyState(OPENED);
}
if (!username.isEmpty()) {
_url.setUserName(username);
}
if (!password.isEmpty()) {
_url.setPassword(password);
}
_request.setUrl(_url);
setReadyState(OPENED);
}
}
@ -239,23 +167,18 @@ void XMLHttpRequestClass::send() {
void XMLHttpRequestClass::send(const QScriptValue& data) {
if (_readyState == OPENED && !_reply) {
if (!data.isNull()) {
if (_url.isLocalFile()) {
notImplemented();
return;
_sendData = new QBuffer(this);
if (data.isObject()) {
QByteArray ba = qscriptvalue_cast<QByteArray>(data);
_sendData->setData(ba);
} else {
_sendData = new QBuffer(this);
if (data.isObject()) {
QByteArray ba = qscriptvalue_cast<QByteArray>(data);
_sendData->setData(ba);
} else {
_sendData->setData(data.toString().toUtf8());
}
_sendData->setData(data.toString().toUtf8());
}
}
doSend();
if (!_async && !_url.isLocalFile()) {
if (!_async) {
QEventLoop loop;
connect(this, SIGNAL(requestComplete()), &loop, SLOT(quit()));
loop.exec();
@ -265,23 +188,13 @@ void XMLHttpRequestClass::send(const QScriptValue& data) {
void XMLHttpRequestClass::doSend() {
if (!_url.isLocalFile()) {
_reply = NetworkAccessManager::getInstance().sendCustomRequest(_request, _method.toLatin1(), _sendData);
connectToReply(_reply);
}
_reply = NetworkAccessManager::getInstance().sendCustomRequest(_request, _method.toLatin1(), _sendData);
connectToReply(_reply);
if (_timeout > 0) {
_timer.start(_timeout);
connect(&_timer, SIGNAL(timeout()), this, SLOT(requestTimeout()));
}
if (_url.isLocalFile()) {
setReadyState(HEADERS_RECEIVED);
setReadyState(LOADING);
_rawResponseData = _file->readAll();
_file->close();
requestFinished();
}
}
void XMLHttpRequestClass::requestTimeout() {
@ -300,16 +213,10 @@ void XMLHttpRequestClass::requestError(QNetworkReply::NetworkError code) {
void XMLHttpRequestClass::requestFinished() {
disconnect(&_timer, SIGNAL(timeout()), this, SLOT(requestTimeout()));
if (!_url.isLocalFile()) {
_errorCode = _reply->error();
} else {
_errorCode = QNetworkReply::NoError;
}
_errorCode = _reply->error();
if (_errorCode == QNetworkReply::NoError) {
if (!_url.isLocalFile()) {
_rawResponseData.append(_reply->readAll());
}
_rawResponseData.append(_reply->readAll());
if (_responseType == "json") {
_responseData = _engine->evaluate("(" + QString(_rawResponseData.data()) + ")");
@ -338,19 +245,6 @@ void XMLHttpRequestClass::abortRequest() {
_reply->deleteLater();
_reply = NULL;
}
if (_file != NULL) {
_file->close();
_file = NULL;
}
}
void XMLHttpRequestClass::notImplemented() {
abortRequest();
//_errorCode = QNetworkReply::OperationNotImplementedError; TODO: Use this status code when update to Qt 5.3
_errorCode = QNetworkReply::ContentOperationNotPermittedError;
setReadyState(DONE);
emit requestComplete();
}
void XMLHttpRequestClass::connectToReply(QNetworkReply* reply) {

View file

@ -97,7 +97,6 @@ private:
void connectToReply(QNetworkReply* reply);
void disconnectFromReply(QNetworkReply* reply);
void abortRequest();
void notImplemented();
QScriptEngine* _engine;
bool _async;
@ -113,7 +112,6 @@ private:
QScriptValue _onReadyStateChange;
ReadyState _readyState;
QNetworkReply::NetworkError _errorCode;
QFile* _file;
int _timeout;
QTimer _timer;
int _numRedirects;

View file

@ -38,6 +38,15 @@ bool vhacd::VHACDUtil::loadFBX(const QString filename, vhacd::LoadFBXResults *re
}
std::cout << "-------------------\n";
foreach (const FBXMesh& mesh, geometry.meshes) {
foreach (const FBXMeshPart &meshPart, mesh.parts) {
std::cout << meshPart.triangleIndices.size() << " ";
}
}
std::cout << "\n";
//results->meshCount = geometry.meshes.count();
// qDebug() << "read in" << geometry.meshes.count() << "meshes";
@ -52,14 +61,29 @@ bool vhacd::VHACDUtil::loadFBX(const QString filename, vhacd::LoadFBXResults *re
vertices.append(glm::vec3(mesh.modelTransform * glm::vec4(vertex, 1.0f)));
}
//get the triangle indices for each mesh
// get the triangle indices for each mesh
QVector<int> triangles;
foreach(FBXMeshPart part, mesh.parts){
QVector<int> indices = part.triangleIndices;
foreach(FBXMeshPart meshPart, mesh.parts){
QVector<int> indices = meshPart.triangleIndices;
triangles += indices;
unsigned int quadCount = meshPart.quadIndices.size() / 4;
for (unsigned int i = 0; i < quadCount; i++) {
unsigned int p0Index = meshPart.quadIndices[i * 4];
unsigned int p1Index = meshPart.quadIndices[i * 4 + 1];
unsigned int p2Index = meshPart.quadIndices[i * 4 + 2];
unsigned int p3Index = meshPart.quadIndices[i * 4 + 3];
// split each quad into two triangles
triangles.append(p0Index);
triangles.append(p1Index);
triangles.append(p2Index);
triangles.append(p0Index);
triangles.append(p2Index);
triangles.append(p3Index);
}
}
//only read meshes with triangles
// only read meshes with triangles
if (triangles.count() <= 0){
continue;
}
@ -131,15 +155,16 @@ void vhacd::VHACDUtil::fattenMeshes(vhacd::LoadFBXResults *meshes, vhacd::LoadFB
auto d1 = p2 - p0;
auto cp = glm::cross(d0, d1);
cp = 5.0f * glm::normalize(cp);
cp = -2.0f * glm::normalize(cp);
auto p3 = p0 + cp;
auto p4 = p1 + cp;
auto p5 = p2 + cp;
auto n = results->perMeshVertices.size();
results->perMeshVertices[i] << p3 << p4 << p5;
results->perMeshTriangleIndices[i] << n << n+1 << n+2;
results->perMeshVertices[i] << p3;
results->perMeshTriangleIndices[i] << triangles[j] << n << triangles[j + 1];
results->perMeshTriangleIndices[i] << triangles[j + 1] << n << triangles[j + 2];
results->perMeshTriangleIndices[i] << triangles[j + 2] << n << triangles[j];
}
results->meshCount++;
@ -148,21 +173,26 @@ void vhacd::VHACDUtil::fattenMeshes(vhacd::LoadFBXResults *meshes, vhacd::LoadFB
bool vhacd::VHACDUtil::computeVHACD(vhacd::LoadFBXResults *meshes, VHACD::IVHACD::Parameters params,
bool vhacd::VHACDUtil::computeVHACD(vhacd::LoadFBXResults *inMeshes, VHACD::IVHACD::Parameters params,
vhacd::ComputeResults *results,
int startMeshIndex, int endMeshIndex, float minimumMeshSize) const {
int startMeshIndex, int endMeshIndex, float minimumMeshSize,
bool fattenFaces) const {
// vhacd::LoadFBXResults *meshes = new vhacd::LoadFBXResults;
vhacd::LoadFBXResults *meshes = new vhacd::LoadFBXResults;
// combineMeshes(inMeshes, meshes);
// vhacd::LoadFBXResults *meshes = new vhacd::LoadFBXResults;
// fattenMeshes(inMeshes, meshes);
if (fattenFaces) {
fattenMeshes(inMeshes, meshes);
} else {
meshes = inMeshes;
}
VHACD::IVHACD * interfaceVHACD = VHACD::CreateVHACD();
int meshCount = meshes->meshCount;
int count = 0;
std::cout << "Performing V-HACD computation on " << meshCount << " meshes ..... " << std::endl;
if (startMeshIndex < 0) {
startMeshIndex = 0;
@ -171,6 +201,14 @@ bool vhacd::VHACDUtil::computeVHACD(vhacd::LoadFBXResults *meshes, VHACD::IVHACD
endMeshIndex = meshCount;
}
for (int i = 0; i < meshCount; i++) {
std::cout << meshes->perMeshTriangleIndices.at(i).size() << " ";
}
std::cout << "\n";
std::cout << "Performing V-HACD computation on " << endMeshIndex - startMeshIndex << " meshes ..... " << std::endl;
for (int i = startMeshIndex; i < endMeshIndex; i++){
qDebug() << "--------------------";
std::vector<glm::vec3> vertices = meshes->perMeshVertices.at(i).toStdVector();
@ -211,7 +249,6 @@ bool vhacd::VHACDUtil::computeVHACD(vhacd::LoadFBXResults *meshes, VHACD::IVHACD
}
hull.m_points = m_points_copy;
int *m_triangles_copy = new int[hull.m_nTriangles * 3];
// std::copy(std::begin(hull.m_triangles), std::end(hull.m_triangles), std::begin(m_triangles_copy));
for (unsigned int i=0; i<hull.m_nTriangles * 3; i++) {

View file

@ -43,7 +43,8 @@ namespace vhacd {
void combineMeshes(vhacd::LoadFBXResults *meshes, vhacd::LoadFBXResults *results) const;
void fattenMeshes(vhacd::LoadFBXResults *meshes, vhacd::LoadFBXResults *results) const;
bool computeVHACD(vhacd::LoadFBXResults *meshes, VHACD::IVHACD::Parameters params,
vhacd::ComputeResults *results, int startMeshIndex, int endMeshIndex, float minimumMeshSize) const;
vhacd::ComputeResults *results, int startMeshIndex, int endMeshIndex, float minimumMeshSize,
bool fattenFaces) const;
~VHACDUtil();
};

View file

@ -92,6 +92,9 @@ VHACDUtilApp::VHACDUtilApp(int argc, char* argv[]) :
const QCommandLineOption outputOneMeshOption("1", "output hulls as single mesh");
parser.addOption(outputOneMeshOption);
const QCommandLineOption fattenFacesOption("f", "fatten faces");
parser.addOption(fattenFacesOption);
const QCommandLineOption inputFilenameOption("i", "input file", "filename.fbx");
parser.addOption(inputFilenameOption);
@ -107,6 +110,15 @@ VHACDUtilApp::VHACDUtilApp(int argc, char* argv[]) :
const QCommandLineOption minimumMeshSizeOption("m", "minimum mesh size to consider", "0");
parser.addOption(minimumMeshSizeOption);
const QCommandLineOption vHacdResolutionOption("resolution", "v-hacd resolution", "100000");
parser.addOption(vHacdResolutionOption);
const QCommandLineOption vHacdDepthOption("depth", "v-hacd depth", "20");
parser.addOption(vHacdDepthOption);
const QCommandLineOption vHacdDeltaOption("delta", "v-hacd delta", "0.05");
parser.addOption(vHacdDeltaOption);
if (!parser.parse(QCoreApplication::arguments())) {
qCritical() << parser.errorText() << endl;
@ -119,17 +131,15 @@ VHACDUtilApp::VHACDUtilApp(int argc, char* argv[]) :
Q_UNREACHABLE();
}
bool fattenFaces = parser.isSet(fattenFacesOption);
bool outputOneMesh = parser.isSet(outputOneMeshOption);
QString inputFilename;
// check for an assignment pool passed on the command line or in the config
if (parser.isSet(inputFilenameOption)) {
inputFilename = parser.value(inputFilenameOption);
}
QString outputFilename;
// check for an assignment pool passed on the command line or in the config
if (parser.isSet(outputFilenameOption)) {
outputFilename = parser.value(outputFilenameOption);
}
@ -148,30 +158,43 @@ VHACDUtilApp::VHACDUtilApp(int argc, char* argv[]) :
}
int startMeshIndex = -1;
// check for an assignment pool passed on the command line or in the config
if (parser.isSet(startMeshIndexOption)) {
startMeshIndex = parser.value(startMeshIndexOption).toInt();
}
int endMeshIndex = -1;
// check for an assignment pool passed on the command line or in the config
if (parser.isSet(endMeshIndexOption)) {
endMeshIndex = parser.value(endMeshIndexOption).toInt();
}
float minimumMeshSize = 0.0f;
// check for an assignment pool passed on the command line or in the config
if (parser.isSet(minimumMeshSizeOption)) {
minimumMeshSize = parser.value(minimumMeshSizeOption).toFloat();
}
int vHacdResolution = 100000;
if (parser.isSet(vHacdResolutionOption)) {
vHacdResolution = parser.value(vHacdResolutionOption).toInt();
}
int vHacdDepth = 20;
if (parser.isSet(vHacdDepthOption)) {
vHacdDepth = parser.value(vHacdDepthOption).toInt();
}
float vHacdDelta = 0.05;
if (parser.isSet(vHacdDeltaOption)) {
vHacdDelta = parser.value(vHacdDeltaOption).toFloat();
}
//set parameters for V-HACD
params.m_callback = &pCallBack; //progress callback
params.m_resolution = 100000; // 100000
params.m_depth = 20; // 20
params.m_resolution = vHacdResolution; // 100000
params.m_depth = vHacdDepth; // 20
params.m_concavity = 0.001; // 0.001
params.m_delta = 0.05; // 0.05
params.m_delta = vHacdDelta; // 0.05
params.m_planeDownsampling = 4; // 4
params.m_convexhullDownsampling = 4; // 4
params.m_alpha = 0.05; // 0.05 // controls the bias toward clipping along symmetry planes
@ -198,8 +221,7 @@ VHACDUtilApp::VHACDUtilApp(int argc, char* argv[]) :
//perform vhacd computation
begin = std::chrono::high_resolution_clock::now();
if (!vUtil.computeVHACD(&fbx, params, &results, startMeshIndex, endMeshIndex, minimumMeshSize)) {
if (!vUtil.computeVHACD(&fbx, params, &results, startMeshIndex, endMeshIndex, minimumMeshSize, fattenFaces)) {
cout << "Compute Failed...";
}
end = std::chrono::high_resolution_clock::now();