mirror of
https://github.com/overte-org/overte.git
synced 2025-08-17 19:22:45 +02:00
Merge branch 'master' into oculus_old_renderer
This commit is contained in:
commit
3b77118baa
15 changed files with 193 additions and 974 deletions
|
@ -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",
|
||||
|
||||
|
|
|
@ -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;
|
||||
}());
|
|
@ -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");
|
||||
}
|
||||
|
|
|
@ -3733,22 +3733,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;
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
|
||||
};
|
||||
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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++) {
|
||||
|
|
|
@ -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();
|
||||
};
|
||||
|
||||
|
|
|
@ -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();
|
||||
|
|
Loading…
Reference in a new issue