This commit is contained in:
Stephen Birarda 2014-05-15 10:45:45 -07:00
commit 491e915c93
55 changed files with 2213 additions and 179 deletions

4
.gitignore vendored
View file

@ -46,5 +46,9 @@ interface/resources/visage/*
interface/external/faceplus/*
!interface/external/faceplus/readme.txt
# Ignore PrioVR
interface/external/priovr/*
!interface/external/priovr/readme.txt
# Ignore interfaceCache for Linux users
interface/interfaceCache/

View file

@ -33,6 +33,7 @@ link_hifi_library(particles ${TARGET_NAME} "${ROOT_DIR}")
link_hifi_library(models ${TARGET_NAME} "${ROOT_DIR}")
link_hifi_library(metavoxels ${TARGET_NAME} "${ROOT_DIR}")
link_hifi_library(networking ${TARGET_NAME} "${ROOT_DIR}")
link_hifi_library(animation ${TARGET_NAME} "${ROOT_DIR}")
link_hifi_library(script-engine ${TARGET_NAME} "${ROOT_DIR}")
link_hifi_library(embedded-webserver ${TARGET_NAME} "${ROOT_DIR}")

View file

@ -250,6 +250,12 @@ void Agent::run() {
_particleViewer.init();
_scriptEngine.getParticlesScriptingInterface()->setParticleTree(_particleViewer.getTree());
_scriptEngine.registerGlobalObject("ModelViewer", &_modelViewer);
JurisdictionListener* modelJL = _scriptEngine.getModelsScriptingInterface()->getJurisdictionListener();
_modelViewer.setJurisdictionListener(modelJL);
_modelViewer.init();
_scriptEngine.getModelsScriptingInterface()->setModelTree(_modelViewer.getTree());
_scriptEngine.setScriptContents(scriptContents);
_scriptEngine.run();
setFinished(true);

View file

@ -0,0 +1,42 @@
# Try to find the PrioVT library
#
# You must provide a PRIOVR_ROOT_DIR which contains lib and include directories
#
# Once done this will define
#
# PRIOVR_FOUND - system found PrioVR
# PRIOVR_INCLUDE_DIRS - the PrioVR include directory
# PRIOVR_LIBRARIES - Link this to use PrioVR
#
# Created on 5/12/2014 by Andrzej Kapolka
# Copyright (c) 2014 High Fidelity
#
if (PRIOVR_LIBRARIES AND PRIOVR_INCLUDE_DIRS)
# in cache already
set(PRIOVR_FOUND TRUE)
else (PRIOVR_LIBRARIES AND PRIOVR_INCLUDE_DIRS)
find_path(PRIOVR_INCLUDE_DIRS yei_skeletal_api.h ${PRIOVR_ROOT_DIR}/include)
if (WIN32)
find_library(PRIOVR_LIBRARIES Skeletal_API.lib ${PRIOVR_ROOT_DIR}/lib)
endif (WIN32)
if (PRIOVR_INCLUDE_DIRS AND PRIOVR_LIBRARIES)
set(PRIOVR_FOUND TRUE)
endif (PRIOVR_INCLUDE_DIRS AND PRIOVR_LIBRARIES)
if (PRIOVR_FOUND)
if (NOT PRIOVR_FIND_QUIETLY)
message(STATUS "Found PrioVR... ${PRIOVR_LIBRARIES}")
endif (NOT PRIOVR_FIND_QUIETLY)
else ()
if (PRIOVR_FIND_REQUIRED)
message(FATAL_ERROR "Could not find PrioVR")
endif (PRIOVR_FIND_REQUIRED)
endif ()
# show the PRIOVR_INCLUDE_DIRS and PRIOVR_LIBRARIES variables only in the advanced view
mark_as_advanced(PRIOVR_INCLUDE_DIRS PRIOVR_LIBRARIES)
endif (PRIOVR_LIBRARIES AND PRIOVR_INCLUDE_DIRS)

68
examples/Test.js Normal file
View file

@ -0,0 +1,68 @@
//
// Test.js
// examples
//
// Created by Ryan Huffman on 5/4/14
// Copyright 2014 High Fidelity, Inc.
//
// This provides very basic unit testing functionality.
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
test = function(name, func) {
print("Running test: " + name);
var unitTest = new UnitTest(name, func);
try {
unitTest.run();
print(" Success: " + unitTest.numAssertions + " assertions passed");
} catch (error) {
print(" Failure: " + error.message);
}
};
AssertionException = function(expected, actual, message) {
print("Creating exception");
this.message = message + "\n: " + actual + " != " + expected;
this.name = 'AssertionException';
};
UnitTest = function(name, func) {
this.numAssertions = 0;
this.func = func;
};
UnitTest.prototype.run = function() {
this.func();
};
UnitTest.prototype.assertNotEquals = function(expected, actual, message) {
this.numAssertions++;
if (expected == actual) {
throw new AssertionException(expected, actual, message);
}
};
UnitTest.prototype.assertEquals = function(expected, actual, message) {
this.numAssertions++;
if (expected != actual) {
throw new AssertionException(expected, actual, message);
}
};
UnitTest.prototype.assertHasProperty = function(property, actual, message) {
this.numAssertions++;
if (actual[property] === undefined) {
throw new AssertionException(property, actual, message);
}
};
UnitTest.prototype.assertNull = function(value, message) {
this.numAssertions++;
if (value !== null) {
throw new AssertionException(value, null, message);
}
};

View file

@ -0,0 +1,124 @@
//
// animatedModelExample.js
// examples
//
// Created by Brad Hefta-Gaub on 12/31/13.
// Copyright 2014 High Fidelity, Inc.
//
// This is an example script that demonstrates creating and editing a model
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
var count = 0;
var moveUntil = 6000;
var stopAfter = moveUntil + 100;
var pitch = 0.0;
var yaw = 0.0;
var roll = 0.0;
var rotation = Quat.fromPitchYawRollDegrees(pitch, yaw, roll)
var originalProperties = {
position: { x: 10,
y: 0,
z: 0 },
radius : 1,
color: { red: 0,
green: 255,
blue: 0 },
modelURL: "http://www.fungibleinsight.com/faces/beta.fst",
modelRotation: rotation,
animationURL: "http://www.fungibleinsight.com/faces/gangnam_style_2.fbx",
animationIsPlaying: true,
};
var modelID = Models.addModel(originalProperties);
print("Models.addModel()... modelID.creatorTokenID = " + modelID.creatorTokenID);
var isPlaying = true;
var playPauseEveryWhile = 360;
var animationFPS = 30;
var adjustFPSEveryWhile = 120;
var resetFrameEveryWhile = 600;
function moveModel(deltaTime) {
var somethingChanged = false;
if (count % playPauseEveryWhile == 0) {
isPlaying = !isPlaying;
print("isPlaying=" + isPlaying);
somethingChanged = true;
}
if (count % adjustFPSEveryWhile == 0) {
if (animationFPS == 30) {
animationFPS = 10;
} else if (animationFPS == 10) {
animationFPS = 60;
} else if (animationFPS == 60) {
animationFPS = 30;
}
print("animationFPS=" + animationFPS);
isPlaying = true;
print("always start playing if we change the FPS -- isPlaying=" + isPlaying);
somethingChanged = true;
}
if (count % resetFrameEveryWhile == 0) {
resetFrame = true;
somethingChanged = true;
}
if (count >= moveUntil) {
// delete it...
if (count == moveUntil) {
print("calling Models.deleteModel()");
Models.deleteModel(modelID);
}
// stop it...
if (count >= stopAfter) {
print("calling Script.stop()");
Script.stop();
}
count++;
return; // break early
}
count++;
//print("modelID.creatorTokenID = " + modelID.creatorTokenID);
if (somethingChanged) {
var newProperties = {
animationIsPlaying: isPlaying,
animationFPS: animationFPS,
};
if (resetFrame) {
print("resetting the frame!");
newProperties.animationFrameIndex = 0;
resetFrame = false;
}
Models.editModel(modelID, newProperties);
}
}
// register the call back so it fires before each data send
Script.update.connect(moveModel);
Script.scriptEnding.connect(function () {
print("cleaning up...");
print("modelID="+ modelID.creatorTokenID + ", id:" + modelID.id);
Models.deleteModel(modelID);
});

View file

@ -9,11 +9,16 @@
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
Script.include("toolBars.js");
var windowDimensions = Controller.getViewportDimensions();
var toolIconUrl = "http://highfidelity-public.s3-us-west-1.amazonaws.com/images/tools/";
var toolHeight = 50;
var toolWidth = 50;
var LASER_WIDTH = 4;
var LASER_COLOR = { red: 255, green: 0, blue: 0 };
var LASER_LENGTH_FACTOR = 1.5;
var LASER_LENGTH_FACTOR = 5;
var LEFT = 0;
var RIGHT = 1;
@ -33,24 +38,7 @@ var modelURLs = [
"http://highfidelity-public.s3-us-west-1.amazonaws.com/meshes/slimer.fbx",
];
var toolIconUrl = "http://highfidelity-public.s3-us-west-1.amazonaws.com/images/tools/";
var numberOfTools = 1;
var toolHeight = 50;
var toolWidth = 50;
var toolVerticalSpacing = 4;
var toolsHeight = toolHeight * numberOfTools + toolVerticalSpacing * (numberOfTools - 1);
var toolsX = windowDimensions.x - 8 - toolWidth;
var toolsY = (windowDimensions.y - toolsHeight) / 2;
var firstModel = Overlays.addOverlay("image", {
x: 0, y: 0, width: toolWidth, height: toolHeight,
subImage: { x: 0, y: toolHeight, width: toolWidth, height: toolHeight },
imageURL: toolIconUrl + "voxel-tool.svg",
x: toolsX, y: toolsY + ((toolHeight + toolVerticalSpacing) * 0), width: toolWidth, height: toolHeight,
visible: true,
alpha: 0.9
});
var toolBar;
function controller(wichSide) {
this.side = wichSide;
@ -206,6 +194,13 @@ function controller(wichSide) {
});
}
this.hideLaser = function() {
Overlays.editOverlay(this.laser, { visible: false });
Overlays.editOverlay(this.ball, { visible: false });
Overlays.editOverlay(this.leftRight, { visible: false });
Overlays.editOverlay(this.topDown, { visible: false });
}
this.moveModel = function () {
if (this.grabbing) {
var newPosition = Vec3.sum(this.palmPosition,
@ -345,67 +340,272 @@ function moveModels() {
rightController.moveModel();
}
var hydraConnected = false;
function checkController(deltaTime) {
var numberOfButtons = Controller.getNumberOfButtons();
var numberOfTriggers = Controller.getNumberOfTriggers();
var numberOfSpatialControls = Controller.getNumberOfSpatialControls();
var controllersPerTrigger = numberOfSpatialControls / numberOfTriggers;
moveOverlays();
// this is expected for hydras
if (!(numberOfButtons==12 && numberOfTriggers == 2 && controllersPerTrigger == 2)) {
//print("no hydra connected?");
return; // bail if no hydra
if (numberOfButtons==12 && numberOfTriggers == 2 && controllersPerTrigger == 2) {
if (!hydraConnected) {
hydraConnected = true;
}
leftController.update();
rightController.update();
moveModels();
} else {
if (hydraConnected) {
hydraConnected = false;
leftController.hideLaser();
rightController.hideLaser();
}
}
leftController.update();
rightController.update();
moveModels();
moveOverlays();
}
function initToolBar() {
toolBar = new ToolBar(0, 0, ToolBar.VERTICAL);
// New Model
newModel = toolBar.addTool({
imageURL: toolIconUrl + "voxel-tool.svg",
subImage: { x: 0, y: Tool.IMAGE_WIDTH, width: Tool.IMAGE_WIDTH, height: Tool.IMAGE_HEIGHT },
width: toolWidth, height: toolHeight,
visible: true,
alpha: 0.9
});
}
function moveOverlays() {
windowDimensions = Controller.getViewportDimensions();
toolsX = windowDimensions.x - 8 - toolWidth;
toolsY = (windowDimensions.y - toolsHeight) / 2;
Overlays.editOverlay(firstModel, {
x: toolsX, y: toolsY + ((toolHeight + toolVerticalSpacing) * 0), width: toolWidth, height: toolHeight,
});
}
function mousePressEvent(event) {
var clickedOverlay = Overlays.getOverlayAtPoint({x: event.x, y: event.y});
var url;
if (clickedOverlay == firstModel) {
url = Window.prompt("Model url", modelURLs[Math.floor(Math.random() * modelURLs.length)]);
if (url == null) {
return; }
} else {
print("Didn't click on anything");
if (typeof(toolBar) === 'undefined') {
initToolBar();
} else if (windowDimensions.x == Controller.getViewportDimensions().x &&
windowDimensions.y == Controller.getViewportDimensions().y) {
return;
}
var position = Vec3.sum(MyAvatar.position, Vec3.multiply(Quat.getFront(MyAvatar.orientation), SPAWN_DISTANCE));
Models.addModel({ position: position,
radius: radiusDefault,
modelURL: url
});
windowDimensions = Controller.getViewportDimensions();
var toolsX = windowDimensions.x - 8 - toolBar.width;
var toolsY = (windowDimensions.y - toolBar.height) / 2;
toolBar.move(toolsX, toolsY);
}
var modelSelected = false;
var selectedModelID;
var selectedModelProperties;
var mouseLastPosition;
var orientation;
var intersection;
var SCALE_FACTOR = 200.0;
var TRANSLATION_FACTOR = 100.0;
var ROTATION_FACTOR = 100.0;
function rayPlaneIntersection(pickRay, point, normal) {
var d = -Vec3.dot(point, normal);
var t = -(Vec3.dot(pickRay.origin, normal) + d) / Vec3.dot(pickRay.direction, normal);
return Vec3.sum(pickRay.origin, Vec3.multiply(pickRay.direction, t));
}
function mousePressEvent(event) {
mouseLastPosition = { x: event.x, y: event.y };
modelSelected = false;
var clickedOverlay = Overlays.getOverlayAtPoint({x: event.x, y: event.y});
if (newModel == toolBar.clicked(clickedOverlay)) {
var url = Window.prompt("Model url", modelURLs[Math.floor(Math.random() * modelURLs.length)]);
if (url == null) {
return;
}
var position = Vec3.sum(MyAvatar.position, Vec3.multiply(Quat.getFront(MyAvatar.orientation), SPAWN_DISTANCE));
Models.addModel({ position: position,
radius: radiusDefault,
modelURL: url
});
} else {
var pickRay = Camera.computePickRay(event.x, event.y);
Vec3.print("[Mouse] Looking at: ", pickRay.origin);
var foundModels = Models.findModels(pickRay.origin, LASER_LENGTH_FACTOR);
for (var i = 0; i < foundModels.length; i++) {
if (!foundModels[i].isKnownID) {
var identify = Models.identifyModel(foundModels[i]);
if (!identify.isKnownID) {
print("Unknown ID " + identify.id + "(update loop)");
continue;
}
foundModels[i] = identify;
}
var properties = Models.getModelProperties(foundModels[i]);
print("Checking properties: " + properties.id + " " + properties.isKnownID);
// P P - Model
// /| A - Palm
// / | d B - unit vector toward tip
// / | X - base of the perpendicular line
// A---X----->B d - distance fom axis
// x x - distance from A
//
// |X-A| = (P-A).B
// X == A + ((P-A).B)B
// d = |P-X|
var A = pickRay.origin;
var B = Vec3.normalize(pickRay.direction);
var P = properties.position;
var x = Vec3.dot(Vec3.subtract(P, A), B);
var X = Vec3.sum(A, Vec3.multiply(B, x));
var d = Vec3.length(Vec3.subtract(P, X));
if (d < properties.radius && 0 < x && x < LASER_LENGTH_FACTOR) {
modelSelected = true;
selectedModelID = foundModels[i];
selectedModelProperties = properties;
selectedModelProperties.oldRadius = selectedModelProperties.radius;
selectedModelProperties.oldPosition = {
x: selectedModelProperties.position.x,
y: selectedModelProperties.position.y,
z: selectedModelProperties.position.z,
};
selectedModelProperties.oldRotation = {
x: selectedModelProperties.modelRotation.x,
y: selectedModelProperties.modelRotation.y,
z: selectedModelProperties.modelRotation.z,
w: selectedModelProperties.modelRotation.w,
};
orientation = MyAvatar.orientation;
intersection = rayPlaneIntersection(pickRay, P, Quat.getFront(orientation));
print("Clicked on " + selectedModelID.id + " " + modelSelected);
return;
}
}
}
}
var oldModifier = 0;
var modifier = 0;
var wasShifted = false;
function mouseMoveEvent(event) {
if (!modelSelected) {
return;
}
if (event.isLeftButton) {
if (event.isRightButton) {
modifier = 1; // Scale
} else {
modifier = 2; // Translate
}
} else if (event.isRightButton) {
modifier = 3; // rotate
} else {
modifier = 0;
}
var pickRay = Camera.computePickRay(event.x, event.y);
if (wasShifted != event.isShifted || modifier != oldModifier) {
selectedModelProperties.oldRadius = selectedModelProperties.radius;
selectedModelProperties.oldPosition = {
x: selectedModelProperties.position.x,
y: selectedModelProperties.position.y,
z: selectedModelProperties.position.z,
};
selectedModelProperties.oldRotation = {
x: selectedModelProperties.modelRotation.x,
y: selectedModelProperties.modelRotation.y,
z: selectedModelProperties.modelRotation.z,
w: selectedModelProperties.modelRotation.w,
};
orientation = MyAvatar.orientation;
intersection = rayPlaneIntersection(pickRay,
selectedModelProperties.oldPosition,
Quat.getFront(orientation));
mouseLastPosition = { x: event.x, y: event.y };
wasShifted = event.isShifted;
oldModifier = modifier;
return;
}
switch (modifier) {
case 0:
return;
case 1:
// Let's Scale
selectedModelProperties.radius = (selectedModelProperties.oldRadius *
(1.0 + (mouseLastPosition.y - event.y) / SCALE_FACTOR));
if (selectedModelProperties.radius < 0.01) {
print("Scale too small ... bailling.");
return;
}
break;
case 2:
// Let's translate
var newIntersection = rayPlaneIntersection(pickRay,
selectedModelProperties.oldPosition,
Quat.getFront(orientation));
var vector = Vec3.subtract(newIntersection, intersection)
if (event.isShifted) {
var i = Vec3.dot(vector, Quat.getRight(orientation));
var j = Vec3.dot(vector, Quat.getUp(orientation));
vector = Vec3.sum(Vec3.multiply(Quat.getRight(orientation), i),
Vec3.multiply(Quat.getFront(orientation), j));
}
selectedModelProperties.position = Vec3.sum(selectedModelProperties.oldPosition, vector);
break;
case 3:
// Let's rotate
var rotation = Quat.fromVec3Degrees({ x: event.y - mouseLastPosition.y, y: event.x - mouseLastPosition.x, z: 0 });
if (event.isShifted) {
rotation = Quat.fromVec3Degrees({ x: event.y - mouseLastPosition.y, y: 0, z: mouseLastPosition.x - event.x });
}
var newRotation = Quat.multiply(orientation, rotation);
newRotation = Quat.multiply(newRotation, Quat.inverse(orientation));
selectedModelProperties.modelRotation = Quat.multiply(newRotation, selectedModelProperties.oldRotation);
break;
}
Models.editModel(selectedModelID, selectedModelProperties);
}
function scriptEnding() {
leftController.cleanup();
rightController.cleanup();
Overlays.deleteOverlay(firstModel);
toolBar.cleanup();
}
Script.scriptEnding.connect(scriptEnding);
// register the call back so it fires before each data send
Script.update.connect(checkController);
Controller.mousePressEvent.connect(mousePressEvent);
Controller.mouseMoveEvent.connect(mouseMoveEvent);

View file

@ -37,6 +37,7 @@ var radiusMinimum = 0.05;
var radiusMaximum = 0.5;
var modelURLs = [
"http://www.fungibleinsight.com/faces/beta.fst",
"https://s3-us-west-1.amazonaws.com/highfidelity-public/models/attachments/topHat.fst",
"http://highfidelity-public.s3-us-west-1.amazonaws.com/meshes/Feisar_Ship.FBX",
"http://highfidelity-public.s3-us-west-1.amazonaws.com/meshes/birarda/birarda_head.fbx",
@ -48,6 +49,19 @@ var modelURLs = [
"http://highfidelity-public.s3-us-west-1.amazonaws.com/meshes/slimer.fbx",
];
var animationURLs = [
"http://www.fungibleinsight.com/faces/gangnam_style_2.fbx",
"",
"",
"",
"",
"",
"",
"",
"",
"",
];
var currentModelURL = 1;
var numModels = modelURLs.length;
@ -214,10 +228,19 @@ function checkControllerSide(whichSide) {
modelRotation: palmRotation,
modelURL: modelURLs[currentModelURL]
};
if (animationURLs[currentModelURL] !== "") {
properties.animationURL = animationURLs[currentModelURL];
properties.animationIsPlaying = true;
}
debugPrint("modelRadius=" +modelRadius);
newModel = Models.addModel(properties);
print("just added model... newModel=" + newModel.creatorTokenID);
print("properties.animationURL=" + properties.animationURL);
if (whichSide == LEFT_PALM) {
leftModelAlreadyInHand = true;
leftHandModel = newModel;

View file

@ -0,0 +1,51 @@
//
// streetAreaExample.js
// examples
//
// Created by Ryan Huffman on 5/4/14
// Copyright 2014 High Fidelity, Inc.
//
// This is an example script showing how to load JSON data using XMLHttpRequest.
//
// URL Macro created by Thijs Wenker.
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
var url = "https://script.google.com/macros/s/AKfycbwIo4lmF-qUwX1Z-9eA_P-g2gse9oFhNcjVyyksGukyDDEFXgU/exec?action=listOwners&domain=alpha.highfidelity.io";
print("Loading street data from " + url);
var req = new XMLHttpRequest();
// Set response type to "json". This will tell XMLHttpRequest to parse the response data as json, so req.response can be used
// as a regular javascript object
req.responseType = 'json';
req.open("GET", url, false);
req.send();
if (req.status == 200) {
for (var domain in req.response) {
print("DOMAIN: " + domain);
var locations = req.response[domain];
var userAreas = [];
for (var i = 0; i < locations.length; i++) {
var loc = locations[i];
var x1 = loc[1],
x2 = loc[2],
y1 = loc[3],
y2 = loc[4];
userAreas.push({
username: loc[0],
area: Math.abs(x2 - x1) * Math.abs(y2 - y1),
});
}
userAreas.sort(function(a, b) { return a.area > b.area ? -1 : (a.area < b.area ? 1 : 0) });
for (var i = 0; i < userAreas.length; i++) {
print(userAreas[i].username + ": " + userAreas[i].area + " sq units");
}
}
} else {
print("Error loading data: " + req.status + " " + req.statusText + ", " + req.errorCode);
}

View file

@ -0,0 +1,147 @@
//
// testXMLHttpRequest.js
// examples
//
// Created by Ryan Huffman on 5/4/14
// Copyright 2014 High Fidelity, Inc.
//
// XMLHttpRequest Tests
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
Script.include("Test.js");
test("Test default request values", function(finished) {
var req = new XMLHttpRequest();
this.assertEquals(req.UNSENT, req.readyState, "readyState should be UNSENT");
this.assertEquals(0, req.status, "status should be `0` by default");
this.assertEquals("", req.statusText, "statusText should be empty string by default");
this.assertEquals("", req.getAllResponseHeaders(), "getAllResponseHeaders() should return empty string by default");
this.assertEquals("", req.response, "response should be empty string by default");
this.assertEquals("", req.responseText, "responseText should be empty string by default");
this.assertEquals("", req.responseType, "responseType should be empty string by default");
this.assertEquals(0, req.timeout, "timeout should be `0` by default");
this.assertEquals(0, req.errorCode, "there should be no error by default");
});
test("Test readyStates", function() {
var req = new XMLHttpRequest();
var state = req.readyState;
var statesVisited = [true, false, false, false, false]
req.onreadystatechange = function() {
statesVisited[req.readyState] = true;
};
req.open("GET", "https://gist.githubusercontent.com/huffman/33cc618fec183d1bccd0/raw/test.json", false);
req.send();
for (var i = 0; i <= req.DONE; i++) {
this.assertEquals(true, statesVisited[i], i + " should be set");
}
this.assertEquals(req.DONE, req.readyState, "readyState should be DONE");
});
test("Test TEXT request", function() {
var req = new XMLHttpRequest();
var state = req.readyState;
req.open("GET", "https://gist.githubusercontent.com/huffman/33cc618fec183d1bccd0/raw/test.json", false);
req.send();
this.assertEquals(req.DONE, req.readyState, "readyState should be DONE");
this.assertEquals(200, req.status, "status should be `200`");
this.assertEquals(0, req.errorCode);
this.assertEquals("OK", req.statusText, "statusText should be `OK`");
this.assertNotEquals("", req.getAllResponseHeaders(), "headers should no longer be empty string");
this.assertNull(req.getResponseHeader('invalidheader'), "invalid header should return `null`");
this.assertEquals("GitHub.com", req.getResponseHeader('Server'), "Server header should be GitHub.com");
this.assertEquals('{"id": 1}', req.response);
this.assertEquals('{"id": 1}', req.responseText);
});
test("Test JSON request", function() {
var req = new XMLHttpRequest();
var state = req.readyState;
req.responseType = "json";
req.open("GET", "https://gist.githubusercontent.com/huffman/33cc618fec183d1bccd0/raw/test.json", false);
req.send();
this.assertEquals(req.DONE, req.readyState, "readyState should be DONE");
this.assertEquals(200, req.status, "status should be `200`");
this.assertEquals(0, req.errorCode);
this.assertEquals("OK", req.statusText, "statusText should be `OK`");
this.assertNotEquals("", req.getAllResponseHeaders(), "headers should no longer be empty string");
this.assertNull(req.getResponseHeader('invalidheader'), "invalid header should return `null`");
this.assertEquals("GitHub.com", req.getResponseHeader('Server'), "Server header should be GitHub.com");
this.assertHasProperty('id', req.response);
this.assertEquals(1, req.response.id);
this.assertEquals('{"id": 1}', req.responseText);
});
test("Test Bad URL", function() {
var req = new XMLHttpRequest();
var state = req.readyState;
req.open("POST", "hifi://domain/path", false);
req.send();
this.assertEquals(req.DONE, req.readyState, "readyState should be DONE");
this.assertNotEquals(0, req.errorCode);
});
test("Test Bad Method Error", function() {
var req = new XMLHttpRequest();
var state = req.readyState;
req.open("POST", "https://www.google.com", false);
req.send("randomdata");
this.assertEquals(req.DONE, req.readyState, "readyState should be DONE");
this.assertEquals(405, req.status);
this.assertEquals(202, req.errorCode);
req.open("POST", "https://www.google.com", false)
req.send();
this.assertEquals(req.DONE, req.readyState, "readyState should be DONE");
this.assertEquals(405, req.status);
this.assertEquals(202, req.errorCode);
});
test("Test abort", function() {
var req = new XMLHttpRequest();
var state = req.readyState;
req.open("POST", "https://www.google.com", true)
req.send();
req.abort();
this.assertEquals(0, req.status);
this.assertEquals(0, req.errorCode);
});
test("Test timeout", function() {
var req = new XMLHttpRequest();
var state = req.readyState;
var timedOut = false;
req.ontimeout = function() {
timedOut = true;
};
req.open("POST", "https://gist.githubusercontent.com/huffman/33cc618fec183d1bccd0/raw/test.json", false)
req.timeout = 1;
req.send();
this.assertEquals(true, timedOut, "request should have timed out");
this.assertEquals(req.DONE, req.readyState, "readyState should be DONE");
this.assertEquals(0, req.status, "status should be `0`");
this.assertEquals(4, req.errorCode, "4 is the timeout error code for QNetworkReply::NetworkError");
});

197
examples/toolBars.js Normal file
View file

@ -0,0 +1,197 @@
//
// toolBars.js
// examples
//
// Created by Clément Brisset on 5/7/14.
// 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
//
Overlay2D = function(properties, overlay) { // overlay is an optionnal variable
if (!(typeof(properties) === 'undefined')) {
if(typeof(overlay) === 'undefined') {
overlay = Overlays.addOverlay("image", properties);
} else {
Overlays.editOverlay(overlay, properties);
}
}
this.overlay = function() {
return overlay;
}
this.x = function() {
return properties.x;
}
this.y = function() {
return properties.y;
}
this.width = function() {
return properties.width;
}
this.height = function() {
return properties.height;
}
this.alpha = function() {
return properties.alpha;
}
this.visible = function() {
return properties.visible;
}
this.move = function(x, y) {
properties.x = x;
properties.y = y;
Overlays.editOverlay(overlay, { x: x, y: y });
}
this.resize = function(width, height) {
properties.width = width;
properties.height = height;
Overlays.editOverlay(overlay, { width: width, height: height });
}
this.setAlpha = function(alpha) {
properties.alpha = alpha;
Overlays.editOverlay(overlay, { alpha: alpha });
}
this.show = function(doShow) {
properties.visible = doShow;
Overlays.editOverlay(overlay, { visible: doShow });
}
this.clicked = function(clickedOverlay) {
return (overlay == clickedOverlay ? true : false);
}
this.cleanup = function() {
print("Cleanup");
Overlays.deleteOverlay(overlay);
}
}
Tool = function(properties, selectable, selected) { // selectable and selected are optional variables.
Overlay2D.call(this, properties);
if(typeof(selectable)==='undefined') {
selectable = false;
if(typeof(selected)==='undefined') {
selected = false;
}
}
this.selectable = function() {
return selectable;
}
this.selected = function() {
return selected;
}
this.select = function(doSelect) {
selected = doSelect;
properties.subImage.y = (selected ? 2 : 1) * properties.subImage.height;
Overlays.editOverlay(this.overlay(), { subImage: properties.subImage });
}
this.toggle = function() {
selected = !selected;
properties.subImage.y = (selected ? 2 : 1) * properties.subImage.height;
Overlays.editOverlay(this.overlay(), { subImage: properties.subImage });
return selected;
}
this.select(selected);
this.baseClicked = this.clicked;
this.clicked = function(clickedOverlay) {
if (this.baseClicked(clickedOverlay)) {
if (selectable) {
this.toggle();
}
return true;
}
return false;
}
}
Tool.prototype = new Overlay2D;
Tool.IMAGE_HEIGHT = 50;
Tool.IMAGE_WIDTH = 50;
ToolBar = function(x, y, direction) {
this.tools = [];
this.x = x;
this.y = y;
this.width = 0;
this.height = 0;
this.addTool = function(properties, selectable, selected) {
if (direction == ToolBar.HORIZONTAL) {
properties.x = this.x + this.width;
properties.y = this.y;
this.width += properties.width + ToolBar.SPACING;
this.height += Math.max(properties.height, this.height);
} else {
properties.x = this.x;
properties.y = this.y + this.height;
this.width = Math.max(properties.width, this.width);
this.height += properties.height + ToolBar.SPACING;
}
this.tools[this.tools.length] = new Tool(properties, selectable, selected);
return ((this.tools.length) - 1);
}
this.move = function(x, y) {
var dx = x - this.x;
var dy = y - this.y;
this.x = x;
this.y = y;
for(var tool in this.tools) {
this.tools[tool].move(this.tools[tool].x() + dx, this.tools[tool].y() + dy);
}
}
this.setAlpha = function(alpha) {
for(var tool in this.tools) {
this.tools[tool].setAlpha(alpha);
}
}
this.show = function(doShow) {
for(var tool in this.tools) {
this.tools[tool].show(doShow);
}
}
this.clicked = function(clickedOverlay) {
for(var tool in this.tools) {
if (this.tools[tool].visible() && this.tools[tool].clicked(clickedOverlay)) {
return parseInt(tool);
}
}
return -1;
}
this.numberOfTools = function() {
return this.tools.length;
}
this.cleanup = function() {
for(var tool in this.tools) {
this.tools[tool].cleanup();
delete this.tools[tool];
}
this.tools = [];
this.x = x;
this.y = y;
this.width = 0;
this.height = 0;
}
}
ToolBar.SPACING = 4;
ToolBar.VERTICAL = 0;
ToolBar.HORIZONTAL = 1;

View file

@ -15,6 +15,7 @@ set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} "${CMAKE_CURRENT_SOURCE_DIR}/../cmake
set(FACEPLUS_ROOT_DIR "${CMAKE_CURRENT_SOURCE_DIR}/external/faceplus")
set(FACESHIFT_ROOT_DIR "${CMAKE_CURRENT_SOURCE_DIR}/external/faceshift")
set(LIBOVR_ROOT_DIR "${CMAKE_CURRENT_SOURCE_DIR}/external/oculus")
set(PRIOVR_ROOT_DIR "${CMAKE_CURRENT_SOURCE_DIR}/external/priovr")
set(SIXENSE_ROOT_DIR "${CMAKE_CURRENT_SOURCE_DIR}/external/Sixense")
set(VISAGE_ROOT_DIR "${CMAKE_CURRENT_SOURCE_DIR}/external/visage")
@ -127,12 +128,14 @@ link_hifi_library(particles ${TARGET_NAME} "${ROOT_DIR}")
link_hifi_library(models ${TARGET_NAME} "${ROOT_DIR}")
link_hifi_library(avatars ${TARGET_NAME} "${ROOT_DIR}")
link_hifi_library(audio ${TARGET_NAME} "${ROOT_DIR}")
link_hifi_library(animation ${TARGET_NAME} "${ROOT_DIR}")
link_hifi_library(script-engine ${TARGET_NAME} "${ROOT_DIR}")
# find any optional libraries
find_package(Faceplus)
find_package(Faceshift)
find_package(LibOVR)
find_package(PrioVR)
find_package(Sixense)
find_package(Visage)
find_package(ZLIB)
@ -183,6 +186,13 @@ if (LIBOVR_FOUND AND NOT DISABLE_LIBOVR)
target_link_libraries(${TARGET_NAME} "${LIBOVR_LIBRARIES}")
endif (LIBOVR_FOUND AND NOT DISABLE_LIBOVR)
# and with PrioVR library
if (PRIOVR_FOUND AND NOT DISABLE_PRIOVR)
add_definitions(-DHAVE_PRIOVR)
include_directories(SYSTEM "${PRIOVR_INCLUDE_DIRS}")
target_link_libraries(${TARGET_NAME} "${PRIOVR_LIBRARIES}")
endif (PRIOVR_FOUND AND NOT DISABLE_PRIOVR)
# and with qxmpp for chat
if (QXMPP_FOUND AND NOT DISABLE_QXMPP)
add_definitions(-DHAVE_QXMPP -DQXMPP_STATIC)

16
interface/external/priovr/readme.txt vendored Normal file
View file

@ -0,0 +1,16 @@
Instructions for adding the PrioVR driver to Interface
Andrzej Kapolka, May 12, 2014
1. Download and install the YEI drivers from https://www.yeitechnology.com/yei-3-space-sensor-software-suite. If using
Window 8+, follow the workaround instructions at http://forum.yeitechnology.com/viewtopic.php?f=3&t=24.
2. Get the PrioVR skeleton API, open ts_c_api2_priovr2/visual_studio/ThreeSpace_API_2/ThreeSpace_API_2.sln
in Visual Studio, and build it.
3. Copy ts_c_api2_priovr2/visual_studio/ThreeSpace_API_2/Skeletal_API/yei_skeletal_api.h to interface/external/priovr/include,
ts_c_api2_priovr2/visual_studio/ThreeSpace_API_2/Debug/Skeletal_API.lib to interface/external/priovr/lib, and
ts_c_api2_priovr2/visual_studio/ThreeSpace_API_2/Debug/*.dll to your path.
4. Delete your build directory, run cmake and build, and you should be all set.

View file

@ -170,7 +170,6 @@ Application::Application(int& argc, char** argv, QElapsedTimer &startup_time) :
_packetsPerSecond(0),
_bytesPerSecond(0),
_previousScriptLocation(),
_logger(new FileLogger(this)),
_runningScriptsWidget(new RunningScriptsWidget(_window)),
_runningScriptsWidgetWasVisible(false)
{
@ -190,6 +189,8 @@ Application::Application(int& argc, char** argv, QElapsedTimer &startup_time) :
setOrganizationName(applicationInfo.value("organizationName").toString());
setOrganizationDomain(applicationInfo.value("organizationDomain").toString());
_logger = new FileLogger(this); // After setting organization name in order to get correct directory
QSettings::setDefaultFormat(QSettings::IniFormat);
_myAvatar = _avatarManager.getMyAvatar();
@ -1982,6 +1983,7 @@ void Application::update(float deltaTime) {
_myAvatar->updateLookAtTargetAvatar();
updateMyAvatarLookAtPosition();
_sixenseManager.update(deltaTime);
_prioVR.update();
updateMyAvatar(deltaTime); // Sample hardware, update view frustum if needed, and send avatar data to mixer/nodes
updateThreads(deltaTime); // If running non-threaded, then give the threads some time to process...
_avatarManager.updateOtherAvatars(deltaTime); //loop through all the other avatars and simulate them...
@ -2751,6 +2753,9 @@ void Application::displayOverlay() {
drawText(_glWidget->width() - 100, _glWidget->height() - timerBottom, 0.30f, 0.0f, 0, frameTimer, WHITE_TEXT);
}
// give external parties a change to hook in
emit renderingOverlay();
_overlays.render2D();
glPopMatrix();
@ -3059,6 +3064,8 @@ void Application::resetSensors() {
OculusManager::reset();
}
_prioVR.reset();
QCursor::setPos(_mouseX, _mouseY);
_myAvatar->reset();

View file

@ -58,6 +58,7 @@
#include "avatar/MyAvatar.h"
#include "devices/Faceplus.h"
#include "devices/Faceshift.h"
#include "devices/PrioVR.h"
#include "devices/SixenseManager.h"
#include "devices/Visage.h"
#include "models/ModelTreeRenderer.h"
@ -194,6 +195,7 @@ public:
Visage* getVisage() { return &_visage; }
FaceTracker* getActiveFaceTracker();
SixenseManager* getSixenseManager() { return &_sixenseManager; }
PrioVR* getPrioVR() { return &_prioVR; }
BandwidthMeter* getBandwidthMeter() { return &_bandwidthMeter; }
QUndoStack* getUndoStack() { return &_undoStack; }
@ -267,6 +269,9 @@ signals:
/// Fired when we're rendering in-world interface elements; allows external parties to hook in.
void renderingInWorldInterface();
/// Fired when we're rendering the overlay.
void renderingOverlay();
/// Fired when the import window is closed
void importDone();
@ -442,6 +447,7 @@ private:
Visage _visage;
SixenseManager _sixenseManager;
PrioVR _prioVR;
Camera _myCamera; // My view onto the world
Camera _viewFrustumOffsetCamera; // The camera we use to sometimes show the view frustum from an offset mode

View file

@ -111,7 +111,7 @@ void MyAvatar::reset() {
void MyAvatar::update(float deltaTime) {
Head* head = getHead();
head->relaxLean(deltaTime);
updateFromFaceTracker(deltaTime);
updateFromTrackers(deltaTime);
if (Menu::getInstance()->isOptionChecked(MenuOption::MoveWithLean)) {
// Faceshift drive is enabled, set the avatar drive based on the head position
moveWithLean();
@ -234,26 +234,33 @@ void MyAvatar::simulate(float deltaTime) {
}
// Update avatar head rotation with sensor data
void MyAvatar::updateFromFaceTracker(float deltaTime) {
void MyAvatar::updateFromTrackers(float deltaTime) {
glm::vec3 estimatedPosition, estimatedRotation;
FaceTracker* tracker = Application::getInstance()->getActiveFaceTracker();
if (tracker) {
estimatedPosition = tracker->getHeadTranslation();
estimatedRotation = glm::degrees(safeEulerAngles(tracker->getHeadRotation()));
// Rotate the body if the head is turned beyond the screen
if (Menu::getInstance()->isOptionChecked(MenuOption::TurnWithHead)) {
const float TRACKER_YAW_TURN_SENSITIVITY = 0.5f;
const float TRACKER_MIN_YAW_TURN = 15.0f;
const float TRACKER_MAX_YAW_TURN = 50.0f;
if ( (fabs(estimatedRotation.y) > TRACKER_MIN_YAW_TURN) &&
(fabs(estimatedRotation.y) < TRACKER_MAX_YAW_TURN) ) {
if (estimatedRotation.y > 0.0f) {
_bodyYawDelta += (estimatedRotation.y - TRACKER_MIN_YAW_TURN) * TRACKER_YAW_TURN_SENSITIVITY;
} else {
_bodyYawDelta += (estimatedRotation.y + TRACKER_MIN_YAW_TURN) * TRACKER_YAW_TURN_SENSITIVITY;
}
if (Application::getInstance()->getPrioVR()->isActive()) {
estimatedRotation = glm::degrees(safeEulerAngles(Application::getInstance()->getPrioVR()->getHeadRotation()));
estimatedRotation.x *= -1.0f;
estimatedRotation.z *= -1.0f;
} else {
FaceTracker* tracker = Application::getInstance()->getActiveFaceTracker();
if (tracker) {
estimatedPosition = tracker->getHeadTranslation();
estimatedRotation = glm::degrees(safeEulerAngles(tracker->getHeadRotation()));
}
}
// Rotate the body if the head is turned beyond the screen
if (Menu::getInstance()->isOptionChecked(MenuOption::TurnWithHead)) {
const float TRACKER_YAW_TURN_SENSITIVITY = 0.5f;
const float TRACKER_MIN_YAW_TURN = 15.0f;
const float TRACKER_MAX_YAW_TURN = 50.0f;
if ( (fabs(estimatedRotation.y) > TRACKER_MIN_YAW_TURN) &&
(fabs(estimatedRotation.y) < TRACKER_MAX_YAW_TURN) ) {
if (estimatedRotation.y > 0.0f) {
_bodyYawDelta += (estimatedRotation.y - TRACKER_MIN_YAW_TURN) * TRACKER_YAW_TURN_SENSITIVITY;
} else {
_bodyYawDelta += (estimatedRotation.y + TRACKER_MIN_YAW_TURN) * TRACKER_YAW_TURN_SENSITIVITY;
}
}
}
@ -271,6 +278,14 @@ void MyAvatar::updateFromFaceTracker(float deltaTime) {
head->setDeltaYaw(estimatedRotation.y * magnifyFieldOfView);
head->setDeltaRoll(estimatedRotation.z);
// the priovr can give us exact lean
if (Application::getInstance()->getPrioVR()->isActive()) {
glm::vec3 eulers = glm::degrees(safeEulerAngles(Application::getInstance()->getPrioVR()->getTorsoRotation()));
head->setLeanSideways(eulers.z);
head->setLeanForward(eulers.x);
return;
}
// Update torso lean distance based on accelerometer data
const float TORSO_LENGTH = 0.5f;
glm::vec3 relativePosition = estimatedPosition - glm::vec3(0.0f, -TORSO_LENGTH, 0.0f);

View file

@ -38,7 +38,7 @@ public:
void reset();
void update(float deltaTime);
void simulate(float deltaTime);
void updateFromFaceTracker(float deltaTime);
void updateFromTrackers(float deltaTime);
void moveWithLean();
void render(const glm::vec3& cameraPosition, RenderMode renderMode = NORMAL_RENDER_MODE);

View file

@ -33,14 +33,28 @@ void SkeletonModel::simulate(float deltaTime, bool fullUpdate) {
return; // only simulate for own avatar
}
const FBXGeometry& geometry = _geometry->getFBXGeometry();
PrioVR* prioVR = Application::getInstance()->getPrioVR();
if (prioVR->isActive()) {
for (int i = 0; i < prioVR->getJointRotations().size(); i++) {
int humanIKJointIndex = prioVR->getHumanIKJointIndices().at(i);
if (humanIKJointIndex == -1) {
continue;
}
int jointIndex = geometry.humanIKJointIndices.at(humanIKJointIndex);
if (jointIndex != -1) {
setJointRotation(jointIndex, _rotation * prioVR->getJointRotations().at(i), true);
}
}
return;
}
// find the left and rightmost active palms
int leftPalmIndex, rightPalmIndex;
Hand* hand = _owningAvatar->getHand();
hand->getLeftRightPalmIndices(leftPalmIndex, rightPalmIndex);
const float HAND_RESTORATION_RATE = 0.25f;
const FBXGeometry& geometry = _geometry->getFBXGeometry();
const float HAND_RESTORATION_RATE = 0.25f;
if (leftPalmIndex == -1) {
// palms are not yet set, use mouse
if (_owningAvatar->getHandState() == HAND_STATE_NULL) {
@ -186,7 +200,7 @@ void SkeletonModel::updateJointState(int index) {
}
void SkeletonModel::maybeUpdateLeanRotation(const JointState& parentState, const FBXJoint& joint, JointState& state) {
if (!_owningAvatar->isMyAvatar()) {
if (!_owningAvatar->isMyAvatar() || Application::getInstance()->getPrioVR()->isActive()) {
return;
}
// get the rotation axes in joint space and use them to adjust the rotation

View file

@ -0,0 +1,117 @@
//
// PrioVR.cpp
// interface/src/devices
//
// Created by Andrzej Kapolka on 5/12/14.
// 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
//
#include <QtDebug>
#include <FBXReader.h>
#include "Application.h"
#include "PrioVR.h"
#include "ui/TextRenderer.h"
const unsigned int SERIAL_LIST[] = { 0x00000001, 0x00000000, 0x00000008, 0x00000009, 0x0000000A,
0x0000000C, 0x0000000D, 0x0000000E, 0x00000004, 0x00000005, 0x00000010, 0x00000011 };
const unsigned char AXIS_LIST[] = { 9, 43, 37, 37, 37, 13, 13, 13, 52, 52, 28, 28 };
const int LIST_LENGTH = sizeof(SERIAL_LIST) / sizeof(SERIAL_LIST[0]);
const char* JOINT_NAMES[] = { "Neck", "Spine", "LeftArm", "LeftForeArm", "LeftHand", "RightArm",
"RightForeArm", "RightHand", "LeftUpLeg", "LeftLeg", "RightUpLeg", "RightLeg" };
#ifdef HAVE_PRIOVR
static int indexOfHumanIKJoint(const char* jointName) {
for (int i = 0;; i++) {
QByteArray humanIKJoint = HUMANIK_JOINTS[i];
if (humanIKJoint.isEmpty()) {
return -1;
}
if (humanIKJoint == jointName) {
return i;
}
}
}
#endif
PrioVR::PrioVR() {
#ifdef HAVE_PRIOVR
char jointsDiscovered[LIST_LENGTH];
_skeletalDevice = yei_setUpPrioVRSensors(0x00000000, const_cast<unsigned int*>(SERIAL_LIST),
const_cast<unsigned char*>(AXIS_LIST), jointsDiscovered, LIST_LENGTH, YEI_TIMESTAMP_SYSTEM);
if (!_skeletalDevice) {
return;
}
_jointRotations.resize(LIST_LENGTH);
for (int i = 0; i < LIST_LENGTH; i++) {
_humanIKJointIndices.append(jointsDiscovered[i] ? indexOfHumanIKJoint(JOINT_NAMES[i]) : -1);
}
#endif
}
PrioVR::~PrioVR() {
#ifdef HAVE_PRIOVR
if (_skeletalDevice) {
yei_stopStreaming(_skeletalDevice);
}
#endif
}
glm::quat PrioVR::getHeadRotation() const {
const int HEAD_ROTATION_INDEX = 0;
return _jointRotations.size() > HEAD_ROTATION_INDEX ? _jointRotations.at(HEAD_ROTATION_INDEX) : glm::quat();
}
glm::quat PrioVR::getTorsoRotation() const {
const int TORSO_ROTATION_INDEX = 1;
return _jointRotations.size() > TORSO_ROTATION_INDEX ? _jointRotations.at(TORSO_ROTATION_INDEX) : glm::quat();
}
void PrioVR::update() {
#ifdef HAVE_PRIOVR
if (!_skeletalDevice) {
return;
}
unsigned int timestamp;
yei_getLastStreamDataAll(_skeletalDevice, (char*)_jointRotations.data(),
_jointRotations.size() * sizeof(glm::quat), &timestamp);
// convert to our expected coordinate system
for (int i = 0; i < _jointRotations.size(); i++) {
_jointRotations[i].y *= -1.0f;
_jointRotations[i].z *= -1.0f;
}
#endif
}
void PrioVR::reset() {
#ifdef HAVE_PRIOVR
if (!_skeletalDevice) {
return;
}
connect(Application::getInstance(), SIGNAL(renderingOverlay()), SLOT(renderCalibrationCountdown()));
_calibrationCountdownStarted = QDateTime::currentDateTime();
#endif
}
void PrioVR::renderCalibrationCountdown() {
#ifdef HAVE_PRIOVR
const int COUNTDOWN_SECONDS = 3;
int secondsRemaining = COUNTDOWN_SECONDS - _calibrationCountdownStarted.secsTo(QDateTime::currentDateTime());
if (secondsRemaining == 0) {
yei_tareSensors(_skeletalDevice);
Application::getInstance()->disconnect(this);
return;
}
static TextRenderer textRenderer(MONO_FONT_FAMILY, 18, QFont::Bold, false, TextRenderer::OUTLINE_EFFECT, 2);
QByteArray text = "Assume T-Pose in " + QByteArray::number(secondsRemaining) + "...";
textRenderer.draw((Application::getInstance()->getGLWidget()->width() - textRenderer.computeWidth(text.constData())) / 2,
Application::getInstance()->getGLWidget()->height() / 2,
text);
#endif
}

View file

@ -0,0 +1,62 @@
//
// PrioVR.h
// interface/src/devices
//
// Created by Andrzej Kapolka on 5/12/14.
// 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
//
#ifndef hifi_PrioVR_h
#define hifi_PrioVR_h
#include <QDateTime>
#include <QObject>
#include <QVector>
#include <glm/gtc/quaternion.hpp>
#ifdef HAVE_PRIOVR
extern "C" {
#include <yei_skeletal_api.h>
}
#endif
/// Handles interaction with the PrioVR skeleton tracking suit.
class PrioVR : public QObject {
Q_OBJECT
public:
PrioVR();
virtual ~PrioVR();
bool isActive() const { return !_jointRotations.isEmpty(); }
glm::quat getHeadRotation() const;
glm::quat getTorsoRotation() const;
const QVector<int>& getHumanIKJointIndices() const { return _humanIKJointIndices; }
const QVector<glm::quat>& getJointRotations() const { return _jointRotations; }
void update();
void reset();
private slots:
void renderCalibrationCountdown();
private:
#ifdef HAVE_PRIOVR
YEI_Device_Id _skeletalDevice;
#endif
QVector<int> _humanIKJointIndices;
QVector<glm::quat> _jointRotations;
QDateTime _calibrationCountdownStarted;
};
#endif // hifi_PrioVR_h

View file

@ -55,7 +55,8 @@ Visage::Visage() :
Visage::~Visage() {
#ifdef HAVE_VISAGE
_tracker->stop();
delete _tracker;
// deleting the tracker crashes windows; disable for now
//delete _tracker;
delete _data;
#endif
}

View file

@ -20,11 +20,16 @@ ModelTreeRenderer::ModelTreeRenderer() :
}
ModelTreeRenderer::~ModelTreeRenderer() {
// delete the models in _modelsItemModels
foreach(Model* model, _modelsItemModels) {
// delete the models in _knownModelsItemModels
foreach(Model* model, _knownModelsItemModels) {
delete model;
}
_modelsItemModels.clear();
_knownModelsItemModels.clear();
foreach(Model* model, _unknownModelsItemModels) {
delete model;
}
_unknownModelsItemModels.clear();
}
void ModelTreeRenderer::init() {
@ -43,27 +48,38 @@ void ModelTreeRenderer::render(RenderMode renderMode) {
OctreeRenderer::render(renderMode);
}
Model* ModelTreeRenderer::getModel(const QString& url) {
Model* ModelTreeRenderer::getModel(const ModelItem& modelItem) {
Model* model = NULL;
// if we don't already have this model then create it and initialize it
if (_modelsItemModels.find(url) == _modelsItemModels.end()) {
model = new Model();
model->init();
model->setURL(QUrl(url));
_modelsItemModels[url] = model;
if (modelItem.isKnownID()) {
if (_knownModelsItemModels.find(modelItem.getID()) != _knownModelsItemModels.end()) {
model = _knownModelsItemModels[modelItem.getID()];
} else {
model = new Model();
model->init();
model->setURL(QUrl(modelItem.getModelURL()));
_knownModelsItemModels[modelItem.getID()] = model;
}
} else {
model = _modelsItemModels[url];
if (_unknownModelsItemModels.find(modelItem.getCreatorTokenID()) != _unknownModelsItemModels.end()) {
model = _unknownModelsItemModels[modelItem.getCreatorTokenID()];
} else {
model = new Model();
model->init();
model->setURL(QUrl(modelItem.getModelURL()));
_unknownModelsItemModels[modelItem.getCreatorTokenID()] = model;
}
}
return model;
}
void ModelTreeRenderer::renderElement(OctreeElement* element, RenderArgs* args) {
args->_elementsTouched++;
// actually render it here...
// we need to iterate the actual modelItems of the element
ModelTreeElement* modelTreeElement = (ModelTreeElement*)element;
const QList<ModelItem>& modelItems = modelTreeElement->getModels();
QList<ModelItem>& modelItems = modelTreeElement->getModels();
uint16_t numberOfModels = modelItems.size();
@ -139,7 +155,7 @@ void ModelTreeRenderer::renderElement(OctreeElement* element, RenderArgs* args)
}
for (uint16_t i = 0; i < numberOfModels; i++) {
const ModelItem& modelItem = modelItems[i];
ModelItem& modelItem = modelItems[i];
// render modelItem aspoints
AABox modelBox = modelItem.getAABox();
modelBox.scale(TREE_SCALE);
@ -150,13 +166,13 @@ void ModelTreeRenderer::renderElement(OctreeElement* element, RenderArgs* args)
bool drawAsModel = modelItem.hasModel();
args->_renderedItems++;
args->_itemsRendered++;
if (drawAsModel) {
glPushMatrix();
const float alpha = 1.0f;
Model* model = getModel(modelItem.getModelURL());
Model* model = getModel(modelItem);
model->setScaleToFit(true, radius * 2.0f);
model->setSnapModelToCenter(true);
@ -167,6 +183,21 @@ void ModelTreeRenderer::renderElement(OctreeElement* element, RenderArgs* args)
// set the position
model->setTranslation(position);
// handle animations..
if (modelItem.hasAnimation()) {
if (!modelItem.jointsMapped()) {
QStringList modelJointNames = model->getJointNames();
modelItem.mapJoints(modelJointNames);
}
QVector<glm::quat> frameData = modelItem.getAnimationFrame();
for (int i = 0; i < frameData.size(); i++) {
model->setJointState(i, true, frameData[i]);
}
}
// make sure to simulate so everything gets set up correctly for rendering
model->simulate(0.0f);
// TODO: should we allow modelItems to have alpha on their models?
@ -190,6 +221,8 @@ void ModelTreeRenderer::renderElement(OctreeElement* element, RenderArgs* args)
glutSolidSphere(radius, 15, 15);
glPopMatrix();
}
} else {
args->_itemsOutOfView++;
}
}
}

View file

@ -49,9 +49,9 @@ public:
virtual void render(RenderMode renderMode = DEFAULT_RENDER_MODE);
protected:
Model* getModel(const QString& url);
QMap<QString, Model*> _modelsItemModels;
Model* getModel(const ModelItem& modelItem);
QMap<uint32_t, Model*> _knownModelsItemModels;
QMap<uint32_t, Model*> _unknownModelsItemModels;
};
#endif // hifi_ModelTreeRenderer_h

View file

@ -76,7 +76,7 @@ void ParticleTreeRenderer::renderElement(OctreeElement* element, RenderArgs* arg
bool drawAsModel = particle.hasModel();
args->_renderedItems++;
args->_itemsRendered++;
if (drawAsModel) {
glPushMatrix();

View file

@ -433,7 +433,8 @@ Extents Model::getMeshExtents() const {
return Extents();
}
const Extents& extents = _geometry->getFBXGeometry().meshExtents;
Extents scaledExtents = { extents.minimum * _scale, extents.maximum * _scale };
glm::vec3 scale = _scale * _geometry->getFBXGeometry().fstScaled;
Extents scaledExtents = { extents.minimum * scale, extents.maximum * scale };
return scaledExtents;
}
@ -441,8 +442,13 @@ Extents Model::getUnscaledMeshExtents() const {
if (!isActive()) {
return Extents();
}
const Extents& extents = _geometry->getFBXGeometry().meshExtents;
return extents;
// even though our caller asked for "unscaled" we need to include any fst scaling
float scale = _geometry->getFBXGeometry().fstScaled;
Extents scaledExtents = { extents.minimum * scale, extents.maximum * scale };
return scaledExtents;
}
bool Model::getJointState(int index, glm::quat& rotation) const {
@ -576,6 +582,17 @@ bool Model::getJointRotation(int jointIndex, glm::quat& rotation, bool fromBind)
return true;
}
QStringList Model::getJointNames() const {
if (QThread::currentThread() != thread()) {
QStringList result;
QMetaObject::invokeMethod(const_cast<Model*>(this), "getJointNames", Qt::BlockingQueuedConnection,
Q_RETURN_ARG(QStringList, result));
return result;
}
return isActive() ? _geometry->getFBXGeometry().getJointNames() : QStringList();
}
void Model::clearShapes() {
for (int i = 0; i < _jointShapes.size(); ++i) {
delete _jointShapes[i];

View file

@ -182,6 +182,8 @@ public:
bool getJointPosition(int jointIndex, glm::vec3& position) const;
bool getJointRotation(int jointIndex, glm::quat& rotation, bool fromBind = false) const;
QStringList getJointNames() const;
void clearShapes();
void rebuildShapes();

View file

@ -78,6 +78,7 @@ QScriptValue WindowScriptingInterface::showPrompt(const QString& message, const
promptDialog.setWindowTitle("");
promptDialog.setLabelText(message);
promptDialog.setTextValue(defaultText);
promptDialog.setFixedSize(600, 200);
if (promptDialog.exec() == QDialog::Accepted) {
return QScriptValue(promptDialog.textValue());

View file

@ -0,0 +1,37 @@
cmake_minimum_required(VERSION 2.8)
if (WIN32)
cmake_policy (SET CMP0020 NEW)
endif (WIN32)
set(ROOT_DIR ../..)
set(MACRO_DIR "${ROOT_DIR}/cmake/macros")
# setup for find modules
set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} "${CMAKE_CURRENT_SOURCE_DIR}/../../cmake/modules/")
set(TARGET_NAME animation)
find_package(Qt5Widgets REQUIRED)
include(${MACRO_DIR}/SetupHifiLibrary.cmake)
setup_hifi_library(${TARGET_NAME})
include(${MACRO_DIR}/IncludeGLM.cmake)
include_glm(${TARGET_NAME} "${ROOT_DIR}")
include(${MACRO_DIR}/LinkHifiLibrary.cmake)
link_hifi_library(shared ${TARGET_NAME} "${ROOT_DIR}")
link_hifi_library(fbx ${TARGET_NAME} "${ROOT_DIR}")
# link ZLIB
find_package(ZLIB)
find_package(GnuTLS REQUIRED)
# add a definition for ssize_t so that windows doesn't bail on gnutls.h
if (WIN32)
add_definitions(-Dssize_t=long)
endif ()
include_directories(SYSTEM "${ZLIB_INCLUDE_DIRS}" "${GNUTLS_INCLUDE_DIR}")
target_link_libraries(${TARGET_NAME} "${ZLIB_LIBRARIES}" "${GNUTLS_LIBRARY}" Qt5::Widgets)

View file

@ -36,7 +36,8 @@ QSharedPointer<Resource> AnimationCache::createResource(const QUrl& url, const Q
}
Animation::Animation(const QUrl& url) :
Resource(url) {
Resource(url),
_isValid(false) {
}
class AnimationReader : public QRunnable {
@ -93,6 +94,7 @@ QVector<FBXAnimationFrame> Animation::getFrames() const {
void Animation::setGeometry(const FBXGeometry& geometry) {
_geometry = geometry;
finishedLoading(true);
_isValid = true;
}
void Animation::downloadFinished(QNetworkReply* reply) {

View file

@ -53,6 +53,8 @@ public:
Q_INVOKABLE QStringList getJointNames() const;
Q_INVOKABLE QVector<FBXAnimationFrame> getFrames() const;
bool isValid() const { return _isValid; }
protected:
@ -63,6 +65,7 @@ protected:
private:
FBXGeometry _geometry;
bool _isValid;
};
#endif // hifi_AnimationCache_h

View file

@ -577,6 +577,25 @@ const char* FACESHIFT_BLENDSHAPES[] = {
""
};
const char* HUMANIK_JOINTS[] = {
"RightHand",
"RightForeArm",
"RightArm",
"Head",
"LeftArm",
"LeftForeArm",
"LeftHand",
"Spine",
"Hips",
"RightUpLeg",
"LeftUpLeg",
"RightLeg",
"LeftLeg",
"RightFoot",
"LeftFoot",
""
};
class FBXModel {
public:
QString name;
@ -1012,10 +1031,6 @@ FBXGeometry extractFBXGeometry(const FBXNode& node, const QVariantHash& mapping)
QString jointHeadName = processID(getString(joints.value("jointHead", "jointHead")));
QString jointLeftHandName = processID(getString(joints.value("jointLeftHand", "jointLeftHand")));
QString jointRightHandName = processID(getString(joints.value("jointRightHand", "jointRightHand")));
QVariantList jointLeftFingerNames = joints.values("jointLeftFinger");
QVariantList jointRightFingerNames = joints.values("jointRightFinger");
QVariantList jointLeftFingertipNames = joints.values("jointLeftFingertip");
QVariantList jointRightFingertipNames = joints.values("jointRightFingertip");
QString jointEyeLeftID;
QString jointEyeRightID;
QString jointNeckID;
@ -1024,10 +1039,16 @@ FBXGeometry extractFBXGeometry(const FBXNode& node, const QVariantHash& mapping)
QString jointHeadID;
QString jointLeftHandID;
QString jointRightHandID;
QVector<QString> jointLeftFingerIDs(jointLeftFingerNames.size());
QVector<QString> jointRightFingerIDs(jointRightFingerNames.size());
QVector<QString> jointLeftFingertipIDs(jointLeftFingertipNames.size());
QVector<QString> jointRightFingertipIDs(jointRightFingertipNames.size());
QVector<QString> humanIKJointNames;
for (int i = 0;; i++) {
QByteArray jointName = HUMANIK_JOINTS[i];
if (jointName.isEmpty()) {
break;
}
humanIKJointNames.append(processID(getString(joints.value(jointName, jointName))));
}
QVector<QString> humanIKJointIDs(humanIKJointNames.size());
QVariantHash blendshapeMappings = mapping.value("bs").toHash();
QMultiHash<QByteArray, WeightedIndex> blendshapeIndices;
@ -1091,7 +1112,6 @@ FBXGeometry extractFBXGeometry(const FBXNode& node, const QVariantHash& mapping)
} else {
name = getID(object.properties);
}
int index;
if (name == jointEyeLeftName || name == "EyeL" || name == "joint_Leye") {
jointEyeLeftID = getID(object.properties);
@ -1115,19 +1135,12 @@ FBXGeometry extractFBXGeometry(const FBXNode& node, const QVariantHash& mapping)
} else if (name == jointRightHandName) {
jointRightHandID = getID(object.properties);
} else if ((index = jointLeftFingerNames.indexOf(name)) != -1) {
jointLeftFingerIDs[index] = getID(object.properties);
} else if ((index = jointRightFingerNames.indexOf(name)) != -1) {
jointRightFingerIDs[index] = getID(object.properties);
} else if ((index = jointLeftFingertipNames.indexOf(name)) != -1) {
jointLeftFingertipIDs[index] = getID(object.properties);
} else if ((index = jointRightFingertipNames.indexOf(name)) != -1) {
jointRightFingertipIDs[index] = getID(object.properties);
}
int humanIKJointIndex = humanIKJointNames.indexOf(name);
if (humanIKJointIndex != -1) {
humanIKJointIDs[humanIKJointIndex] = getID(object.properties);
}
glm::vec3 translation;
// NOTE: the euler angles as supplied by the FBX file are in degrees
glm::vec3 rotationOffset;
@ -1352,7 +1365,7 @@ FBXGeometry extractFBXGeometry(const FBXNode& node, const QVariantHash& mapping)
} else if (type.contains("bump") || type.contains("normal")) {
bumpTextures.insert(getID(connection.properties, 2), getID(connection.properties, 1));
} else if (type.contains("specular")) {
} else if (type.contains("specular") || type.contains("reflection")) {
specularTextures.insert(getID(connection.properties, 2), getID(connection.properties, 1));
} else if (type == "lcl rotation") {
@ -1385,6 +1398,7 @@ FBXGeometry extractFBXGeometry(const FBXNode& node, const QVariantHash& mapping)
// get offset transform from mapping
float offsetScale = mapping.value("scale", 1.0f).toFloat();
geometry.fstScaled = offsetScale;
glm::quat offsetRotation = glm::quat(glm::radians(glm::vec3(mapping.value("rx").toFloat(),
mapping.value("ry").toFloat(), mapping.value("rz").toFloat())));
geometry.offset = glm::translate(glm::vec3(mapping.value("tx").toFloat(), mapping.value("ty").toFloat(),
@ -1513,11 +1527,11 @@ FBXGeometry extractFBXGeometry(const FBXNode& node, const QVariantHash& mapping)
geometry.headJointIndex = modelIDs.indexOf(jointHeadID);
geometry.leftHandJointIndex = modelIDs.indexOf(jointLeftHandID);
geometry.rightHandJointIndex = modelIDs.indexOf(jointRightHandID);
geometry.leftFingerJointIndices = getIndices(jointLeftFingerIDs, modelIDs);
geometry.rightFingerJointIndices = getIndices(jointRightFingerIDs, modelIDs);
geometry.leftFingertipJointIndices = getIndices(jointLeftFingertipIDs, modelIDs);
geometry.rightFingertipJointIndices = getIndices(jointRightFingertipIDs, modelIDs);
foreach (const QString& id, humanIKJointIDs) {
geometry.humanIKJointIndices.append(modelIDs.indexOf(id));
}
// extract the translation component of the neck transform
if (geometry.neckJointIndex != -1) {
const glm::mat4& transform = geometry.joints.at(geometry.neckJointIndex).transform;

View file

@ -30,6 +30,9 @@ typedef QList<FBXNode> FBXNodeList;
/// The names of the blendshapes expected by Faceshift, terminated with an empty string.
extern const char* FACESHIFT_BLENDSHAPES[];
/// The names of the joints in the Maya HumanIK rig, terminated with an empty string.
extern const char* HUMANIK_JOINTS[];
class Extents {
public:
/// set minimum and maximum to FLT_MAX and -FLT_MAX respectively
@ -199,11 +202,7 @@ public:
int leftHandJointIndex;
int rightHandJointIndex;
QVector<int> leftFingerJointIndices;
QVector<int> rightFingerJointIndices;
QVector<int> leftFingertipJointIndices;
QVector<int> rightFingertipJointIndices;
QVector<int> humanIKJointIndices;
glm::vec3 palmDirection;
@ -212,6 +211,8 @@ public:
Extents bindExtents;
Extents meshExtents;
float fstScaled;
QVector<FBXAnimationFrame> animationFrames;
QVector<FBXAttachment> attachments;

View file

@ -28,6 +28,7 @@ link_hifi_library(shared ${TARGET_NAME} "${ROOT_DIR}")
link_hifi_library(octree ${TARGET_NAME} "${ROOT_DIR}")
link_hifi_library(fbx ${TARGET_NAME} "${ROOT_DIR}")
link_hifi_library(networking ${TARGET_NAME} "${ROOT_DIR}")
link_hifi_library(animation ${TARGET_NAME} "${ROOT_DIR}")
# for streamable
link_hifi_library(metavoxels ${TARGET_NAME} "${ROOT_DIR}")

View file

@ -85,6 +85,15 @@ ModelItem::ModelItem(const ModelItemID& modelItemID, const ModelItemProperties&
_shouldDie = false;
_modelURL = MODEL_DEFAULT_MODEL_URL;
_modelRotation = MODEL_DEFAULT_MODEL_ROTATION;
// animation related
_animationURL = MODEL_DEFAULT_ANIMATION_URL;
_animationIsPlaying = false;
_animationFrameIndex = 0.0f;
_animationFPS = MODEL_DEFAULT_ANIMATION_FPS;
_jointMappingCompleted = false;
_lastAnimated = now;
setProperties(properties);
}
@ -110,6 +119,14 @@ void ModelItem::init(glm::vec3 position, float radius, rgbColor color, uint32_t
_shouldDie = false;
_modelURL = MODEL_DEFAULT_MODEL_URL;
_modelRotation = MODEL_DEFAULT_MODEL_ROTATION;
// animation related
_animationURL = MODEL_DEFAULT_ANIMATION_URL;
_animationIsPlaying = false;
_animationFrameIndex = 0.0f;
_animationFPS = MODEL_DEFAULT_ANIMATION_FPS;
_jointMappingCompleted = false;
_lastAnimated = now;
}
bool ModelItem::appendModelData(OctreePacketData* packetData) const {
@ -150,6 +167,31 @@ bool ModelItem::appendModelData(OctreePacketData* packetData) const {
if (success) {
success = packetData->appendValue(getModelRotation());
}
// animationURL
if (success) {
uint16_t animationURLLength = _animationURL.size() + 1; // include NULL
success = packetData->appendValue(animationURLLength);
if (success) {
success = packetData->appendRawData((const unsigned char*)qPrintable(_animationURL), animationURLLength);
}
}
// animationIsPlaying
if (success) {
success = packetData->appendValue(getAnimationIsPlaying());
}
// animationFrameIndex
if (success) {
success = packetData->appendValue(getAnimationFrameIndex());
}
// animationFPS
if (success) {
success = packetData->appendValue(getAnimationFPS());
}
return success;
}
@ -166,6 +208,7 @@ int ModelItem::expectedBytes() {
}
int ModelItem::readModelDataFromBuffer(const unsigned char* data, int bytesLeftToRead, ReadBitstreamToTreeParams& args) {
int bytesRead = 0;
if (bytesLeftToRead >= expectedBytes()) {
int clockSkew = args.sourceNode ? args.sourceNode->getClockSkewUsec() : 0;
@ -215,7 +258,7 @@ int ModelItem::readModelDataFromBuffer(const unsigned char* data, int bytesLeftT
dataAt += sizeof(modelURLLength);
bytesRead += sizeof(modelURLLength);
QString modelURLString((const char*)dataAt);
_modelURL = modelURLString;
setModelURL(modelURLString);
dataAt += modelURLLength;
bytesRead += modelURLLength;
@ -224,13 +267,37 @@ int ModelItem::readModelDataFromBuffer(const unsigned char* data, int bytesLeftT
dataAt += bytes;
bytesRead += bytes;
//printf("ModelItem::readModelDataFromBuffer()... "); debugDump();
if (args.bitstreamVersion >= VERSION_MODELS_HAVE_ANIMATION) {
// animationURL
uint16_t animationURLLength;
memcpy(&animationURLLength, dataAt, sizeof(animationURLLength));
dataAt += sizeof(animationURLLength);
bytesRead += sizeof(animationURLLength);
QString animationURLString((const char*)dataAt);
setAnimationURL(animationURLString);
dataAt += animationURLLength;
bytesRead += animationURLLength;
// animationIsPlaying
memcpy(&_animationIsPlaying, dataAt, sizeof(_animationIsPlaying));
dataAt += sizeof(_animationIsPlaying);
bytesRead += sizeof(_animationIsPlaying);
// animationFrameIndex
memcpy(&_animationFrameIndex, dataAt, sizeof(_animationFrameIndex));
dataAt += sizeof(_animationFrameIndex);
bytesRead += sizeof(_animationFrameIndex);
// animationFPS
memcpy(&_animationFPS, dataAt, sizeof(_animationFPS));
dataAt += sizeof(_animationFPS);
bytesRead += sizeof(_animationFPS);
}
}
return bytesRead;
}
ModelItem ModelItem::fromEditPacket(const unsigned char* data, int length, int& processedBytes, ModelTree* tree, bool& valid) {
ModelItem newModelItem; // id and _lastUpdated will get set here...
const unsigned char* dataAt = data;
processedBytes = 0;
@ -340,12 +407,52 @@ ModelItem ModelItem::fromEditPacket(const unsigned char* data, int length, int&
}
// modelRotation
if (isNewModelItem || ((packetContainsBits & MODEL_PACKET_CONTAINS_MODEL_ROTATION) == MODEL_PACKET_CONTAINS_MODEL_ROTATION)) {
if (isNewModelItem || ((packetContainsBits &
MODEL_PACKET_CONTAINS_MODEL_ROTATION) == MODEL_PACKET_CONTAINS_MODEL_ROTATION)) {
int bytes = unpackOrientationQuatFromBytes(dataAt, newModelItem._modelRotation);
dataAt += bytes;
processedBytes += bytes;
}
// animationURL
if (isNewModelItem || ((packetContainsBits & MODEL_PACKET_CONTAINS_ANIMATION_URL) == MODEL_PACKET_CONTAINS_ANIMATION_URL)) {
uint16_t animationURLLength;
memcpy(&animationURLLength, dataAt, sizeof(animationURLLength));
dataAt += sizeof(animationURLLength);
processedBytes += sizeof(animationURLLength);
QString tempString((const char*)dataAt);
newModelItem._animationURL = tempString;
dataAt += animationURLLength;
processedBytes += animationURLLength;
}
// animationIsPlaying
if (isNewModelItem || ((packetContainsBits &
MODEL_PACKET_CONTAINS_ANIMATION_PLAYING) == MODEL_PACKET_CONTAINS_ANIMATION_PLAYING)) {
memcpy(&newModelItem._animationIsPlaying, dataAt, sizeof(newModelItem._animationIsPlaying));
dataAt += sizeof(newModelItem._animationIsPlaying);
processedBytes += sizeof(newModelItem._animationIsPlaying);
}
// animationFrameIndex
if (isNewModelItem || ((packetContainsBits &
MODEL_PACKET_CONTAINS_ANIMATION_FRAME) == MODEL_PACKET_CONTAINS_ANIMATION_FRAME)) {
memcpy(&newModelItem._animationFrameIndex, dataAt, sizeof(newModelItem._animationFrameIndex));
dataAt += sizeof(newModelItem._animationFrameIndex);
processedBytes += sizeof(newModelItem._animationFrameIndex);
}
// animationFPS
if (isNewModelItem || ((packetContainsBits &
MODEL_PACKET_CONTAINS_ANIMATION_FPS) == MODEL_PACKET_CONTAINS_ANIMATION_FPS)) {
memcpy(&newModelItem._animationFPS, dataAt, sizeof(newModelItem._animationFPS));
dataAt += sizeof(newModelItem._animationFPS);
processedBytes += sizeof(newModelItem._animationFPS);
}
const bool wantDebugging = false;
if (wantDebugging) {
qDebug("ModelItem::fromEditPacket()...");
@ -363,6 +470,7 @@ void ModelItem::debugDump() const {
qDebug(" position:%f,%f,%f", _position.x, _position.y, _position.z);
qDebug(" radius:%f", getRadius());
qDebug(" color:%d,%d,%d", _color[0], _color[1], _color[2]);
qDebug() << " modelURL:" << qPrintable(getModelURL());
}
bool ModelItem::encodeModelEditMessageDetails(PacketType command, ModelItemID id, const ModelItemProperties& properties,
@ -476,6 +584,47 @@ bool ModelItem::encodeModelEditMessageDetails(PacketType command, ModelItemID id
sizeOut += bytes;
}
// animationURL
if (isNewModelItem || ((packetContainsBits & MODEL_PACKET_CONTAINS_ANIMATION_URL) == MODEL_PACKET_CONTAINS_ANIMATION_URL)) {
uint16_t urlLength = properties.getAnimationURL().size() + 1;
memcpy(copyAt, &urlLength, sizeof(urlLength));
copyAt += sizeof(urlLength);
sizeOut += sizeof(urlLength);
memcpy(copyAt, qPrintable(properties.getAnimationURL()), urlLength);
copyAt += urlLength;
sizeOut += urlLength;
}
// animationIsPlaying
if (isNewModelItem || ((packetContainsBits &
MODEL_PACKET_CONTAINS_ANIMATION_PLAYING) == MODEL_PACKET_CONTAINS_ANIMATION_PLAYING)) {
bool animationIsPlaying = properties.getAnimationIsPlaying();
memcpy(copyAt, &animationIsPlaying, sizeof(animationIsPlaying));
copyAt += sizeof(animationIsPlaying);
sizeOut += sizeof(animationIsPlaying);
}
// animationFrameIndex
if (isNewModelItem || ((packetContainsBits &
MODEL_PACKET_CONTAINS_ANIMATION_FRAME) == MODEL_PACKET_CONTAINS_ANIMATION_FRAME)) {
float animationFrameIndex = properties.getAnimationFrameIndex();
memcpy(copyAt, &animationFrameIndex, sizeof(animationFrameIndex));
copyAt += sizeof(animationFrameIndex);
sizeOut += sizeof(animationFrameIndex);
}
// animationFPS
if (isNewModelItem || ((packetContainsBits &
MODEL_PACKET_CONTAINS_ANIMATION_FPS) == MODEL_PACKET_CONTAINS_ANIMATION_FPS)) {
float animationFPS = properties.getAnimationFPS();
memcpy(copyAt, &animationFPS, sizeof(animationFPS));
copyAt += sizeof(animationFPS);
sizeOut += sizeof(animationFPS);
}
bool wantDebugging = false;
if (wantDebugging) {
qDebug("encodeModelItemEditMessageDetails()....");
@ -521,9 +670,106 @@ void ModelItem::adjustEditPacketForClockSkew(unsigned char* codeColorBuffer, ssi
}
}
void ModelItem::update(const quint64& now) {
_lastUpdated = now;
QMap<QString, AnimationPointer> ModelItem::_loadedAnimations; // TODO: improve cleanup by leveraging the AnimationPointer(s)
AnimationCache ModelItem::_animationCache;
// This class/instance will cleanup the animations once unloaded.
class ModelAnimationsBookkeeper {
public:
~ModelAnimationsBookkeeper() {
ModelItem::cleanupLoadedAnimations();
}
};
ModelAnimationsBookkeeper modelAnimationsBookkeeperInstance;
void ModelItem::cleanupLoadedAnimations() {
foreach(AnimationPointer animation, _loadedAnimations) {
animation.clear();
}
_loadedAnimations.clear();
}
Animation* ModelItem::getAnimation(const QString& url) {
AnimationPointer animation;
// if we don't already have this model then create it and initialize it
if (_loadedAnimations.find(url) == _loadedAnimations.end()) {
animation = _animationCache.getAnimation(url);
_loadedAnimations[url] = animation;
} else {
animation = _loadedAnimations[url];
}
return animation.data();
}
void ModelItem::mapJoints(const QStringList& modelJointNames) {
// if we don't have animation, or we're already joint mapped then bail early
if (!hasAnimation() || _jointMappingCompleted) {
return;
}
Animation* myAnimation = getAnimation(_animationURL);
if (!_jointMappingCompleted) {
QStringList animationJointNames = myAnimation->getJointNames();
if (modelJointNames.size() > 0 && animationJointNames.size() > 0) {
_jointMapping.resize(modelJointNames.size());
for (int i = 0; i < modelJointNames.size(); i++) {
_jointMapping[i] = animationJointNames.indexOf(modelJointNames[i]);
}
_jointMappingCompleted = true;
}
}
}
QVector<glm::quat> ModelItem::getAnimationFrame() {
QVector<glm::quat> frameData;
if (hasAnimation() && _jointMappingCompleted) {
Animation* myAnimation = getAnimation(_animationURL);
QVector<FBXAnimationFrame> frames = myAnimation->getFrames();
int animationFrameIndex = (int)std::floor(_animationFrameIndex) % frames.size();
QVector<glm::quat> rotations = frames[animationFrameIndex].rotations;
frameData.resize(_jointMapping.size());
for (int j = 0; j < _jointMapping.size(); j++) {
int rotationIndex = _jointMapping[j];
if (rotationIndex != -1 && rotationIndex < rotations.size()) {
frameData[j] = rotations[rotationIndex];
}
}
}
return frameData;
}
void ModelItem::update(const quint64& updateTime) {
_lastUpdated = updateTime;
setShouldDie(getShouldDie());
quint64 now = usecTimestampNow();
// only advance the frame index if we're playing
if (getAnimationIsPlaying()) {
float deltaTime = (float)(now - _lastAnimated) / (float)USECS_PER_SECOND;
const bool wantDebugging = false;
if (wantDebugging) {
qDebug() << "ModelItem::update() now=" << now;
qDebug() << " updateTime=" << updateTime;
qDebug() << " _lastAnimated=" << _lastAnimated;
qDebug() << " deltaTime=" << deltaTime;
}
_lastAnimated = now;
_animationFrameIndex += deltaTime * _animationFPS;
if (wantDebugging) {
qDebug() << " _animationFrameIndex=" << _animationFrameIndex;
}
} else {
_lastAnimated = now;
}
}
void ModelItem::copyChangedProperties(const ModelItem& other) {
@ -547,6 +793,10 @@ ModelItemProperties::ModelItemProperties() :
_shouldDie(false),
_modelURL(""),
_modelRotation(MODEL_DEFAULT_MODEL_ROTATION),
_animationURL(""),
_animationIsPlaying(false),
_animationFrameIndex(0.0),
_animationFPS(MODEL_DEFAULT_ANIMATION_FPS),
_id(UNKNOWN_MODEL_ID),
_idSet(false),
@ -558,6 +808,10 @@ ModelItemProperties::ModelItemProperties() :
_shouldDieChanged(false),
_modelURLChanged(false),
_modelRotationChanged(false),
_animationURLChanged(false),
_animationIsPlayingChanged(false),
_animationFrameIndexChanged(false),
_animationFPSChanged(false),
_defaultSettings(true)
{
}
@ -589,6 +843,22 @@ uint16_t ModelItemProperties::getChangedBits() const {
changedBits += MODEL_PACKET_CONTAINS_MODEL_ROTATION;
}
if (_animationURLChanged) {
changedBits += MODEL_PACKET_CONTAINS_ANIMATION_URL;
}
if (_animationIsPlayingChanged) {
changedBits += MODEL_PACKET_CONTAINS_ANIMATION_PLAYING;
}
if (_animationFrameIndexChanged) {
changedBits += MODEL_PACKET_CONTAINS_ANIMATION_FRAME;
}
if (_animationFPSChanged) {
changedBits += MODEL_PACKET_CONTAINS_ANIMATION_FPS;
}
return changedBits;
}
@ -611,6 +881,10 @@ QScriptValue ModelItemProperties::copyToScriptValue(QScriptEngine* engine) const
QScriptValue modelRotation = quatToScriptValue(engine, _modelRotation);
properties.setProperty("modelRotation", modelRotation);
properties.setProperty("animationURL", _animationURL);
properties.setProperty("animationIsPlaying", _animationIsPlaying);
properties.setProperty("animationFrameIndex", _animationFrameIndex);
properties.setProperty("animationFPS", _animationFPS);
if (_idSet) {
properties.setProperty("id", _id);
@ -707,6 +981,46 @@ void ModelItemProperties::copyFromScriptValue(const QScriptValue &object) {
}
}
QScriptValue animationURL = object.property("animationURL");
if (animationURL.isValid()) {
QString newAnimationURL;
newAnimationURL = animationURL.toVariant().toString();
if (_defaultSettings || newAnimationURL != _animationURL) {
_animationURL = newAnimationURL;
_animationURLChanged = true;
}
}
QScriptValue animationIsPlaying = object.property("animationIsPlaying");
if (animationIsPlaying.isValid()) {
bool newIsAnimationPlaying;
newIsAnimationPlaying = animationIsPlaying.toVariant().toBool();
if (_defaultSettings || newIsAnimationPlaying != _animationIsPlaying) {
_animationIsPlaying = newIsAnimationPlaying;
_animationIsPlayingChanged = true;
}
}
QScriptValue animationFrameIndex = object.property("animationFrameIndex");
if (animationFrameIndex.isValid()) {
float newFrameIndex;
newFrameIndex = animationFrameIndex.toVariant().toFloat();
if (_defaultSettings || newFrameIndex != _animationFrameIndex) {
_animationFrameIndex = newFrameIndex;
_animationFrameIndexChanged = true;
}
}
QScriptValue animationFPS = object.property("animationFPS");
if (animationFPS.isValid()) {
float newFPS;
newFPS = animationFPS.toVariant().toFloat();
if (_defaultSettings || newFPS != _animationFPS) {
_animationFPS = newFPS;
_animationFPSChanged = true;
}
}
_lastEdited = usecTimestampNow();
}
@ -741,7 +1055,27 @@ void ModelItemProperties::copyToModelItem(ModelItem& modelItem) const {
modelItem.setModelRotation(_modelRotation);
somethingChanged = true;
}
if (_animationURLChanged) {
modelItem.setAnimationURL(_animationURL);
somethingChanged = true;
}
if (_animationIsPlayingChanged) {
modelItem.setAnimationIsPlaying(_animationIsPlaying);
somethingChanged = true;
}
if (_animationFrameIndexChanged) {
modelItem.setAnimationFrameIndex(_animationFrameIndex);
somethingChanged = true;
}
if (_animationFPSChanged) {
modelItem.setAnimationFPS(_animationFPS);
somethingChanged = true;
}
if (somethingChanged) {
bool wantDebug = false;
if (wantDebug) {
@ -761,6 +1095,10 @@ void ModelItemProperties::copyFromModelItem(const ModelItem& modelItem) {
_shouldDie = modelItem.getShouldDie();
_modelURL = modelItem.getModelURL();
_modelRotation = modelItem.getModelRotation();
_animationURL = modelItem.getAnimationURL();
_animationIsPlaying = modelItem.getAnimationIsPlaying();
_animationFrameIndex = modelItem.getAnimationFrameIndex();
_animationFPS = modelItem.getAnimationFPS();
_id = modelItem.getID();
_idSet = true;
@ -772,6 +1110,10 @@ void ModelItemProperties::copyFromModelItem(const ModelItem& modelItem) {
_shouldDieChanged = false;
_modelURLChanged = false;
_modelRotationChanged = false;
_animationURLChanged = false;
_animationIsPlayingChanged = false;
_animationFrameIndexChanged = false;
_animationFPSChanged = false;
_defaultSettings = false;
}

View file

@ -18,6 +18,7 @@
#include <QtScript/QScriptEngine>
#include <QtCore/QObject>
#include <AnimationCache.h>
#include <CollisionInfo.h>
#include <SharedUtil.h>
#include <OctreePacketData.h>
@ -39,14 +40,22 @@ const uint32_t UNKNOWN_MODEL_ID = 0xFFFFFFFF;
const uint16_t MODEL_PACKET_CONTAINS_RADIUS = 1;
const uint16_t MODEL_PACKET_CONTAINS_POSITION = 2;
const uint16_t MODEL_PACKET_CONTAINS_COLOR = 4;
const uint16_t MODEL_PACKET_CONTAINS_SHOULDDIE = 512;
const uint16_t MODEL_PACKET_CONTAINS_MODEL_URL = 1024;
const uint16_t MODEL_PACKET_CONTAINS_MODEL_ROTATION = 2048;
const uint16_t MODEL_PACKET_CONTAINS_SHOULDDIE = 8;
const uint16_t MODEL_PACKET_CONTAINS_MODEL_URL = 16;
const uint16_t MODEL_PACKET_CONTAINS_MODEL_ROTATION = 32;
const uint16_t MODEL_PACKET_CONTAINS_ANIMATION_URL = 64;
const uint16_t MODEL_PACKET_CONTAINS_ANIMATION_PLAYING = 128;
const uint16_t MODEL_PACKET_CONTAINS_ANIMATION_FRAME = 256;
const uint16_t MODEL_PACKET_CONTAINS_ANIMATION_FPS = 512;
const float MODEL_DEFAULT_RADIUS = 0.1f / TREE_SCALE;
const float MINIMUM_MODEL_ELEMENT_SIZE = (1.0f / 100000.0f) / TREE_SCALE; // smallest size container
const QString MODEL_DEFAULT_MODEL_URL("");
const glm::quat MODEL_DEFAULT_MODEL_ROTATION;
const QString MODEL_DEFAULT_ANIMATION_URL("");
const float MODEL_DEFAULT_ANIMATION_FPS = 30.0f;
const PacketVersion VERSION_MODELS_HAVE_ANIMATION = 1;
/// A collection of properties of a model item used in the scripting API. Translates between the actual properties of a model
/// and a JavaScript style hash/QScriptValue storing a set of properties. Used in scripting to set/get the complete set of
@ -69,6 +78,10 @@ public:
const QString& getModelURL() const { return _modelURL; }
const glm::quat& getModelRotation() const { return _modelRotation; }
const QString& getAnimationURL() const { return _animationURL; }
float getAnimationFrameIndex() const { return _animationFrameIndex; }
bool getAnimationIsPlaying() const { return _animationIsPlaying; }
float getAnimationFPS() const { return _animationFPS; }
quint64 getLastEdited() const { return _lastEdited; }
uint16_t getChangedBits() const;
@ -82,6 +95,10 @@ public:
// model related properties
void setModelURL(const QString& url) { _modelURL = url; _modelURLChanged = true; }
void setModelRotation(const glm::quat& rotation) { _modelRotation = rotation; _modelRotationChanged = true; }
void setAnimationURL(const QString& url) { _animationURL = url; _animationURLChanged = true; }
void setAnimationFrameIndex(float value) { _animationFrameIndex = value; _animationFrameIndexChanged = true; }
void setAnimationIsPlaying(bool value) { _animationIsPlaying = value; _animationIsPlayingChanged = true; }
void setAnimationFPS(float value) { _animationFPS = value; _animationFPSChanged = true; }
/// used by ModelScriptingInterface to return ModelItemProperties for unknown models
void setIsUnknownID() { _id = UNKNOWN_MODEL_ID; _idSet = true; }
@ -97,6 +114,10 @@ private:
QString _modelURL;
glm::quat _modelRotation;
QString _animationURL;
bool _animationIsPlaying;
float _animationFrameIndex;
float _animationFPS;
uint32_t _id;
bool _idSet;
@ -109,6 +130,10 @@ private:
bool _modelURLChanged;
bool _modelRotationChanged;
bool _animationURLChanged;
bool _animationIsPlayingChanged;
bool _animationFrameIndexChanged;
bool _animationFPSChanged;
bool _defaultSettings;
};
Q_DECLARE_METATYPE(ModelItemProperties);
@ -178,6 +203,8 @@ public:
bool hasModel() const { return !_modelURL.isEmpty(); }
const QString& getModelURL() const { return _modelURL; }
const glm::quat& getModelRotation() const { return _modelRotation; }
bool hasAnimation() const { return !_animationURL.isEmpty(); }
const QString& getAnimationURL() const { return _animationURL; }
ModelItemID getModelItemID() const { return ModelItemID(getID(), getCreatorTokenID(), getID() != UNKNOWN_MODEL_ID); }
ModelItemProperties getProperties() const;
@ -196,6 +223,7 @@ public:
bool getShouldDie() const { return _shouldDie; }
uint32_t getCreatorTokenID() const { return _creatorTokenID; }
bool isNewlyCreated() const { return _newlyCreated; }
bool isKnownID() const { return getID() != UNKNOWN_MODEL_ID; }
/// set position in domain scale units (0.0 - 1.0)
void setPosition(const glm::vec3& value) { _position = value; }
@ -215,6 +243,10 @@ public:
// model related properties
void setModelURL(const QString& url) { _modelURL = url; }
void setModelRotation(const glm::quat& rotation) { _modelRotation = rotation; }
void setAnimationURL(const QString& url) { _animationURL = url; }
void setAnimationFrameIndex(float value) { _animationFrameIndex = value; }
void setAnimationIsPlaying(bool value) { _animationIsPlaying = value; }
void setAnimationFPS(float value) { _animationFPS = value; }
void setProperties(const ModelItemProperties& properties);
@ -239,6 +271,16 @@ public:
static uint32_t getNextCreatorTokenID();
static void handleAddModelResponse(const QByteArray& packet);
void mapJoints(const QStringList& modelJointNames);
QVector<glm::quat> getAnimationFrame();
bool jointsMapped() const { return _jointMappingCompleted; }
bool getAnimationIsPlaying() const { return _animationIsPlaying; }
float getAnimationFrameIndex() const { return _animationFrameIndex; }
float getAnimationFPS() const { return _animationFPS; }
static void cleanupLoadedAnimations();
protected:
glm::vec3 _position;
rgbColor _color;
@ -256,10 +298,26 @@ protected:
quint64 _lastUpdated;
quint64 _lastEdited;
quint64 _lastAnimated;
QString _animationURL;
float _animationFrameIndex; // we keep this as a float and round to int only when we need the exact index
bool _animationIsPlaying;
float _animationFPS;
bool _jointMappingCompleted;
QVector<int> _jointMapping;
// used by the static interfaces for creator token ids
static uint32_t _nextCreatorTokenID;
static std::map<uint32_t,uint32_t> _tokenIDsToIDs;
static Animation* getAnimation(const QString& url);
static QMap<QString, AnimationPointer> _loadedAnimations;
static AnimationCache _animationCache;
};
#endif // hifi_ModelItem_h

View file

@ -491,11 +491,12 @@ void ModelTree::update() {
lockForWrite();
_isDirty = true;
ModelTreeUpdateArgs args = { };
ModelTreeUpdateArgs args;
recurseTreeWithOperation(updateOperation, &args);
// now add back any of the particles that moved elements....
int movingModels = args._movingModels.size();
for (int i = 0; i < movingModels; i++) {
bool shouldDie = args._movingModels[i].getShouldDie();
@ -553,7 +554,7 @@ bool ModelTree::encodeModelsDeletedSince(quint64& sinceTime, unsigned char* outp
memcpy(copyAt, &numberOfIds, sizeof(numberOfIds));
copyAt += sizeof(numberOfIds);
outputLength += sizeof(numberOfIds);
// we keep a multi map of model IDs to timestamps, we only want to include the model IDs that have been
// deleted since we last sent to this node
_recentlyDeletedModelsLock.lockForRead();
@ -595,7 +596,6 @@ bool ModelTree::encodeModelsDeletedSince(quint64& sinceTime, unsigned char* outp
// replace the correct count for ids included
memcpy(numberOfIDsAt, &numberOfIds, sizeof(numberOfIds));
return hasMoreToSend;
}

View file

@ -36,6 +36,7 @@ public:
// own definition. Implement these to allow your octree based server to support editing
virtual bool getWantSVOfileVersions() const { return true; }
virtual PacketType expectedDataPacketType() const { return PacketTypeModelData; }
virtual bool canProcessVersion(PacketVersion thisVersion) const { return true; } // we support all versions
virtual bool handlesEditPacketType(PacketType packetType) const;
virtual int processEditPacketData(PacketType packetType, const unsigned char* packetData, int packetLength,
const unsigned char* editData, int maxLength, const SharedNodePointer& senderNode);

View file

@ -84,14 +84,17 @@ bool ModelTreeElement::appendElementData(OctreePacketData* packetData, EncodeBit
}
bool ModelTreeElement::containsModelBounds(const ModelItem& model) const {
return _box.contains(model.getMinimumPoint()) && _box.contains(model.getMaximumPoint());
glm::vec3 clampedMin = glm::clamp(model.getMinimumPoint(), 0.0f, 1.0f);
glm::vec3 clampedMax = glm::clamp(model.getMaximumPoint(), 0.0f, 1.0f);
return _box.contains(clampedMin) && _box.contains(clampedMax);
}
bool ModelTreeElement::bestFitModelBounds(const ModelItem& model) const {
if (_box.contains(model.getMinimumPoint()) && _box.contains(model.getMaximumPoint())) {
int childForMinimumPoint = getMyChildContainingPoint(model.getMinimumPoint());
int childForMaximumPoint = getMyChildContainingPoint(model.getMaximumPoint());
glm::vec3 clampedMin = glm::clamp(model.getMinimumPoint(), 0.0f, 1.0f);
glm::vec3 clampedMax = glm::clamp(model.getMaximumPoint(), 0.0f, 1.0f);
if (_box.contains(clampedMin) && _box.contains(clampedMax)) {
int childForMinimumPoint = getMyChildContainingPoint(clampedMin);
int childForMaximumPoint = getMyChildContainingPoint(clampedMax);
// If I contain both the minimum and maximum point, but two different children of mine
// contain those points, then I am the best fit for that model
if (childForMinimumPoint != childForMaximumPoint) {
@ -102,10 +105,16 @@ bool ModelTreeElement::bestFitModelBounds(const ModelItem& model) const {
}
void ModelTreeElement::update(ModelTreeUpdateArgs& args) {
args._totalElements++;
// update our contained models
QList<ModelItem>::iterator modelItr = _modelItems->begin();
while(modelItr != _modelItems->end()) {
ModelItem& model = (*modelItr);
args._totalItems++;
// TODO: this _lastChanged isn't actually changing because we're not marking this element as changed.
// how do we want to handle this??? We really only want to consider an element changed when it is
// edited... not just animated...
model.update(_lastChanged);
// If the model wants to die, or if it's left our bounding box, then move it
@ -115,6 +124,8 @@ void ModelTreeElement::update(ModelTreeUpdateArgs& args) {
// erase this model
modelItr = _modelItems->erase(modelItr);
args._movingItems++;
// this element has changed so mark it...
markWithChangedTime();
@ -324,7 +335,7 @@ int ModelTreeElement::readElementDataFromBuffer(const unsigned char* data, int b
dataAt += sizeof(numberOfModels);
bytesLeftToRead -= (int)sizeof(numberOfModels);
bytesRead += sizeof(numberOfModels);
if (bytesLeftToRead >= (int)(numberOfModels * expectedBytesPerModel)) {
for (uint16_t i = 0; i < numberOfModels; i++) {
ModelItem tempModel;

View file

@ -23,7 +23,16 @@ class ModelTreeElement;
class ModelTreeUpdateArgs {
public:
ModelTreeUpdateArgs() :
_totalElements(0),
_totalItems(0),
_movingItems(0)
{ }
QList<ModelItem> _movingModels;
int _totalElements;
int _totalItems;
int _movingItems;
};
class FindAndUpdateModelItemIDArgs {
@ -63,7 +72,11 @@ public:
/// Should this element be considered to have content in it. This will be used in collision and ray casting methods.
/// By default we assume that only leaves are actual content, but some octrees may have different semantics.
virtual bool hasContent() const { return isLeaf(); }
virtual bool hasContent() const { return hasModels(); }
/// Should this element be considered to have detailed content in it. Specifically should it be rendered.
/// By default we assume that only leaves have detailed content, but some octrees may have different semantics.
virtual bool hasDetailedContent() const { return hasModels(); }
/// Override this to break up large octree elements when an edit operation is performed on a smaller octree element.
/// For example, if the octrees represent solid cubes and a delete of a smaller octree element is done then the
@ -92,7 +105,7 @@ public:
const QList<ModelItem>& getModels() const { return *_modelItems; }
QList<ModelItem>& getModels() { return *_modelItems; }
bool hasModels() const { return _modelItems->size() > 0; }
bool hasModels() const { return _modelItems ? _modelItems->size() > 0 : false; }
void update(ModelTreeUpdateArgs& args);
void setTree(ModelTree* tree) { _myTree = tree; }

View file

@ -105,7 +105,6 @@ ModelItemID ModelsScriptingInterface::editModel(ModelItemID modelID, const Model
_modelTree->updateModel(modelID, properties);
_modelTree->unlock();
}
return modelID;
}

View file

@ -66,6 +66,8 @@ PacketVersion versionForPacketType(PacketType type) {
return 1;
case PacketTypeOctreeStats:
return 1;
case PacketTypeModelData:
return 1;
default:
return 0;
}

View file

@ -335,10 +335,8 @@ void Octree::readBitstreamToTree(const unsigned char * bitstream, unsigned long
int octalCodeBytes = bytesRequiredForCodeLength(*bitstreamAt);
int theseBytesRead = 0;
theseBytesRead += octalCodeBytes;
theseBytesRead += readElementData(bitstreamRootElement, bitstreamAt + octalCodeBytes,
bufferSizeBytes - (bytesRead + octalCodeBytes), args);
// skip bitstream to new startPoint
bitstreamAt += theseBytesRead;
bytesRead += theseBytesRead;
@ -1556,6 +1554,7 @@ int Octree::encodeTreeBitstreamRecursion(OctreeElement* element,
bool Octree::readFromSVOFile(const char* fileName) {
bool fileOk = false;
PacketVersion gotVersion = 0;
std::ifstream file(fileName, std::ios::in|std::ios::binary|std::ios::ate);
if(file.is_open()) {
emit importSize(1.0f, 1.0f, 1.0f);
@ -1586,14 +1585,16 @@ bool Octree::readFromSVOFile(const char* fileName) {
if (gotType == expectedType) {
dataAt += sizeof(expectedType);
dataLength -= sizeof(expectedType);
PacketVersion expectedVersion = versionForPacketType(expectedType);
PacketVersion gotVersion = *dataAt;
if (gotVersion == expectedVersion) {
dataAt += sizeof(expectedVersion);
dataLength -= sizeof(expectedVersion);
gotVersion = *dataAt;
if (canProcessVersion(gotVersion)) {
dataAt += sizeof(gotVersion);
dataLength -= sizeof(gotVersion);
fileOk = true;
qDebug("SVO file version match. Expected: %d Got: %d",
versionForPacketType(expectedDataPacketType()), gotVersion);
} else {
qDebug("SVO file version mismatch. Expected: %d Got: %d", expectedVersion, gotVersion);
qDebug("SVO file version mismatch. Expected: %d Got: %d",
versionForPacketType(expectedDataPacketType()), gotVersion);
}
} else {
qDebug("SVO file type mismatch. Expected: %c Got: %c", expectedType, gotType);
@ -1602,7 +1603,8 @@ bool Octree::readFromSVOFile(const char* fileName) {
fileOk = true; // assume the file is ok
}
if (fileOk) {
ReadBitstreamToTreeParams args(WANT_COLOR, NO_EXISTS_BITS, NULL, 0, SharedNodePointer(), wantImportProgress);
ReadBitstreamToTreeParams args(WANT_COLOR, NO_EXISTS_BITS, NULL, 0,
SharedNodePointer(), wantImportProgress, gotVersion);
readBitstreamToTree(dataAt, dataLength, args);
}
delete[] entireFile;
@ -1615,7 +1617,6 @@ bool Octree::readFromSVOFile(const char* fileName) {
}
void Octree::writeToSVOFile(const char* fileName, OctreeElement* element) {
std::ofstream file(fileName, std::ios::out|std::ios::binary);
if(file.is_open()) {
@ -1638,13 +1639,12 @@ void Octree::writeToSVOFile(const char* fileName, OctreeElement* element) {
nodeBag.insert(_rootElement);
}
static OctreePacketData packetData;
OctreePacketData packetData;
int bytesWritten = 0;
bool lastPacketWritten = false;
while (!nodeBag.isEmpty()) {
OctreeElement* subTree = nodeBag.extract();
lockForRead(); // do tree locking down here so that we have shorter slices and less thread contention
EncodeBitstreamParams params(INT_MAX, IGNORE_VIEW_FRUSTUM, WANT_COLOR, NO_EXISTS_BITS);
bytesWritten = encodeTreeBitstream(subTree, &packetData, nodeBag, params);
@ -1666,7 +1666,6 @@ void Octree::writeToSVOFile(const char* fileName, OctreeElement* element) {
if (!lastPacketWritten) {
file.write((const char*)packetData.getFinalizedData(), packetData.getFinalizedSize());
}
}
file.close();
}

View file

@ -170,6 +170,7 @@ public:
QUuid sourceUUID;
SharedNodePointer sourceNode;
bool wantImportProgress;
PacketVersion bitstreamVersion;
ReadBitstreamToTreeParams(
bool includeColor = WANT_COLOR,
@ -177,13 +178,15 @@ public:
OctreeElement* destinationElement = NULL,
QUuid sourceUUID = QUuid(),
SharedNodePointer sourceNode = SharedNodePointer(),
bool wantImportProgress = false) :
bool wantImportProgress = false,
PacketVersion bitstreamVersion = 0) :
includeColor(includeColor),
includeExistsBits(includeExistsBits),
destinationElement(destinationElement),
sourceUUID(sourceUUID),
sourceNode(sourceNode),
wantImportProgress(wantImportProgress)
wantImportProgress(wantImportProgress),
bitstreamVersion(bitstreamVersion)
{}
};
@ -200,6 +203,9 @@ public:
// own definition. Implement these to allow your octree based server to support editing
virtual bool getWantSVOfileVersions() const { return false; }
virtual PacketType expectedDataPacketType() const { return PacketTypeUnknown; }
virtual bool canProcessVersion(PacketVersion thisVersion) const {
return thisVersion == versionForPacketType(expectedDataPacketType()); }
virtual PacketVersion expectedVersion() const { return versionForPacketType(expectedDataPacketType()); }
virtual bool handlesEditPacketType(PacketType packetType) const { return false; }
virtual int processEditPacketData(PacketType packetType, const unsigned char* packetData, int packetLength,
const unsigned char* editData, int maxLength, const SharedNodePointer& sourceNode) { return 0; }
@ -303,6 +309,7 @@ public:
bool getIsViewing() const { return _isViewing; }
void setIsViewing(bool isViewing) { _isViewing = isViewing; }
signals:
void importSize(float x, float y, float z);

View file

@ -1213,7 +1213,7 @@ bool OctreeElement::calculateShouldRender(const ViewFrustum* viewFrustum, float
float furthestDistance = furthestDistanceToCamera(*viewFrustum);
float childBoundary = boundaryDistanceForRenderLevel(getLevel() + 1 + boundaryLevelAdjust, voxelScaleSize);
bool inChildBoundary = (furthestDistance <= childBoundary);
if (isLeaf() && inChildBoundary) {
if (hasDetailedContent() && inChildBoundary) {
shouldRender = true;
} else {
float boundary = childBoundary * 2.0f; // the boundary is always twice the distance of the child boundary

View file

@ -71,6 +71,10 @@ public:
/// Should this element be considered to have content in it. This will be used in collision and ray casting methods.
/// By default we assume that only leaves are actual content, but some octrees may have different semantics.
virtual bool hasContent() const { return isLeaf(); }
/// Should this element be considered to have detailed content in it. Specifically should it be rendered.
/// By default we assume that only leaves have detailed content, but some octrees may have different semantics.
virtual bool hasDetailedContent() const { return isLeaf(); }
/// Override this to break up large octree elements when an edit operation is performed on a smaller octree element.
/// For example, if the octrees represent solid cubes and a delete of a smaller octree element is done then the

View file

@ -64,6 +64,7 @@ void OctreeRenderer::processDatagram(const QByteArray& dataByteArray, const Shar
unsigned int numBytesPacketHeader = numBytesForPacketHeader(dataByteArray);
QUuid sourceUUID = uuidFromPacketHeader(dataByteArray);
PacketType expectedType = getExpectedPacketType();
PacketVersion expectedVersion = _tree->expectedVersion(); // TODO: would be better to read this from the packet!
if(command == expectedType) {
PerformanceWarning warn(showTimingDetails, "OctreeRenderer::processDatagram expected PacketType", showTimingDetails);
@ -115,7 +116,7 @@ void OctreeRenderer::processDatagram(const QByteArray& dataByteArray, const Shar
if (sectionLength) {
// ask the VoxelTree to read the bitstream into the tree
ReadBitstreamToTreeParams args(packetIsColored ? WANT_COLOR : NO_COLOR, WANT_EXISTS_BITS, NULL,
sourceUUID, sourceNode);
sourceUUID, sourceNode, false, expectedVersion);
_tree->lockForWrite();
OctreePacketData packetData(packetIsCompressed);
packetData.loadFinalizedContent(dataAt, sectionLength);
@ -155,7 +156,7 @@ bool OctreeRenderer::renderOperation(OctreeElement* element, void* extraData) {
}
void OctreeRenderer::render(RenderMode renderMode) {
RenderArgs args = { 0, this, _viewFrustum, getSizeScale(), getBoundaryLevelAdjust(), renderMode };
RenderArgs args = { this, _viewFrustum, getSizeScale(), getBoundaryLevelAdjust(), renderMode, 0, 0, 0 };
if (_tree) {
_tree->lockForRead();
_tree->recurseTreeWithOperation(renderOperation, &args);

View file

@ -71,12 +71,15 @@ protected:
class RenderArgs {
public:
int _renderedItems;
OctreeRenderer* _renderer;
ViewFrustum* _viewFrustum;
float _sizeScale;
int _boundaryLevelAdjust;
OctreeRenderer::RenderMode _renderMode;
int _elementsTouched;
int _itemsRendered;
int _itemsOutOfView;
};

View file

@ -25,6 +25,7 @@ link_hifi_library(shared ${TARGET_NAME} "${ROOT_DIR}")
link_hifi_library(octree ${TARGET_NAME} "${ROOT_DIR}")
link_hifi_library(fbx ${TARGET_NAME} "${ROOT_DIR}")
link_hifi_library(networking ${TARGET_NAME} "${ROOT_DIR}")
link_hifi_library(animation ${TARGET_NAME} "${ROOT_DIR}")
# link ZLIB and GnuTLS
find_package(ZLIB)

View file

@ -27,6 +27,7 @@ link_hifi_library(voxels ${TARGET_NAME} "${ROOT_DIR}")
link_hifi_library(fbx ${TARGET_NAME} "${ROOT_DIR}")
link_hifi_library(particles ${TARGET_NAME} "${ROOT_DIR}")
link_hifi_library(models ${TARGET_NAME} "${ROOT_DIR}")
link_hifi_library(animation ${TARGET_NAME} "${ROOT_DIR}")
# link ZLIB
find_package(ZLIB)

View file

@ -35,6 +35,7 @@
#include "MenuItemProperties.h"
#include "LocalVoxels.h"
#include "ScriptEngine.h"
#include "XMLHttpRequestClass.h"
VoxelsScriptingInterface ScriptEngine::_voxelsScriptingInterface;
ParticlesScriptingInterface ScriptEngine::_particlesScriptingInterface;
@ -49,7 +50,12 @@ static QScriptValue soundConstructor(QScriptContext* context, QScriptEngine* eng
static QScriptValue debugPrint(QScriptContext* context, QScriptEngine* engine){
qDebug() << "script:print()<<" << context->argument(0).toString();
engine->evaluate("Script.print('" + context->argument(0).toString() + "')");
QString message = context->argument(0).toString()
.replace("\\", "\\\\")
.replace("\n", "\\n")
.replace("\r", "\\r")
.replace("'", "\\'");
engine->evaluate("Script.print('" + message + "')");
return QScriptValue();
}
@ -224,6 +230,9 @@ void ScriptEngine::init() {
qScriptRegisterSequenceMetaType<QVector<glm::quat> >(&_engine);
qScriptRegisterSequenceMetaType<QVector<QString> >(&_engine);
QScriptValue xmlHttpRequestConstructorValue = _engine.newFunction(XMLHttpRequestClass::constructor);
_engine.globalObject().setProperty("XMLHttpRequest", xmlHttpRequestConstructorValue);
QScriptValue printConstructorValue = _engine.newFunction(debugPrint);
_engine.globalObject().setProperty("print", printConstructorValue);

View file

@ -18,13 +18,12 @@
#include <QtCore/QUrl>
#include <QtScript/QScriptEngine>
#include <AnimationCache.h>
#include <AudioScriptingInterface.h>
#include <VoxelsScriptingInterface.h>
#include <AvatarData.h>
#include <AvatarHashMap.h>
#include <VoxelsScriptingInterface.h>
#include "AnimationCache.h"
#include "AbstractControllerScriptingInterface.h"
#include "Quat.h"
#include "ScriptUUID.h"

View file

@ -0,0 +1,233 @@
//
// XMLHttpRequestClass.cpp
// libraries/script-engine/src/
//
// Created by Ryan Huffman on 5/2/14.
// Copyright (c) 2014 High Fidelity, Inc. All rights reserved.
//
// This class is an implementation of the XMLHttpRequest object for scripting use. It provides a near-complete implementation
// of the class described in the Mozilla docs: https://developer.mozilla.org/en-US/docs/Web/API/XMLHttpRequest
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
#include <QEventLoop>
#include "XMLHttpRequestClass.h"
XMLHttpRequestClass::XMLHttpRequestClass(QScriptEngine* engine) :
_engine(engine),
_async(true),
_url(),
_method(""),
_responseType(""),
_manager(this),
_request(),
_reply(NULL),
_sendData(NULL),
_rawResponseData(),
_responseData(""),
_onTimeout(QScriptValue::NullValue),
_onReadyStateChange(QScriptValue::NullValue),
_readyState(XMLHttpRequestClass::UNSENT),
_errorCode(QNetworkReply::NoError),
_timeout(0),
_timer(this),
_numRedirects(0) {
_timer.setSingleShot(true);
}
XMLHttpRequestClass::~XMLHttpRequestClass() {
if (_reply) { delete _reply; }
if (_sendData) { delete _sendData; }
}
QScriptValue XMLHttpRequestClass::constructor(QScriptContext* context, QScriptEngine* engine) {
return engine->newQObject(new XMLHttpRequestClass(engine));
}
QScriptValue XMLHttpRequestClass::getStatus() const {
if (_reply) {
return QScriptValue(_reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt());
}
return QScriptValue(0);
}
QString XMLHttpRequestClass::getStatusText() const {
if (_reply) {
return _reply->attribute(QNetworkRequest::HttpReasonPhraseAttribute).toString();
}
return "";
}
void XMLHttpRequestClass::abort() {
abortRequest();
}
void XMLHttpRequestClass::setRequestHeader(const QString& name, const QString& value) {
_request.setRawHeader(QByteArray(name.toLatin1()), QByteArray(value.toLatin1()));
}
void XMLHttpRequestClass::requestMetaDataChanged() {
QVariant redirect = _reply->attribute(QNetworkRequest::RedirectionTargetAttribute);
// If this is a redirect, abort the current request and start a new one
if (redirect.isValid() && _numRedirects < MAXIMUM_REDIRECTS) {
_numRedirects++;
abortRequest();
QUrl newUrl = _url.resolved(redirect.toUrl().toString());
_request.setUrl(newUrl);
doSend();
}
}
void XMLHttpRequestClass::requestDownloadProgress(qint64 bytesReceived, qint64 bytesTotal) {
if (_readyState == OPENED && bytesReceived > 0) {
setReadyState(HEADERS_RECEIVED);
setReadyState(LOADING);
}
}
QScriptValue XMLHttpRequestClass::getAllResponseHeaders() const {
if (_reply) {
QList<QNetworkReply::RawHeaderPair> headerList = _reply->rawHeaderPairs();
QByteArray headers;
for (int i = 0; i < headerList.size(); i++) {
headers.append(headerList[i].first);
headers.append(": ");
headers.append(headerList[i].second);
headers.append("\n");
}
return QString(headers.data());
}
return QScriptValue("");
}
QScriptValue XMLHttpRequestClass::getResponseHeader(const QString& name) const {
if (_reply && _reply->hasRawHeader(name.toLatin1())) {
return QScriptValue(QString(_reply->rawHeader(name.toLatin1())));
}
return QScriptValue::NullValue;
}
void XMLHttpRequestClass::setReadyState(ReadyState readyState) {
if (readyState != _readyState) {
_readyState = readyState;
if (_onReadyStateChange.isFunction()) {
_onReadyStateChange.call(QScriptValue::NullValue);
}
}
}
void XMLHttpRequestClass::open(const QString& method, const QString& url, bool async, const QString& username,
const QString& password) {
if (_readyState == UNSENT) {
_async = async;
_url.setUrl(url);
if (!username.isEmpty()) {
_url.setUserName(username);
}
if (!password.isEmpty()) {
_url.setPassword(password);
}
_request.setUrl(_url);
_method = method;
setReadyState(OPENED);
}
}
void XMLHttpRequestClass::send() {
send(QString::Null());
}
void XMLHttpRequestClass::send(const QString& data) {
if (_readyState == OPENED && !_reply) {
if (!data.isNull()) {
_sendData = new QBuffer(this);
_sendData->setData(data.toUtf8());
}
doSend();
if (!_async) {
QEventLoop loop;
connect(this, SIGNAL(requestComplete()), &loop, SLOT(quit()));
loop.exec();
}
}
}
void XMLHttpRequestClass::doSend() {
_reply = _manager.sendCustomRequest(_request, _method.toLatin1(), _sendData);
connectToReply(_reply);
if (_timeout > 0) {
_timer.start(_timeout);
connect(&_timer, SIGNAL(timeout()), this, SLOT(requestTimeout()));
}
}
void XMLHttpRequestClass::requestTimeout() {
if (_onTimeout.isFunction()) {
_onTimeout.call(QScriptValue::NullValue);
}
abortRequest();
_errorCode = QNetworkReply::TimeoutError;
setReadyState(DONE);
emit requestComplete();
}
void XMLHttpRequestClass::requestError(QNetworkReply::NetworkError code) {
}
void XMLHttpRequestClass::requestFinished() {
disconnect(&_timer, SIGNAL(timeout()), this, SLOT(requestTimeout()));
_errorCode = _reply->error();
if (_errorCode == QNetworkReply::NoError) {
_rawResponseData.append(_reply->readAll());
if (_responseType == "json") {
_responseData = _engine->evaluate("(" + QString(_rawResponseData.data()) + ")");
if (_responseData.isError()) {
_engine->clearExceptions();
_responseData = QScriptValue::NullValue;
}
} else if (_responseType == "arraybuffer") {
_responseData = QScriptValue(_rawResponseData.data());
} else {
_responseData = QScriptValue(QString(_rawResponseData.data()));
}
}
setReadyState(DONE);
emit requestComplete();
}
void XMLHttpRequestClass::abortRequest() {
// Disconnect from signals we don't want to receive any longer.
disconnect(&_timer, SIGNAL(timeout()), this, SLOT(requestTimeout()));
if (_reply) {
disconnectFromReply(_reply);
_reply->abort();
delete _reply;
_reply = NULL;
}
}
void XMLHttpRequestClass::connectToReply(QNetworkReply* reply) {
connect(reply, SIGNAL(finished()), this, SLOT(requestFinished()));
connect(reply, SIGNAL(error(QNetworkReply::NetworkError)), this, SLOT(requestError(QNetworkReply::NetworkError)));
connect(reply, SIGNAL(downloadProgress(qint64, qint64)), this, SLOT(requestDownloadProgress(qint64, qint64)));
connect(reply, SIGNAL(metaDataChanged()), this, SLOT(requestMetaDataChanged()));
}
void XMLHttpRequestClass::disconnectFromReply(QNetworkReply* reply) {
disconnect(reply, SIGNAL(finished()), this, SLOT(requestFinished()));
disconnect(reply, SIGNAL(error(QNetworkReply::NetworkError)), this, SLOT(requestError(QNetworkReply::NetworkError)));
disconnect(reply, SIGNAL(downloadProgress(qint64, qint64)), this, SLOT(requestDownloadProgress(qint64, qint64)));
disconnect(reply, SIGNAL(metaDataChanged()), this, SLOT(requestMetaDataChanged()));
}

View file

@ -0,0 +1,129 @@
//
// XMLHttpRequestClass.h
// libraries/script-engine/src/
//
// Created by Ryan Huffman on 5/2/14.
// Copyright (c) 2014 High Fidelity, Inc. All rights reserved.
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
#ifndef hifi_XMLHttpRequestClass_h
#define hifi_XMLHttpRequestClass_h
#include <QBuffer>
#include <QNetworkAccessManager>
#include <QNetworkReply>
#include <QNetworkRequest>
#include <QObject>
#include <QScriptContext>
#include <QScriptEngine>
#include <QScriptValue>
#include <QTimer>
class XMLHttpRequestClass : public QObject {
Q_OBJECT
Q_PROPERTY(QScriptValue response READ getResponse)
Q_PROPERTY(QScriptValue responseText READ getResponseText)
Q_PROPERTY(QString responseType READ getResponseType WRITE setResponseType)
Q_PROPERTY(QScriptValue status READ getStatus)
Q_PROPERTY(QString statusText READ getStatusText)
Q_PROPERTY(QScriptValue readyState READ getReadyState)
Q_PROPERTY(QScriptValue errorCode READ getError)
Q_PROPERTY(int timeout READ getTimeout WRITE setTimeout)
Q_PROPERTY(int UNSENT READ getUnsent)
Q_PROPERTY(int OPENED READ getOpened)
Q_PROPERTY(int HEADERS_RECEIVED READ getHeadersReceived)
Q_PROPERTY(int LOADING READ getLoading)
Q_PROPERTY(int DONE READ getDone)
// Callbacks
Q_PROPERTY(QScriptValue ontimeout READ getOnTimeout WRITE setOnTimeout)
Q_PROPERTY(QScriptValue onreadystatechange READ getOnReadyStateChange WRITE setOnReadyStateChange)
public:
XMLHttpRequestClass(QScriptEngine* engine);
~XMLHttpRequestClass();
static const int MAXIMUM_REDIRECTS = 5;
enum ReadyState {
UNSENT = 0,
OPENED,
HEADERS_RECEIVED,
LOADING,
DONE
};
int getUnsent() const { return UNSENT; };
int getOpened() const { return OPENED; };
int getHeadersReceived() const { return HEADERS_RECEIVED; };
int getLoading() const { return LOADING; };
int getDone() const { return DONE; };
static QScriptValue constructor(QScriptContext* context, QScriptEngine* engine);
int getTimeout() const { return _timeout; }
void setTimeout(int timeout) { _timeout = timeout; }
QScriptValue getResponse() const { return _responseData; }
QScriptValue getResponseText() const { return QScriptValue(QString(_rawResponseData.data())); }
QString getResponseType() const { return _responseType; }
void setResponseType(const QString& responseType) { _responseType = responseType; }
QScriptValue getReadyState() const { return QScriptValue(_readyState); }
QScriptValue getError() const { return QScriptValue(_errorCode); }
QScriptValue getStatus() const;
QString getStatusText() const;
QScriptValue getOnTimeout() const { return _onTimeout; }
void setOnTimeout(QScriptValue function) { _onTimeout = function; }
QScriptValue getOnReadyStateChange() const { return _onReadyStateChange; }
void setOnReadyStateChange(QScriptValue function) { _onReadyStateChange = function; }
public slots:
void abort();
void setRequestHeader(const QString& name, const QString& value);
void open(const QString& method, const QString& url, bool async = true, const QString& username = "",
const QString& password = "");
void send();
void send(const QString& data);
QScriptValue getAllResponseHeaders() const;
QScriptValue getResponseHeader(const QString& name) const;
signals:
void requestComplete();
private:
void setReadyState(ReadyState readyState);
void doSend();
void connectToReply(QNetworkReply* reply);
void disconnectFromReply(QNetworkReply* reply);
void abortRequest();
QScriptEngine* _engine;
bool _async;
QUrl _url;
QString _method;
QString _responseType;
QNetworkAccessManager _manager;
QNetworkRequest _request;
QNetworkReply* _reply;
QBuffer* _sendData;
QByteArray _rawResponseData;
QScriptValue _responseData;
QScriptValue _onTimeout;
QScriptValue _onReadyStateChange;
ReadyState _readyState;
QNetworkReply::NetworkError _errorCode;
int _timeout;
QTimer _timer;
int _numRedirects;
private slots:
void requestFinished();
void requestError(QNetworkReply::NetworkError code);
void requestMetaDataChanged();
void requestDownloadProgress(qint64 bytesReceived, qint64 bytesTotal);
void requestTimeout();
};
#endif // hifi_XMLHttpRequestClass_h