mirror of
https://github.com/overte-org/overte.git
synced 2025-04-23 22:33:31 +02:00
Merge branch '19630' of github.com:huffman/hifi into 19630
Conflicts: interface/src/Menu.h
This commit is contained in:
commit
21819ce3b4
98 changed files with 3734 additions and 942 deletions
4
.gitignore
vendored
4
.gitignore
vendored
|
@ -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/
|
||||
|
|
|
@ -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}")
|
||||
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -49,7 +49,7 @@ else (LIBOVR_LIBRARIES AND LIBOVR_INCLUDE_DIRS)
|
|||
set(WINDOWS_LIBOVR_NAME "libovr.lib")
|
||||
endif()
|
||||
|
||||
find_library(LIBOVR_LIBRARIES "Lib/Win32/${LIBOVR_NAME}" HINTS ${LIBOVR_SEARCH_DIRS})
|
||||
find_library(LIBOVR_LIBRARIES "Lib/Win32/VS2010/${LIBOVR_NAME}" HINTS ${LIBOVR_SEARCH_DIRS})
|
||||
endif ()
|
||||
|
||||
if (LIBOVR_INCLUDE_DIRS AND LIBOVR_LIBRARIES)
|
||||
|
|
42
cmake/modules/FindPrioVR.cmake
Normal file
42
cmake/modules/FindPrioVR.cmake
Normal 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
68
examples/Test.js
Normal 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);
|
||||
}
|
||||
};
|
|
@ -25,24 +25,36 @@ function vMinus(a, b) {
|
|||
return rval;
|
||||
}
|
||||
|
||||
// First, load two percussion sounds to be used on the sticks
|
||||
// The model file to be used for the guitar
|
||||
var guitarModel = "https://s3-us-west-1.amazonaws.com/highfidelity-public/models/attachments/guitar.fst";
|
||||
|
||||
var guitarType = 2;
|
||||
// Load sounds that will be played
|
||||
|
||||
if (guitarType == 1) {
|
||||
var chord1 = new Sound("https://s3-us-west-1.amazonaws.com/highfidelity-public/sounds/Guitars/Guitar+-+Nylon+A.raw");
|
||||
var chord2 = new Sound("https://s3-us-west-1.amazonaws.com/highfidelity-public/sounds/Guitars/Guitar+-+Nylon+B.raw");
|
||||
var chord3 = new Sound("https://s3-us-west-1.amazonaws.com/highfidelity-public/sounds/Guitars/Guitar+-+Nylon+E.raw");
|
||||
} else {
|
||||
var chord1 = new Sound("https://s3-us-west-1.amazonaws.com/highfidelity-public/sounds/Guitars/Guitar+-+Metal+A+short.raw");
|
||||
var chord2 = new Sound("https://s3-us-west-1.amazonaws.com/highfidelity-public/sounds/Guitars/Guitar+-+Metal+B+short.raw");
|
||||
var chord3 = new Sound("https://s3-us-west-1.amazonaws.com/highfidelity-public/sounds/Guitars/Guitar+-+Metal+E+short.raw");
|
||||
}
|
||||
var chords = new Array();
|
||||
// Nylon string guitar
|
||||
chords[1] = new Sound("https://s3-us-west-1.amazonaws.com/highfidelity-public/sounds/Guitars/Guitar+-+Nylon+A.raw");
|
||||
chords[2] = new Sound("https://s3-us-west-1.amazonaws.com/highfidelity-public/sounds/Guitars/Guitar+-+Nylon+B.raw");
|
||||
chords[3] = new Sound("https://s3-us-west-1.amazonaws.com/highfidelity-public/sounds/Guitars/Guitar+-+Nylon+E.raw");
|
||||
chords[4] = new Sound("https://s3-us-west-1.amazonaws.com/highfidelity-public/sounds/Guitars/Guitar+-+Nylon+G.raw");
|
||||
|
||||
// Electric guitar
|
||||
chords[5] = new Sound("https://s3-us-west-1.amazonaws.com/highfidelity-public/sounds/Guitars/Guitar+-+Metal+A+short.raw");
|
||||
chords[6] = new Sound("https://s3-us-west-1.amazonaws.com/highfidelity-public/sounds/Guitars/Guitar+-+Metal+B+short.raw");
|
||||
chords[7] = new Sound("https://s3-us-west-1.amazonaws.com/highfidelity-public/sounds/Guitars/Guitar+-+Metal+E+short.raw");
|
||||
chords[8] = new Sound("https://s3-us-west-1.amazonaws.com/highfidelity-public/sounds/Guitars/Guitar+-+Metal+G+short.raw");
|
||||
|
||||
var whichChord = chord1;
|
||||
// Steel Guitar
|
||||
chords[9] = new Sound("https://s3-us-west-1.amazonaws.com/highfidelity-public/sounds/Guitars/Guitar+-+Steel+A.raw");
|
||||
chords[10] = new Sound("https://s3-us-west-1.amazonaws.com/highfidelity-public/sounds/Guitars/Guitar+-+Steel+B.raw");
|
||||
chords[11] = new Sound("https://s3-us-west-1.amazonaws.com/highfidelity-public/sounds/Guitars/Guitar+-+Steel+E.raw");
|
||||
chords[12] = new Sound("https://s3-us-west-1.amazonaws.com/highfidelity-public/sounds/Guitars/Guitar+-+Steel+G.raw");
|
||||
|
||||
var leftHanded = false;
|
||||
var NUM_CHORDS = 4;
|
||||
var NUM_GUITARS = 3;
|
||||
var guitarSelector = NUM_CHORDS;
|
||||
var whichChord = 1;
|
||||
|
||||
var leftHanded = true;
|
||||
if (leftHanded) {
|
||||
var strumHand = 0;
|
||||
var chordHand = 1;
|
||||
|
@ -55,42 +67,103 @@ var lastPosition = { x: 0.0,
|
|||
y: 0.0,
|
||||
z: 0.0 };
|
||||
|
||||
var soundPlaying = false;
|
||||
var selectorPressed = false;
|
||||
var position;
|
||||
|
||||
MyAvatar.attach(guitarModel, "Hips", {x: -0.0, y: -0.0, z: 0.0}, Quat.fromPitchYawRollDegrees(0, 0, 0), 1.0);
|
||||
|
||||
function checkHands(deltaTime) {
|
||||
for (var palm = 0; palm < 2; palm++) {
|
||||
var palmVelocity = Controller.getSpatialControlVelocity(palm * 2 + 1);
|
||||
var speed = length(palmVelocity);
|
||||
var volume = length(palmVelocity) / 5.0;
|
||||
var position = Controller.getSpatialControlPosition(palm * 2 + 1);
|
||||
var myPelvis = MyAvatar.position;
|
||||
var trigger = Controller.getTriggerValue(strumHand);
|
||||
var chord = Controller.getTriggerValue(chordHand);
|
||||
|
||||
if (volume > 1.0) volume = 1.0;
|
||||
if ((chord > 0.1) && Audio.isInjectorPlaying(soundPlaying)) {
|
||||
// If chord finger trigger pulled, stop current chord
|
||||
print("stopped sound");
|
||||
Audio.stopInjector(soundPlaying);
|
||||
}
|
||||
|
||||
var BUTTON_COUNT = 6;
|
||||
|
||||
// Change guitars if button FWD (5) pressed
|
||||
if (Controller.isButtonPressed(chordHand * BUTTON_COUNT + 5)) {
|
||||
if (!selectorPressed) {
|
||||
guitarSelector += NUM_CHORDS;
|
||||
if (guitarSelector >= NUM_CHORDS * NUM_GUITARS) {
|
||||
guitarSelector = 0;
|
||||
}
|
||||
selectorPressed = true;
|
||||
}
|
||||
} else {
|
||||
selectorPressed = false;
|
||||
}
|
||||
|
||||
if (Controller.isButtonPressed(chordHand * BUTTON_COUNT + 1)) {
|
||||
whichChord = 1;
|
||||
} else if (Controller.isButtonPressed(chordHand * BUTTON_COUNT + 2)) {
|
||||
whichChord = 2;
|
||||
} else if (Controller.isButtonPressed(chordHand * BUTTON_COUNT + 3)) {
|
||||
whichChord = 3;
|
||||
} else if (Controller.isButtonPressed(chordHand * BUTTON_COUNT + 4)) {
|
||||
whichChord = 4;
|
||||
}
|
||||
|
||||
if (palm == strumHand) {
|
||||
|
||||
var STRUM_HEIGHT_ABOVE_PELVIS = -0.30;
|
||||
var STRUM_HEIGHT_ABOVE_PELVIS = 0.10;
|
||||
var strumTriggerHeight = myPelvis.y + STRUM_HEIGHT_ABOVE_PELVIS;
|
||||
//printVector(position);
|
||||
if ((position.y < strumTriggerHeight) && (lastPosition.y >= strumTriggerHeight)) {
|
||||
// If hand passes downward through guitar strings, play a chord!
|
||||
var options = new AudioInjectionOptions();
|
||||
options.position = position;
|
||||
if (speed > 1.0) { speed = 1.0; }
|
||||
options.volume = speed;
|
||||
Audio.playSound(whichChord, options);
|
||||
if ( ( ((position.y < strumTriggerHeight) && (lastPosition.y >= strumTriggerHeight)) ||
|
||||
((position.y > strumTriggerHeight) && (lastPosition.y <= strumTriggerHeight)) ) && (trigger > 0.1) ){
|
||||
// If hand passes downward or upward through 'strings', and finger trigger pulled, play
|
||||
playChord(position, volume);
|
||||
}
|
||||
lastPosition = Controller.getSpatialControlPosition(palm * 2 + 1);
|
||||
} else {
|
||||
// This is the chord controller
|
||||
var distanceFromPelvis = Vec3.length(Vec3.subtract(position, myPelvis));
|
||||
//print(distanceFromPelvis);
|
||||
if (distanceFromPelvis > 0.63) {
|
||||
whichChord = chord3;
|
||||
} else if (distanceFromPelvis > 0.55) {
|
||||
whichChord = chord2;
|
||||
} else {
|
||||
whichChord = chord1;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function playChord(position, volume) {
|
||||
var options = new AudioInjectionOptions();
|
||||
options.position = position;
|
||||
options.volume = volume;
|
||||
if (Audio.isInjectorPlaying(soundPlaying)) {
|
||||
print("stopped sound");
|
||||
Audio.stopInjector(soundPlaying);
|
||||
}
|
||||
print("Played sound: " + whichChord + " at volume " + options.volume);
|
||||
soundPlaying = Audio.playSound(chords[guitarSelector + whichChord], options);
|
||||
}
|
||||
|
||||
function keyPressEvent(event) {
|
||||
// check for keypresses and use those to trigger sounds if not hydra
|
||||
keyVolume = 0.4;
|
||||
if (event.text == "1") {
|
||||
whichChord = 1;
|
||||
playChord(MyAvatar.position, keyVolume);
|
||||
} else if (event.text == "2") {
|
||||
whichChord = 2;
|
||||
playChord(MyAvatar.position, keyVolume);
|
||||
} else if (event.text == "3") {
|
||||
whichChord = 3;
|
||||
playChord(MyAvatar.position, keyVolume);
|
||||
} else if (event.text == "4") {
|
||||
whichChord = 4;
|
||||
playChord(MyAvatar.position, keyVolume);
|
||||
}
|
||||
}
|
||||
|
||||
function scriptEnding() {
|
||||
MyAvatar.detachOne(guitarModel);
|
||||
}
|
||||
// Connect a call back that happens every frame
|
||||
Script.update.connect(checkHands);
|
||||
Script.update.connect(checkHands);
|
||||
Script.scriptEnding.connect(scriptEnding);
|
||||
Controller.keyPressEvent.connect(keyPressEvent);
|
||||
|
||||
|
|
124
examples/animatedModelExample.js
Normal file
124
examples/animatedModelExample.js
Normal 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);
|
||||
});
|
||||
|
|
@ -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);
|
||||
|
||||
|
||||
|
||||
|
|
|
@ -28,7 +28,7 @@ var BULLET_VELOCITY = 5.0;
|
|||
var MIN_THROWER_DELAY = 1000;
|
||||
var MAX_THROWER_DELAY = 1000;
|
||||
var LEFT_BUTTON_3 = 3;
|
||||
var RELOAD_INTERVAL = 9;
|
||||
var RELOAD_INTERVAL = 5;
|
||||
|
||||
var showScore = false;
|
||||
|
||||
|
@ -39,6 +39,8 @@ var impactSound = new Sound("https://s3-us-west-1.amazonaws.com/highfidelity-pub
|
|||
var targetHitSound = new Sound("http://highfidelity-public.s3-us-west-1.amazonaws.com/sounds/Space%20Invaders/hit.raw");
|
||||
var targetLaunchSound = new Sound("http://highfidelity-public.s3-us-west-1.amazonaws.com/sounds/Space%20Invaders/shoot.raw");
|
||||
|
||||
var gunModel = "http://highfidelity-public.s3-us-west-1.amazonaws.com/models/attachments/Raygun2.fst";
|
||||
|
||||
var audioOptions = new AudioInjectionOptions();
|
||||
audioOptions.volume = 0.9;
|
||||
|
||||
|
@ -90,12 +92,12 @@ function printVector(string, vector) {
|
|||
}
|
||||
|
||||
function shootBullet(position, velocity) {
|
||||
var BULLET_SIZE = 0.02;
|
||||
var BULLET_SIZE = 0.01;
|
||||
var BULLET_GRAVITY = -0.02;
|
||||
Particles.addParticle(
|
||||
{ position: position,
|
||||
radius: BULLET_SIZE,
|
||||
color: { red: 200, green: 0, blue: 0 },
|
||||
color: { red: 10, green: 10, blue: 10 },
|
||||
velocity: velocity,
|
||||
gravity: { x: 0, y: BULLET_GRAVITY, z: 0 },
|
||||
damping: 0 });
|
||||
|
@ -185,11 +187,24 @@ function keyPressEvent(event) {
|
|||
if (event.text == "t") {
|
||||
var time = MIN_THROWER_DELAY + Math.random() * MAX_THROWER_DELAY;
|
||||
Script.setTimeout(shootTarget, time);
|
||||
} if (event.text == ".") {
|
||||
} else if (event.text == ".") {
|
||||
shootFromMouse();
|
||||
} else if (event.text == "r") {
|
||||
playLoadSound();
|
||||
}
|
||||
}
|
||||
|
||||
function playLoadSound() {
|
||||
audioOptions.position = Vec3.sum(Camera.getPosition(), Quat.getFront(Camera.getOrientation()));
|
||||
Audio.playSound(loadSound, audioOptions);
|
||||
}
|
||||
|
||||
MyAvatar.attach(gunModel, "RightHand", {x: -0.02, y: -.14, z: 0.07}, Quat.fromPitchYawRollDegrees(-70, -151, 72), 0.20);
|
||||
MyAvatar.attach(gunModel, "LeftHand", {x: -0.02, y: -.14, z: 0.07}, Quat.fromPitchYawRollDegrees(-70, -151, 72), 0.20);
|
||||
|
||||
// Give a bit of time to load before playing sound
|
||||
Script.setTimeout(playLoadSound, 2000);
|
||||
|
||||
function update(deltaTime) {
|
||||
// Check for mouseLook movement, update rotation
|
||||
// rotate body yaw for yaw received from mouse
|
||||
|
@ -303,7 +318,9 @@ function mouseMoveEvent(event) {
|
|||
|
||||
function scriptEnding() {
|
||||
Overlays.deleteOverlay(reticle);
|
||||
Overlays.deleteOverlay(text);
|
||||
Overlays.deleteOverlay(text);
|
||||
MyAvatar.detachOne(gunModel);
|
||||
MyAvatar.detachOne(gunModel);
|
||||
}
|
||||
|
||||
Particles.particleCollisionWithVoxel.connect(particleCollisionWithVoxel);
|
||||
|
|
|
@ -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;
|
||||
|
|
51
examples/streetAreaExample.js
Normal file
51
examples/streetAreaExample.js
Normal 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);
|
||||
}
|
147
examples/testXMLHttpRequest.js
Normal file
147
examples/testXMLHttpRequest.js
Normal 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
197
examples/toolBars.js
Normal 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;
|
|
@ -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
16
interface/external/priovr/readme.txt
vendored
Normal 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.
|
||||
|
|
@ -152,6 +152,8 @@ Application::Application(int& argc, char** argv, QElapsedTimer &startup_time) :
|
|||
_mirrorViewRect(QRect(MIRROR_VIEW_LEFT_PADDING, MIRROR_VIEW_TOP_PADDING, MIRROR_VIEW_WIDTH, MIRROR_VIEW_HEIGHT)),
|
||||
_cameraPushback(0.0f),
|
||||
_scaleMirror(1.0f),
|
||||
_rotateMirror(0.0f),
|
||||
_raiseMirror(0.0f),
|
||||
_mouseX(0),
|
||||
_mouseY(0),
|
||||
_lastMouseMove(usecTimestampNow()),
|
||||
|
@ -685,11 +687,8 @@ void Application::resizeGL(int width, int height) {
|
|||
glLoadIdentity();
|
||||
|
||||
// update Stats width
|
||||
int horizontalOffset = 0;
|
||||
if (Menu::getInstance()->isOptionChecked(MenuOption::Mirror)) {
|
||||
// mirror is enabled, let's set horizontal offset to give stats some margin
|
||||
horizontalOffset += MIRROR_VIEW_WIDTH + MIRROR_VIEW_LEFT_PADDING * 2;
|
||||
}
|
||||
// let's set horizontal offset to give stats some margin to mirror
|
||||
int horizontalOffset = MIRROR_VIEW_WIDTH + MIRROR_VIEW_LEFT_PADDING * 2;
|
||||
Stats::getInstance()->resetWidth(width, horizontalOffset);
|
||||
}
|
||||
|
||||
|
@ -1163,10 +1162,8 @@ void Application::mouseReleaseEvent(QMouseEvent* event) {
|
|||
_mousePressed = false;
|
||||
checkBandwidthMeterClick();
|
||||
if (Menu::getInstance()->isOptionChecked(MenuOption::Stats)) {
|
||||
int horizontalOffset = 0;
|
||||
if (Menu::getInstance()->isOptionChecked(MenuOption::Mirror)) {
|
||||
horizontalOffset = MIRROR_VIEW_WIDTH;
|
||||
}
|
||||
// let's set horizontal offset to give stats some margin to mirror
|
||||
int horizontalOffset = MIRROR_VIEW_WIDTH;
|
||||
Stats::getInstance()->checkClick(_mouseX, _mouseY, _mouseDragStartedX, _mouseDragStartedY, horizontalOffset);
|
||||
}
|
||||
}
|
||||
|
@ -1218,10 +1215,6 @@ void Application::touchBeginEvent(QTouchEvent* event) {
|
|||
return;
|
||||
}
|
||||
|
||||
// put any application specific touch behavior below here..
|
||||
_lastTouchAvgX = _touchAvgX;
|
||||
_lastTouchAvgY = _touchAvgY;
|
||||
|
||||
}
|
||||
|
||||
void Application::touchEndEvent(QTouchEvent* event) {
|
||||
|
@ -1876,34 +1869,6 @@ void Application::updateMyAvatarLookAtPosition() {
|
|||
_myAvatar->getHead()->setLookAtPosition(lookAtSpot);
|
||||
}
|
||||
|
||||
void Application::updateHandAndTouch(float deltaTime) {
|
||||
bool showWarnings = Menu::getInstance()->isOptionChecked(MenuOption::PipelineWarnings);
|
||||
PerformanceWarning warn(showWarnings, "Application::updateHandAndTouch()");
|
||||
|
||||
// Update from Touch
|
||||
if (_isTouchPressed) {
|
||||
_lastTouchAvgX = _touchAvgX;
|
||||
_lastTouchAvgY = _touchAvgY;
|
||||
}
|
||||
}
|
||||
|
||||
void Application::updateLeap(float deltaTime) {
|
||||
bool showWarnings = Menu::getInstance()->isOptionChecked(MenuOption::PipelineWarnings);
|
||||
PerformanceWarning warn(showWarnings, "Application::updateLeap()");
|
||||
}
|
||||
|
||||
void Application::updateSixense(float deltaTime) {
|
||||
bool showWarnings = Menu::getInstance()->isOptionChecked(MenuOption::PipelineWarnings);
|
||||
PerformanceWarning warn(showWarnings, "Application::updateSixense()");
|
||||
|
||||
_sixenseManager.update(deltaTime);
|
||||
}
|
||||
|
||||
void Application::updateSerialDevices(float deltaTime) {
|
||||
bool showWarnings = Menu::getInstance()->isOptionChecked(MenuOption::PipelineWarnings);
|
||||
PerformanceWarning warn(showWarnings, "Application::updateSerialDevices()");
|
||||
}
|
||||
|
||||
void Application::updateThreads(float deltaTime) {
|
||||
bool showWarnings = Menu::getInstance()->isOptionChecked(MenuOption::PipelineWarnings);
|
||||
PerformanceWarning warn(showWarnings, "Application::updateThreads()");
|
||||
|
@ -2017,11 +1982,8 @@ void Application::update(float deltaTime) {
|
|||
updateVisage();
|
||||
_myAvatar->updateLookAtTargetAvatar();
|
||||
updateMyAvatarLookAtPosition();
|
||||
|
||||
updateHandAndTouch(deltaTime); // Update state for touch sensors
|
||||
updateLeap(deltaTime); // Leap finger-sensing device
|
||||
updateSixense(deltaTime); // Razer Hydra controllers
|
||||
updateSerialDevices(deltaTime); // Read serial port interface devices
|
||||
_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...
|
||||
|
@ -2770,11 +2732,8 @@ void Application::displayOverlay() {
|
|||
glPointSize(1.0f);
|
||||
|
||||
if (Menu::getInstance()->isOptionChecked(MenuOption::Stats)) {
|
||||
int horizontalOffset = 0;
|
||||
if (Menu::getInstance()->isOptionChecked(MenuOption::Mirror)) {
|
||||
// mirror is enabled, let's set horizontal offset to give stats some margin
|
||||
horizontalOffset += MIRROR_VIEW_WIDTH + MIRROR_VIEW_LEFT_PADDING * 2;
|
||||
}
|
||||
// let's set horizontal offset to give stats some margin to mirror
|
||||
int horizontalOffset = MIRROR_VIEW_WIDTH + MIRROR_VIEW_LEFT_PADDING * 2;
|
||||
int voxelPacketsToProcess = _voxelProcessor.packetsToProcessCount();
|
||||
// Onscreen text about position, servers, etc
|
||||
Stats::getInstance()->display(WHITE_TEXT, horizontalOffset, _fps, _packetsPerSecond, _bytesPerSecond, voxelPacketsToProcess);
|
||||
|
@ -2798,6 +2757,9 @@ void Application::displayOverlay() {
|
|||
}
|
||||
_nodeBoundsDisplay.drawOverlay();
|
||||
|
||||
// give external parties a change to hook in
|
||||
emit renderingOverlay();
|
||||
|
||||
_overlays.render2D();
|
||||
|
||||
glPopMatrix();
|
||||
|
@ -3106,6 +3068,8 @@ void Application::resetSensors() {
|
|||
OculusManager::reset();
|
||||
}
|
||||
|
||||
_prioVR.reset();
|
||||
|
||||
QCursor::setPos(_mouseX, _mouseY);
|
||||
_myAvatar->reset();
|
||||
|
||||
|
@ -3415,71 +3379,6 @@ void Application::saveScripts() {
|
|||
_settings->endArray();
|
||||
}
|
||||
|
||||
void Application::stopAllScripts() {
|
||||
// stops all current running scripts
|
||||
for (int i = 0; i < _scriptEnginesHash.size(); ++i) {
|
||||
_scriptEnginesHash.values().at(i)->stop();
|
||||
qDebug() << "stopping script..." << getRunningScripts().at(i);
|
||||
}
|
||||
_scriptEnginesHash.clear();
|
||||
_runningScriptsWidget->setRunningScripts(getRunningScripts());
|
||||
bumpSettings();
|
||||
}
|
||||
|
||||
void Application::stopScript(const QString &scriptName) {
|
||||
if (_scriptEnginesHash.contains(scriptName)) {
|
||||
_scriptEnginesHash.value(scriptName)->stop();
|
||||
qDebug() << "stopping script..." << scriptName;
|
||||
_scriptEnginesHash.remove(scriptName);
|
||||
_runningScriptsWidget->setRunningScripts(getRunningScripts());
|
||||
bumpSettings();
|
||||
}
|
||||
}
|
||||
|
||||
void Application::reloadAllScripts() {
|
||||
// remember all the current scripts so we can reload them
|
||||
QStringList reloadList = getRunningScripts();
|
||||
// reloads all current running scripts
|
||||
stopAllScripts();
|
||||
|
||||
foreach (QString scriptName, reloadList){
|
||||
qDebug() << "reloading script..." << scriptName;
|
||||
loadScript(scriptName);
|
||||
}
|
||||
}
|
||||
|
||||
void Application::manageRunningScriptsWidgetVisibility(bool shown) {
|
||||
if (_runningScriptsWidgetWasVisible && shown) {
|
||||
_runningScriptsWidget->show();
|
||||
} else if (_runningScriptsWidgetWasVisible && !shown) {
|
||||
_runningScriptsWidget->hide();
|
||||
}
|
||||
}
|
||||
|
||||
void Application::toggleRunningScriptsWidget() {
|
||||
if (_runningScriptsWidgetWasVisible) {
|
||||
_runningScriptsWidget->hide();
|
||||
_runningScriptsWidgetWasVisible = false;
|
||||
} else {
|
||||
_runningScriptsWidget->setBoundary(QRect(_window->geometry().topLeft(),
|
||||
_window->size()));
|
||||
_runningScriptsWidget->show();
|
||||
_runningScriptsWidgetWasVisible = true;
|
||||
}
|
||||
}
|
||||
|
||||
void Application::uploadHead() {
|
||||
uploadModel(HEAD_MODEL);
|
||||
}
|
||||
|
||||
void Application::uploadSkeleton() {
|
||||
uploadModel(SKELETON_MODEL);
|
||||
}
|
||||
|
||||
void Application::uploadAttachment() {
|
||||
uploadModel(ATTACHMENT_MODEL);
|
||||
}
|
||||
|
||||
ScriptEngine* Application::loadScript(const QString& scriptName, bool loadScriptFromEditor) {
|
||||
if(loadScriptFromEditor && _scriptEnginesHash.contains(scriptName) && !_scriptEnginesHash[scriptName]->isFinished()){
|
||||
return _scriptEnginesHash[scriptName];
|
||||
|
@ -3517,6 +3416,8 @@ ScriptEngine* Application::loadScript(const QString& scriptName, bool loadScript
|
|||
scriptEngine->registerGlobalObject("Clipboard", clipboardScriptable);
|
||||
connect(scriptEngine, SIGNAL(finished(const QString&)), clipboardScriptable, SLOT(deleteLater()));
|
||||
|
||||
connect(scriptEngine, SIGNAL(finished(const QString&)), this, SLOT(scriptFinished(const QString&)));
|
||||
|
||||
scriptEngine->registerGlobalObject("Overlays", &_overlays);
|
||||
|
||||
QScriptValue windowValue = scriptEngine->registerGlobalObject("Window", WindowScriptingInterface::getInstance());
|
||||
|
@ -3559,6 +3460,69 @@ ScriptEngine* Application::loadScript(const QString& scriptName, bool loadScript
|
|||
return scriptEngine;
|
||||
}
|
||||
|
||||
void Application::scriptFinished(const QString& scriptName) {
|
||||
if (_scriptEnginesHash.remove(scriptName)) {
|
||||
_runningScriptsWidget->scriptStopped(scriptName);
|
||||
_runningScriptsWidget->setRunningScripts(getRunningScripts());
|
||||
bumpSettings();
|
||||
}
|
||||
}
|
||||
|
||||
void Application::stopAllScripts(bool restart) {
|
||||
// stops all current running scripts
|
||||
for (QHash<QString, ScriptEngine*>::const_iterator it = _scriptEnginesHash.constBegin();
|
||||
it != _scriptEnginesHash.constEnd(); it++) {
|
||||
if (restart) {
|
||||
connect(it.value(), SIGNAL(finished(const QString&)), SLOT(loadScript(const QString&)));
|
||||
}
|
||||
it.value()->stop();
|
||||
qDebug() << "stopping script..." << it.key();
|
||||
}
|
||||
}
|
||||
|
||||
void Application::stopScript(const QString &scriptName) {
|
||||
if (_scriptEnginesHash.contains(scriptName)) {
|
||||
_scriptEnginesHash.value(scriptName)->stop();
|
||||
qDebug() << "stopping script..." << scriptName;
|
||||
}
|
||||
}
|
||||
|
||||
void Application::reloadAllScripts() {
|
||||
stopAllScripts(true);
|
||||
}
|
||||
|
||||
void Application::manageRunningScriptsWidgetVisibility(bool shown) {
|
||||
if (_runningScriptsWidgetWasVisible && shown) {
|
||||
_runningScriptsWidget->show();
|
||||
} else if (_runningScriptsWidgetWasVisible && !shown) {
|
||||
_runningScriptsWidget->hide();
|
||||
}
|
||||
}
|
||||
|
||||
void Application::toggleRunningScriptsWidget() {
|
||||
if (_runningScriptsWidgetWasVisible) {
|
||||
_runningScriptsWidget->hide();
|
||||
_runningScriptsWidgetWasVisible = false;
|
||||
} else {
|
||||
_runningScriptsWidget->setBoundary(QRect(_window->geometry().topLeft(),
|
||||
_window->size()));
|
||||
_runningScriptsWidget->show();
|
||||
_runningScriptsWidgetWasVisible = true;
|
||||
}
|
||||
}
|
||||
|
||||
void Application::uploadHead() {
|
||||
uploadModel(HEAD_MODEL);
|
||||
}
|
||||
|
||||
void Application::uploadSkeleton() {
|
||||
uploadModel(SKELETON_MODEL);
|
||||
}
|
||||
|
||||
void Application::uploadAttachment() {
|
||||
uploadModel(ATTACHMENT_MODEL);
|
||||
}
|
||||
|
||||
QString Application::getPreviousScriptLocation() {
|
||||
QString suggestedName;
|
||||
if (_previousScriptLocation.isEmpty()) {
|
||||
|
@ -3698,7 +3662,17 @@ void Application::takeSnapshot() {
|
|||
player->setMedia(QUrl::fromLocalFile(inf.absoluteFilePath()));
|
||||
player->play();
|
||||
|
||||
Snapshot::saveSnapshot(_glWidget, _myAvatar);
|
||||
QString fileName = Snapshot::saveSnapshot(_glWidget, _myAvatar);
|
||||
|
||||
AccountManager& accountManager = AccountManager::getInstance();
|
||||
if (!accountManager.isLoggedIn()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!_snapshotShareDialog) {
|
||||
_snapshotShareDialog = new SnapshotShareDialog(fileName, _glWidget);
|
||||
}
|
||||
_snapshotShareDialog->show();
|
||||
}
|
||||
|
||||
void Application::urlGoTo(int argc, const char * constArgv[]) {
|
||||
|
|
|
@ -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"
|
||||
|
@ -75,6 +76,7 @@
|
|||
#include "ui/NodeBounds.h"
|
||||
#include "ui/OctreeStatsDialog.h"
|
||||
#include "ui/RearMirrorTools.h"
|
||||
#include "ui/SnapshotShareDialog.h"
|
||||
#include "ui/LodToolsDialog.h"
|
||||
#include "ui/LogDialog.h"
|
||||
#include "ui/UpdateDialog.h"
|
||||
|
@ -127,7 +129,6 @@ public:
|
|||
~Application();
|
||||
|
||||
void restoreSizeAndPosition();
|
||||
ScriptEngine* loadScript(const QString& fileNameString, bool loadScriptFromEditor = false);
|
||||
void loadScripts();
|
||||
QString getPreviousScriptLocation();
|
||||
void setPreviousScriptLocation(const QString& previousScriptLocation);
|
||||
|
@ -197,6 +198,7 @@ public:
|
|||
Visage* getVisage() { return &_visage; }
|
||||
FaceTracker* getActiveFaceTracker();
|
||||
SixenseManager* getSixenseManager() { return &_sixenseManager; }
|
||||
PrioVR* getPrioVR() { return &_prioVR; }
|
||||
BandwidthMeter* getBandwidthMeter() { return &_bandwidthMeter; }
|
||||
QUndoStack* getUndoStack() { return &_undoStack; }
|
||||
|
||||
|
@ -270,6 +272,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();
|
||||
|
||||
|
@ -294,7 +299,9 @@ public slots:
|
|||
void loadScriptURLDialog();
|
||||
void toggleLogDialog();
|
||||
void initAvatarAndViewFrustum();
|
||||
void stopAllScripts();
|
||||
ScriptEngine* loadScript(const QString& fileNameString, bool loadScriptFromEditor = false);
|
||||
void scriptFinished(const QString& scriptName);
|
||||
void stopAllScripts(bool restart = false);
|
||||
void stopScript(const QString& scriptName);
|
||||
void reloadAllScripts();
|
||||
void toggleRunningScriptsWidget();
|
||||
|
@ -346,10 +353,6 @@ private:
|
|||
void updateFaceshift();
|
||||
void updateVisage();
|
||||
void updateMyAvatarLookAtPosition();
|
||||
void updateHandAndTouch(float deltaTime);
|
||||
void updateLeap(float deltaTime);
|
||||
void updateSixense(float deltaTime);
|
||||
void updateSerialDevices(float deltaTime);
|
||||
void updateThreads(float deltaTime);
|
||||
void updateMetavoxels(float deltaTime);
|
||||
void updateCamera(float deltaTime);
|
||||
|
@ -447,6 +450,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
|
||||
|
@ -480,8 +484,6 @@ private:
|
|||
|
||||
float _touchAvgX;
|
||||
float _touchAvgY;
|
||||
float _lastTouchAvgX;
|
||||
float _lastTouchAvgY;
|
||||
float _touchDragStartedAvgX;
|
||||
float _touchDragStartedAvgY;
|
||||
bool _isTouchPressed; // true if multitouch has been pressed (clear when finished)
|
||||
|
@ -528,6 +530,7 @@ private:
|
|||
std::vector<VoxelFade> _voxelFades;
|
||||
ControllerScriptingInterface _controllerScriptingInterface;
|
||||
QPointer<LogDialog> _logDialog;
|
||||
QPointer<SnapshotShareDialog> _snapshotShareDialog;
|
||||
|
||||
QString _previousScriptLocation;
|
||||
|
||||
|
|
|
@ -1068,6 +1068,7 @@ void Audio::toggleScope() {
|
|||
memset(_scopeInput.data(), 0, width * sizeof(int16_t));
|
||||
memset(_scopeOutputLeft.data(), 0, width * sizeof(int16_t));
|
||||
memset(_scopeOutputRight.data(), 0, width * sizeof(int16_t));
|
||||
_scopeEnabledPause = false;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -60,15 +60,13 @@ BuckyBalls::BuckyBalls() {
|
|||
|
||||
void BuckyBalls::grab(PalmData& palm, float deltaTime) {
|
||||
float penetration;
|
||||
glm::vec3 diff;
|
||||
FingerData& finger = palm.getFingers()[0]; // Sixense has only one finger
|
||||
glm::vec3 fingerTipPosition = finger.getTipPosition();
|
||||
glm::vec3 fingerTipPosition = palm.getFingerTipPosition();
|
||||
|
||||
if (palm.getControllerButtons() & BUTTON_FWD) {
|
||||
if (!_bballIsGrabbed[palm.getSixenseID()]) {
|
||||
// Look for a ball to grab
|
||||
for (int i = 0; i < NUM_BBALLS; i++) {
|
||||
diff = _bballPosition[i] - fingerTipPosition;
|
||||
glm::vec3 diff = _bballPosition[i] - fingerTipPosition;
|
||||
penetration = glm::length(diff) - (_bballRadius[i] + COLLISION_RADIUS);
|
||||
if (penetration < 0.f) {
|
||||
_bballIsGrabbed[palm.getSixenseID()] = i;
|
||||
|
@ -77,7 +75,7 @@ void BuckyBalls::grab(PalmData& palm, float deltaTime) {
|
|||
}
|
||||
if (_bballIsGrabbed[palm.getSixenseID()]) {
|
||||
// If ball being grabbed, move with finger
|
||||
diff = _bballPosition[_bballIsGrabbed[palm.getSixenseID()]] - fingerTipPosition;
|
||||
glm::vec3 diff = _bballPosition[_bballIsGrabbed[palm.getSixenseID()]] - fingerTipPosition;
|
||||
penetration = glm::length(diff) - (_bballRadius[_bballIsGrabbed[palm.getSixenseID()]] + COLLISION_RADIUS);
|
||||
_bballPosition[_bballIsGrabbed[palm.getSixenseID()]] -= glm::normalize(diff) * penetration;
|
||||
glm::vec3 fingerTipVelocity = palm.getTipVelocity();
|
||||
|
|
|
@ -285,9 +285,6 @@ Menu::Menu() :
|
|||
addCheckableActionToQMenuAndActionHash(viewMenu, MenuOption::Bandwidth, 0, true);
|
||||
addActionToQMenuAndActionHash(viewMenu, MenuOption::BandwidthDetails, 0, this, SLOT(bandwidthDetails()));
|
||||
addActionToQMenuAndActionHash(viewMenu, MenuOption::OctreeStats, 0, this, SLOT(octreeStatsDetails()));
|
||||
addCheckableActionToQMenuAndActionHash(viewMenu, MenuOption::AudioScope, 0, false,
|
||||
appInstance->getAudio(),
|
||||
SLOT(toggleScope()));
|
||||
|
||||
QMenu* developerMenu = addMenu("Developer");
|
||||
|
||||
|
@ -389,20 +386,6 @@ Menu::Menu() :
|
|||
addCheckableActionToQMenuAndActionHash(renderDebugMenu, MenuOption::PipelineWarnings);
|
||||
addCheckableActionToQMenuAndActionHash(renderDebugMenu, MenuOption::SuppressShortTimings);
|
||||
|
||||
addCheckableActionToQMenuAndActionHash(renderDebugMenu,
|
||||
MenuOption::CullSharedFaces,
|
||||
Qt::CTRL | Qt::SHIFT | Qt::Key_C,
|
||||
false,
|
||||
appInstance->getVoxels(),
|
||||
SLOT(cullSharedFaces()));
|
||||
|
||||
addCheckableActionToQMenuAndActionHash(renderDebugMenu,
|
||||
MenuOption::ShowCulledSharedFaces,
|
||||
0,
|
||||
false,
|
||||
appInstance->getVoxels(),
|
||||
SLOT(showCulledSharedFaces()));
|
||||
|
||||
QMenu* audioDebugMenu = developerMenu->addMenu("Audio Debugging Tools");
|
||||
addCheckableActionToQMenuAndActionHash(audioDebugMenu, MenuOption::AudioNoiseReduction,
|
||||
0,
|
||||
|
@ -426,8 +409,11 @@ Menu::Menu() :
|
|||
false,
|
||||
appInstance->getAudio(),
|
||||
SLOT(toggleToneInjection()));
|
||||
addCheckableActionToQMenuAndActionHash(audioDebugMenu, MenuOption::AudioScope, Qt::CTRL | Qt::Key_P, false,
|
||||
appInstance->getAudio(),
|
||||
SLOT(toggleScope()));
|
||||
addCheckableActionToQMenuAndActionHash(audioDebugMenu, MenuOption::AudioScopePause,
|
||||
Qt::CTRL | Qt::Key_P,
|
||||
Qt::CTRL | Qt::SHIFT | Qt::Key_P ,
|
||||
false,
|
||||
appInstance->getAudio(),
|
||||
SLOT(toggleScopePause()));
|
||||
|
@ -916,7 +902,7 @@ void Menu::goToDomainDialog() {
|
|||
}
|
||||
|
||||
void Menu::goToOrientation(QString orientation) {
|
||||
LocationManager::getInstance().goToDestination(orientation);
|
||||
LocationManager::getInstance().goToOrientation(orientation);
|
||||
}
|
||||
|
||||
bool Menu::goToDestination(QString destination) {
|
||||
|
@ -997,9 +983,7 @@ void Menu::goToUser(const QString& user) {
|
|||
/// Open a url, shortcutting any "hifi" scheme URLs to the local application.
|
||||
void Menu::openUrl(const QUrl& url) {
|
||||
if (url.scheme() == "hifi") {
|
||||
QString path = url.toString(QUrl::RemoveScheme);
|
||||
path = path.remove(QRegExp("^:?/*"));
|
||||
goTo(path);
|
||||
goToURL(url.toString());
|
||||
} else {
|
||||
QDesktopServices::openUrl(url);
|
||||
}
|
||||
|
|
|
@ -297,7 +297,6 @@ namespace MenuOption {
|
|||
const QString CollideWithParticles = "Collide With Particles";
|
||||
const QString CollideWithVoxels = "Collide With Voxels";
|
||||
const QString Collisions = "Collisions";
|
||||
const QString CullSharedFaces = "Cull Shared Voxel Faces";
|
||||
const QString DecreaseAvatarSize = "Decrease Avatar Size";
|
||||
const QString DecreaseVoxelSize = "Decrease Voxel Size";
|
||||
const QString DisableAutoAdjustLOD = "Disable Automatically Adjusting LOD";
|
||||
|
@ -370,7 +369,6 @@ namespace MenuOption {
|
|||
const QString ShowBordersVoxelNodes = "Show Borders - Voxel Nodes";
|
||||
const QString ShowBordersModelNodes = "Show Borders - Model Nodes";
|
||||
const QString ShowBordersParticleNodes = "Show Borders - Particle Nodes";
|
||||
const QString ShowCulledSharedFaces = "Show Culled Shared Voxel Faces";
|
||||
const QString ShowIKConstraints = "Show IK Constraints";
|
||||
const QString Stars = "Stars";
|
||||
const QString Stats = "Stats";
|
||||
|
|
|
@ -49,7 +49,7 @@ static const QString TRANSLATION_Z_FIELD = "tz";
|
|||
static const QString JOINT_FIELD = "joint";
|
||||
static const QString FREE_JOINT_FIELD = "freeJoint";
|
||||
|
||||
static const QString S3_URL = "http://highfidelity-public.s3-us-west-1.amazonaws.com";
|
||||
static const QString S3_URL = "http://public.highfidelity.io";
|
||||
static const QString DATA_SERVER_URL = "https://data-web.highfidelity.io";
|
||||
static const QString MODEL_URL = "/api/v1/models";
|
||||
|
||||
|
@ -201,12 +201,15 @@ bool ModelUploader::zip() {
|
|||
mapping = properties.getMapping();
|
||||
|
||||
QByteArray nameField = mapping.value(NAME_FIELD).toByteArray();
|
||||
QString urlBase;
|
||||
if (!nameField.isEmpty()) {
|
||||
QHttpPart textPart;
|
||||
textPart.setHeader(QNetworkRequest::ContentDispositionHeader, "form-data; name=\"model_name\"");
|
||||
textPart.setBody(nameField);
|
||||
_dataMultiPart->append(textPart);
|
||||
_url = S3_URL + "/models/" + MODEL_TYPE_NAMES[_modelType] + "/" + nameField + ".fst";
|
||||
urlBase = S3_URL + "/models/" + MODEL_TYPE_NAMES[_modelType] + "/" + nameField;
|
||||
_url = urlBase + ".fst";
|
||||
|
||||
} else {
|
||||
QMessageBox::warning(NULL,
|
||||
QString("ModelUploader::zip()"),
|
||||
|
@ -218,6 +221,7 @@ bool ModelUploader::zip() {
|
|||
|
||||
QByteArray texdirField = mapping.value(TEXDIR_FIELD).toByteArray();
|
||||
QString texDir;
|
||||
_textureBase = urlBase + "/textures/";
|
||||
if (!texdirField.isEmpty()) {
|
||||
texDir = basePath + "/" + texdirField;
|
||||
QFileInfo texInfo(texDir);
|
||||
|
@ -407,6 +411,10 @@ void ModelUploader::processCheck() {
|
|||
QString("ModelUploader::processCheck()"),
|
||||
QString("Your model is now available in the browser."),
|
||||
QMessageBox::Ok);
|
||||
Application::getInstance()->getGeometryCache()->refresh(_url);
|
||||
foreach (const QByteArray& filename, _textureFilenames) {
|
||||
Application::getInstance()->getTextureCache()->refresh(_textureBase + filename);
|
||||
}
|
||||
deleteLater();
|
||||
break;
|
||||
case QNetworkReply::ContentNotFoundError:
|
||||
|
@ -428,32 +436,31 @@ void ModelUploader::processCheck() {
|
|||
}
|
||||
|
||||
bool ModelUploader::addTextures(const QString& texdir, const FBXGeometry& geometry) {
|
||||
QSet<QByteArray> added;
|
||||
foreach (FBXMesh mesh, geometry.meshes) {
|
||||
foreach (FBXMeshPart part, mesh.parts) {
|
||||
if (!part.diffuseTexture.filename.isEmpty() && part.diffuseTexture.content.isEmpty() &&
|
||||
!added.contains(part.diffuseTexture.filename)) {
|
||||
!_textureFilenames.contains(part.diffuseTexture.filename)) {
|
||||
if (!addPart(texdir + "/" + part.diffuseTexture.filename,
|
||||
QString("texture%1").arg(++_texturesCount), true)) {
|
||||
return false;
|
||||
}
|
||||
added.insert(part.diffuseTexture.filename);
|
||||
_textureFilenames.insert(part.diffuseTexture.filename);
|
||||
}
|
||||
if (!part.normalTexture.filename.isEmpty() && part.normalTexture.content.isEmpty() &&
|
||||
!added.contains(part.normalTexture.filename)) {
|
||||
!_textureFilenames.contains(part.normalTexture.filename)) {
|
||||
if (!addPart(texdir + "/" + part.normalTexture.filename,
|
||||
QString("texture%1").arg(++_texturesCount), true)) {
|
||||
return false;
|
||||
}
|
||||
added.insert(part.normalTexture.filename);
|
||||
_textureFilenames.insert(part.normalTexture.filename);
|
||||
}
|
||||
if (!part.specularTexture.filename.isEmpty() && part.specularTexture.content.isEmpty() &&
|
||||
!added.contains(part.specularTexture.filename)) {
|
||||
!_textureFilenames.contains(part.specularTexture.filename)) {
|
||||
if (!addPart(texdir + "/" + part.specularTexture.filename,
|
||||
QString("texture%1").arg(++_texturesCount), true)) {
|
||||
return false;
|
||||
}
|
||||
added.insert(part.specularTexture.filename);
|
||||
_textureFilenames.insert(part.specularTexture.filename);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -49,6 +49,8 @@ private slots:
|
|||
|
||||
private:
|
||||
QString _url;
|
||||
QString _textureBase;
|
||||
QSet<QByteArray> _textureFilenames;
|
||||
int _lodCount;
|
||||
int _texturesCount;
|
||||
int _totalSize;
|
||||
|
|
|
@ -73,7 +73,6 @@ const float BILLBOARD_LOD_DISTANCE = 40.0f;
|
|||
|
||||
void Avatar::init() {
|
||||
getHead()->init();
|
||||
getHand()->init();
|
||||
_skeletonModel.init();
|
||||
_initialized = true;
|
||||
_shouldRenderBillboard = (getLODDistance() >= BILLBOARD_LOD_DISTANCE);
|
||||
|
@ -354,7 +353,7 @@ void Avatar::renderBody(RenderMode renderMode, float glowLevel) {
|
|||
return;
|
||||
}
|
||||
_skeletonModel.render(1.0f, modelRenderMode);
|
||||
renderAttachments(modelRenderMode);
|
||||
renderAttachments(renderMode);
|
||||
getHand()->render(false, modelRenderMode);
|
||||
}
|
||||
getHead()->render(1.0f, modelRenderMode);
|
||||
|
@ -371,6 +370,9 @@ void Avatar::simulateAttachments(float deltaTime) {
|
|||
int jointIndex = getJointIndex(attachment.jointName);
|
||||
glm::vec3 jointPosition;
|
||||
glm::quat jointRotation;
|
||||
if (!isMyAvatar()) {
|
||||
model->setLODDistance(getLODDistance());
|
||||
}
|
||||
if (_skeletonModel.getJointPosition(jointIndex, jointPosition) &&
|
||||
_skeletonModel.getJointRotation(jointIndex, jointRotation)) {
|
||||
model->setTranslation(jointPosition + jointRotation * attachment.translation * _scale);
|
||||
|
@ -381,9 +383,11 @@ void Avatar::simulateAttachments(float deltaTime) {
|
|||
}
|
||||
}
|
||||
|
||||
void Avatar::renderAttachments(Model::RenderMode renderMode) {
|
||||
void Avatar::renderAttachments(RenderMode renderMode) {
|
||||
Model::RenderMode modelRenderMode = (renderMode == SHADOW_RENDER_MODE) ?
|
||||
Model::SHADOW_RENDER_MODE : Model::DEFAULT_RENDER_MODE;
|
||||
foreach (Model* model, _attachmentModels) {
|
||||
model->render(1.0f, renderMode);
|
||||
model->render(1.0f, modelRenderMode);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -402,7 +406,7 @@ void Avatar::renderBillboard() {
|
|||
}
|
||||
_billboardTexture.reset(new Texture());
|
||||
glBindTexture(GL_TEXTURE_2D, _billboardTexture->getID());
|
||||
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, image.width(), image.height(), 1,
|
||||
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, image.width(), image.height(), 0,
|
||||
GL_BGRA, GL_UNSIGNED_BYTE, image.constBits());
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
|
||||
|
||||
|
@ -605,18 +609,6 @@ bool Avatar::findParticleCollisions(const glm::vec3& particleCenter, float parti
|
|||
const PalmData* palm = handData->getPalm(i);
|
||||
if (palm && palm->hasPaddle()) {
|
||||
// create a disk collision proxy where the hand is
|
||||
glm::vec3 fingerAxis(0.0f);
|
||||
for (size_t f = 0; f < palm->getNumFingers(); ++f) {
|
||||
const FingerData& finger = (palm->getFingers())[f];
|
||||
if (finger.isActive()) {
|
||||
// compute finger axis
|
||||
glm::vec3 fingerTip = finger.getTipPosition();
|
||||
glm::vec3 fingerRoot = finger.getRootPosition();
|
||||
fingerAxis = glm::normalize(fingerTip - fingerRoot);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
int jointIndex = -1;
|
||||
glm::vec3 handPosition;
|
||||
if (i == 0) {
|
||||
|
@ -627,6 +619,8 @@ bool Avatar::findParticleCollisions(const glm::vec3& particleCenter, float parti
|
|||
_skeletonModel.getRightHandPosition(handPosition);
|
||||
jointIndex = _skeletonModel.getRightHandJointIndex();
|
||||
}
|
||||
|
||||
glm::vec3 fingerAxis = palm->getFingerDirection();
|
||||
glm::vec3 diskCenter = handPosition + HAND_PADDLE_OFFSET * fingerAxis;
|
||||
glm::vec3 diskNormal = palm->getNormal();
|
||||
const float DISK_THICKNESS = 0.08f;
|
||||
|
@ -709,7 +703,9 @@ void Avatar::setSkeletonModelURL(const QUrl& skeletonModelURL) {
|
|||
|
||||
void Avatar::setAttachmentData(const QVector<AttachmentData>& attachmentData) {
|
||||
AvatarData::setAttachmentData(attachmentData);
|
||||
|
||||
if (QThread::currentThread() != thread()) {
|
||||
return;
|
||||
}
|
||||
// make sure we have as many models as attachments
|
||||
while (_attachmentModels.size() < attachmentData.size()) {
|
||||
Model* model = new Model(this);
|
||||
|
|
|
@ -54,10 +54,9 @@ enum ScreenTintLayer {
|
|||
NUM_SCREEN_TINT_LAYERS
|
||||
};
|
||||
|
||||
// Where one's own Avatar begins in the world (will be overwritten if avatar data file is found)
|
||||
// this is basically in the center of the ground plane. Slightly adjusted. This was asked for by
|
||||
// Grayson as he's building a street around here for demo dinner 2
|
||||
const glm::vec3 START_LOCATION(0.485f * TREE_SCALE, 0.0f, 0.5f * TREE_SCALE);
|
||||
// Where one's own Avatar begins in the world (will be overwritten if avatar data file is found).
|
||||
// This is the start location in the Sandbox (xyz: 6270, 211, 6000).
|
||||
const glm::vec3 START_LOCATION(0.38269043f * TREE_SCALE, 0.01287842f * TREE_SCALE, 0.36621094f * TREE_SCALE);
|
||||
|
||||
class Texture;
|
||||
|
||||
|
@ -192,7 +191,7 @@ protected:
|
|||
virtual bool shouldRenderHead(const glm::vec3& cameraPosition, RenderMode renderMode) const;
|
||||
|
||||
void simulateAttachments(float deltaTime);
|
||||
void renderAttachments(Model::RenderMode renderMode);
|
||||
virtual void renderAttachments(RenderMode renderMode);
|
||||
|
||||
virtual void updateJointMappings();
|
||||
|
||||
|
|
|
@ -23,28 +23,16 @@
|
|||
|
||||
using namespace std;
|
||||
|
||||
const float FINGERTIP_COLLISION_RADIUS = 0.01f;
|
||||
const float PALM_COLLISION_RADIUS = 0.03f;
|
||||
|
||||
|
||||
Hand::Hand(Avatar* owningAvatar) :
|
||||
HandData((AvatarData*)owningAvatar),
|
||||
|
||||
_owningAvatar(owningAvatar),
|
||||
_renderAlpha(1.0)
|
||||
_owningAvatar(owningAvatar)
|
||||
{
|
||||
}
|
||||
|
||||
void Hand::init() {
|
||||
}
|
||||
|
||||
void Hand::reset() {
|
||||
}
|
||||
|
||||
void Hand::simulate(float deltaTime, bool isMine) {
|
||||
|
||||
calculateGeometry();
|
||||
|
||||
if (isMine) {
|
||||
// Iterate hand controllers, take actions as needed
|
||||
for (size_t i = 0; i < getNumPalms(); ++i) {
|
||||
|
@ -146,55 +134,7 @@ void Hand::resolvePenetrations() {
|
|||
}
|
||||
}
|
||||
|
||||
void Hand::calculateGeometry() {
|
||||
// generate finger tip balls....
|
||||
_leapFingerTipBalls.clear();
|
||||
for (size_t i = 0; i < getNumPalms(); ++i) {
|
||||
PalmData& palm = getPalms()[i];
|
||||
if (palm.isActive()) {
|
||||
for (size_t f = 0; f < palm.getNumFingers(); ++f) {
|
||||
FingerData& finger = palm.getFingers()[f];
|
||||
if (finger.isActive()) {
|
||||
const float standardBallRadius = FINGERTIP_COLLISION_RADIUS;
|
||||
HandBall ball;
|
||||
ball.rotation = getBaseOrientation();
|
||||
ball.position = finger.getTipPosition();
|
||||
ball.radius = standardBallRadius;
|
||||
ball.touchForce = 0.0;
|
||||
ball.isCollidable = true;
|
||||
ball.isColliding = false;
|
||||
_leapFingerTipBalls.push_back(ball);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// generate finger root balls....
|
||||
_leapFingerRootBalls.clear();
|
||||
for (size_t i = 0; i < getNumPalms(); ++i) {
|
||||
PalmData& palm = getPalms()[i];
|
||||
if (palm.isActive()) {
|
||||
for (size_t f = 0; f < palm.getNumFingers(); ++f) {
|
||||
FingerData& finger = palm.getFingers()[f];
|
||||
if (finger.isActive()) {
|
||||
const float standardBallRadius = 0.005f;
|
||||
HandBall ball;
|
||||
ball.rotation = getBaseOrientation();
|
||||
ball.position = finger.getRootPosition();
|
||||
ball.radius = standardBallRadius;
|
||||
ball.touchForce = 0.0;
|
||||
ball.isCollidable = true;
|
||||
ball.isColliding = false;
|
||||
_leapFingerRootBalls.push_back(ball);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Hand::render(bool isMine, Model::RenderMode renderMode) {
|
||||
_renderAlpha = 1.0;
|
||||
|
||||
if (renderMode != Model::SHADOW_RENDER_MODE &&
|
||||
Menu::getInstance()->isOptionChecked(MenuOption::RenderSkeletonCollisionShapes)) {
|
||||
// draw a green sphere at hand joint location, which is actually near the wrist)
|
||||
|
@ -213,19 +153,18 @@ void Hand::render(bool isMine, Model::RenderMode renderMode) {
|
|||
}
|
||||
|
||||
if (renderMode != Model::SHADOW_RENDER_MODE && Menu::getInstance()->isOptionChecked(MenuOption::DisplayHands)) {
|
||||
renderLeapHands(isMine);
|
||||
renderHandTargets(isMine);
|
||||
}
|
||||
|
||||
glEnable(GL_DEPTH_TEST);
|
||||
glEnable(GL_RESCALE_NORMAL);
|
||||
|
||||
}
|
||||
|
||||
void Hand::renderLeapHands(bool isMine) {
|
||||
void Hand::renderHandTargets(bool isMine) {
|
||||
glPushMatrix();
|
||||
|
||||
const float alpha = 1.0f;
|
||||
|
||||
const glm::vec3 handColor(1.0, 0.84, 0.66); // use the skin color
|
||||
const glm::vec3 handColor(1.0, 0.0, 0.0); // Color the hand targets red to be different than skin
|
||||
|
||||
glEnable(GL_DEPTH_TEST);
|
||||
glDepthMask(GL_TRUE);
|
||||
|
@ -236,9 +175,9 @@ void Hand::renderLeapHands(bool isMine) {
|
|||
if (!palm.isActive()) {
|
||||
continue;
|
||||
}
|
||||
glm::vec3 targetPosition;
|
||||
palm.getBallHoldPosition(targetPosition);
|
||||
glm::vec3 targetPosition = palm.getFingerTipPosition();
|
||||
glPushMatrix();
|
||||
glTranslatef(targetPosition.x, targetPosition.y, targetPosition.z);
|
||||
|
||||
const float collisionRadius = 0.05f;
|
||||
glColor4f(0.5f,0.5f,0.5f, alpha);
|
||||
|
@ -247,66 +186,26 @@ void Hand::renderLeapHands(bool isMine) {
|
|||
}
|
||||
}
|
||||
|
||||
glPushMatrix();
|
||||
// Draw the leap balls
|
||||
for (size_t i = 0; i < _leapFingerTipBalls.size(); i++) {
|
||||
if (alpha > 0.0f) {
|
||||
if (_leapFingerTipBalls[i].isColliding) {
|
||||
glColor4f(handColor.r, 0, 0, alpha);
|
||||
} else {
|
||||
glColor4f(handColor.r, handColor.g, handColor.b, alpha);
|
||||
}
|
||||
glPushMatrix();
|
||||
glTranslatef(_leapFingerTipBalls[i].position.x, _leapFingerTipBalls[i].position.y, _leapFingerTipBalls[i].position.z);
|
||||
glutSolidSphere(_leapFingerTipBalls[i].radius, 20.0f, 20.0f);
|
||||
glPopMatrix();
|
||||
}
|
||||
}
|
||||
|
||||
// Draw the finger root cones
|
||||
const float PALM_BALL_RADIUS = 0.03f;
|
||||
const float PALM_DISK_RADIUS = 0.06f;
|
||||
const float PALM_DISK_THICKNESS = 0.01f;
|
||||
const float PALM_FINGER_ROD_RADIUS = 0.003f;
|
||||
|
||||
// Draw the palm ball and disk
|
||||
for (size_t i = 0; i < getNumPalms(); ++i) {
|
||||
PalmData& palm = getPalms()[i];
|
||||
if (palm.isActive()) {
|
||||
for (size_t f = 0; f < palm.getNumFingers(); ++f) {
|
||||
FingerData& finger = palm.getFingers()[f];
|
||||
if (finger.isActive()) {
|
||||
glColor4f(handColor.r, handColor.g, handColor.b, 0.5);
|
||||
glm::vec3 tip = finger.getTipPosition();
|
||||
glm::vec3 root = finger.getRootPosition();
|
||||
Avatar::renderJointConnectingCone(root, tip, 0.001f, 0.003f);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Draw the hand paddles
|
||||
int MAX_NUM_PADDLES = 2; // one for left and one for right
|
||||
glColor4f(handColor.r, handColor.g, handColor.b, 0.3f);
|
||||
for (int i = 0; i < MAX_NUM_PADDLES; i++) {
|
||||
const PalmData* palm = getPalm(i);
|
||||
if (palm) {
|
||||
// compute finger axis
|
||||
glm::vec3 fingerAxis(0.f);
|
||||
for (size_t f = 0; f < palm->getNumFingers(); ++f) {
|
||||
const FingerData& finger = (palm->getFingers())[f];
|
||||
if (finger.isActive()) {
|
||||
glm::vec3 fingerTip = finger.getTipPosition();
|
||||
glm::vec3 fingerRoot = finger.getRootPosition();
|
||||
fingerAxis = glm::normalize(fingerTip - fingerRoot);
|
||||
break;
|
||||
}
|
||||
}
|
||||
// compute paddle position
|
||||
glm::vec3 handPosition;
|
||||
if (i == SIXENSE_CONTROLLER_ID_LEFT_HAND) {
|
||||
_owningAvatar->getSkeletonModel().getLeftHandPosition(handPosition);
|
||||
} else if (i == SIXENSE_CONTROLLER_ID_RIGHT_HAND) {
|
||||
_owningAvatar->getSkeletonModel().getRightHandPosition(handPosition);
|
||||
}
|
||||
glm::vec3 tip = handPosition + HAND_PADDLE_OFFSET * fingerAxis;
|
||||
glm::vec3 root = tip + palm->getNormal() * HAND_PADDLE_THICKNESS;
|
||||
// render a very shallow cone as the paddle
|
||||
Avatar::renderJointConnectingCone(root, tip, HAND_PADDLE_RADIUS, 0.f);
|
||||
glColor4f(handColor.r, handColor.g, handColor.b, alpha);
|
||||
glm::vec3 tip = palm.getFingerTipPosition();
|
||||
glm::vec3 root = palm.getPosition();
|
||||
Avatar::renderJointConnectingCone(root, tip, PALM_FINGER_ROD_RADIUS, PALM_FINGER_ROD_RADIUS);
|
||||
// Render sphere at palm/finger root
|
||||
glm::vec3 offsetFromPalm = root + palm.getNormal() * PALM_DISK_THICKNESS;
|
||||
Avatar::renderJointConnectingCone(root, offsetFromPalm, PALM_DISK_RADIUS, 0.0f);
|
||||
glPushMatrix();
|
||||
glTranslatef(root.x, root.y, root.z);
|
||||
glutSolidSphere(PALM_BALL_RADIUS, 20.0f, 20.0f);
|
||||
glPopMatrix();
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -50,15 +50,9 @@ public:
|
|||
float touchForce; // a scalar determining the amount that the cursor (or hand) is penetrating the ball
|
||||
};
|
||||
|
||||
void init();
|
||||
void reset();
|
||||
void simulate(float deltaTime, bool isMine);
|
||||
void render(bool isMine, Model::RenderMode renderMode = Model::DEFAULT_RENDER_MODE);
|
||||
|
||||
// getters
|
||||
const glm::vec3& getLeapFingerTipBallPosition (int ball) const { return _leapFingerTipBalls [ball].position;}
|
||||
const glm::vec3& getLeapFingerRootBallPosition(int ball) const { return _leapFingerRootBalls[ball].position;}
|
||||
|
||||
void collideAgainstAvatar(Avatar* avatar, bool isMyHand);
|
||||
void collideAgainstOurself();
|
||||
|
||||
|
@ -72,14 +66,8 @@ private:
|
|||
int _controllerButtons; /// Button states read from hand-held controllers
|
||||
|
||||
Avatar* _owningAvatar;
|
||||
float _renderAlpha;
|
||||
std::vector<HandBall> _leapFingerTipBalls;
|
||||
std::vector<HandBall> _leapFingerRootBalls;
|
||||
|
||||
void renderLeapHands(bool isMine);
|
||||
void renderLeapFingerTrails();
|
||||
|
||||
void calculateGeometry();
|
||||
void renderHandTargets(bool isMine);
|
||||
};
|
||||
|
||||
#endif // hifi_Hand_h
|
||||
|
|
|
@ -106,16 +106,10 @@ void Head::simulate(float deltaTime, bool isMine, bool billboard) {
|
|||
|
||||
const float BROW_LIFT_THRESHOLD = 100.0f;
|
||||
if (_audioAttack > BROW_LIFT_THRESHOLD) {
|
||||
_browAudioLift += sqrtf(_audioAttack) * 0.00005f;
|
||||
_browAudioLift += sqrtf(_audioAttack) * 0.01f;
|
||||
}
|
||||
_browAudioLift = glm::clamp(_browAudioLift *= 0.7f, 0.0f, 1.0f);
|
||||
|
||||
const float CLAMP = 0.01f;
|
||||
if (_browAudioLift > CLAMP) {
|
||||
_browAudioLift = CLAMP;
|
||||
}
|
||||
|
||||
_browAudioLift *= 0.7f;
|
||||
|
||||
const float BLINK_SPEED = 10.0f;
|
||||
const float FULLY_OPEN = 0.0f;
|
||||
const float FULLY_CLOSED = 1.0f;
|
||||
|
@ -147,12 +141,12 @@ void Head::simulate(float deltaTime, bool isMine, bool billboard) {
|
|||
}
|
||||
|
||||
// use data to update fake Faceshift blendshape coefficients
|
||||
const float BROW_LIFT_SCALE = 500.0f;
|
||||
const float JAW_OPEN_SCALE = 0.01f;
|
||||
const float JAW_OPEN_DEAD_ZONE = 0.75f;
|
||||
Application::getInstance()->getFaceshift()->updateFakeCoefficients(_leftEyeBlink, _rightEyeBlink,
|
||||
min(1.0f, _browAudioLift * BROW_LIFT_SCALE), glm::clamp(sqrt(_averageLoudness * JAW_OPEN_SCALE) -
|
||||
JAW_OPEN_DEAD_ZONE, 0.0f, 1.0f), _blendshapeCoefficients);
|
||||
const float JAW_OPEN_SCALE = 10.f;
|
||||
Application::getInstance()->getFaceshift()->updateFakeCoefficients(_leftEyeBlink,
|
||||
_rightEyeBlink,
|
||||
_browAudioLift,
|
||||
glm::clamp(log(_averageLoudness) / JAW_OPEN_SCALE, 0.0f, 1.0f),
|
||||
_blendshapeCoefficients);
|
||||
}
|
||||
|
||||
if (!isMine) {
|
||||
|
|
|
@ -46,6 +46,9 @@ const float PITCH_SPEED = 100.0f; // degrees/sec
|
|||
const float COLLISION_RADIUS_SCALAR = 1.2f; // pertains to avatar-to-avatar collisions
|
||||
const float COLLISION_RADIUS_SCALE = 0.125f;
|
||||
|
||||
const float MIN_KEYBOARD_CONTROL_SPEED = 2.0f;
|
||||
const float MAX_WALKING_SPEED = 3.0f * MIN_KEYBOARD_CONTROL_SPEED;
|
||||
|
||||
const float DATA_SERVER_LOCATION_CHANGE_UPDATE_MSECS = 5.0f * 1000.0f;
|
||||
|
||||
// TODO: normalize avatar speed for standard avatar size, then scale all motion logic
|
||||
|
@ -64,7 +67,7 @@ MyAvatar::MyAvatar() :
|
|||
_distanceToNearestAvatar(std::numeric_limits<float>::max()),
|
||||
_wasPushing(false),
|
||||
_isPushing(false),
|
||||
_wasStuck(false),
|
||||
_trapDuration(0.0f),
|
||||
_thrust(0.0f),
|
||||
_motorVelocity(0.0f),
|
||||
_motorTimescale(DEFAULT_MOTOR_TIMESCALE),
|
||||
|
@ -94,18 +97,21 @@ MyAvatar::~MyAvatar() {
|
|||
void MyAvatar::reset() {
|
||||
_skeletonModel.reset();
|
||||
getHead()->reset();
|
||||
getHand()->reset();
|
||||
_oculusYawOffset = 0.0f;
|
||||
|
||||
setVelocity(glm::vec3(0.0f));
|
||||
setThrust(glm::vec3(0.0f));
|
||||
setOrientation(glm::quat(glm::vec3(0.0f)));
|
||||
// Reset the pitch and roll components of the avatar's orientation, preserve yaw direction
|
||||
glm::vec3 eulers = safeEulerAngles(getOrientation());
|
||||
eulers.x = 0.f;
|
||||
eulers.z = 0.f;
|
||||
setOrientation(glm::quat(eulers));
|
||||
}
|
||||
|
||||
void MyAvatar::update(float deltaTime) {
|
||||
Head* head = getHead();
|
||||
head->relaxLean(deltaTime);
|
||||
updateFromGyros(deltaTime);
|
||||
updateFromTrackers(deltaTime);
|
||||
if (Menu::getInstance()->isOptionChecked(MenuOption::MoveWithLean)) {
|
||||
// Faceshift drive is enabled, set the avatar drive based on the head position
|
||||
moveWithLean();
|
||||
|
@ -131,8 +137,8 @@ void MyAvatar::simulate(float deltaTime) {
|
|||
Application::getInstance()->getCamera()->setScale(scale);
|
||||
}
|
||||
|
||||
// update the movement of the hand and process handshaking with other avatars...
|
||||
updateHandMovementAndTouching(deltaTime);
|
||||
// no extra movement of the hand here any more ...
|
||||
_handState = HAND_STATE_NULL;
|
||||
|
||||
updateOrientation(deltaTime);
|
||||
|
||||
|
@ -215,7 +221,7 @@ void MyAvatar::simulate(float deltaTime) {
|
|||
if (_collisionGroups & COLLISION_GROUP_VOXELS) {
|
||||
updateCollisionWithVoxels(deltaTime, radius);
|
||||
} else {
|
||||
_wasStuck = false;
|
||||
_trapDuration = 0.0f;
|
||||
}
|
||||
if (_collisionGroups & COLLISION_GROUP_AVATARS) {
|
||||
updateCollisionWithAvatars(deltaTime);
|
||||
|
@ -228,26 +234,33 @@ void MyAvatar::simulate(float deltaTime) {
|
|||
}
|
||||
|
||||
// Update avatar head rotation with sensor data
|
||||
void MyAvatar::updateFromGyros(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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -265,6 +278,14 @@ void MyAvatar::updateFromGyros(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);
|
||||
|
@ -502,6 +523,67 @@ void MyAvatar::loadData(QSettings* settings) {
|
|||
settings->endGroup();
|
||||
}
|
||||
|
||||
void MyAvatar::saveAttachmentData(const AttachmentData& attachment) const {
|
||||
QSettings* settings = Application::getInstance()->lockSettings();
|
||||
settings->beginGroup("savedAttachmentData");
|
||||
settings->beginGroup(_skeletonModel.getURL().toString());
|
||||
settings->beginGroup(attachment.modelURL.toString());
|
||||
settings->setValue("jointName", attachment.jointName);
|
||||
|
||||
settings->beginGroup(attachment.jointName);
|
||||
settings->setValue("translation_x", attachment.translation.x);
|
||||
settings->setValue("translation_y", attachment.translation.y);
|
||||
settings->setValue("translation_z", attachment.translation.z);
|
||||
glm::vec3 eulers = safeEulerAngles(attachment.rotation);
|
||||
settings->setValue("rotation_x", eulers.x);
|
||||
settings->setValue("rotation_y", eulers.y);
|
||||
settings->setValue("rotation_z", eulers.z);
|
||||
settings->setValue("scale", attachment.scale);
|
||||
|
||||
settings->endGroup();
|
||||
settings->endGroup();
|
||||
settings->endGroup();
|
||||
settings->endGroup();
|
||||
Application::getInstance()->unlockSettings();
|
||||
}
|
||||
|
||||
AttachmentData MyAvatar::loadAttachmentData(const QUrl& modelURL, const QString& jointName) const {
|
||||
QSettings* settings = Application::getInstance()->lockSettings();
|
||||
settings->beginGroup("savedAttachmentData");
|
||||
settings->beginGroup(_skeletonModel.getURL().toString());
|
||||
settings->beginGroup(modelURL.toString());
|
||||
|
||||
AttachmentData attachment;
|
||||
attachment.modelURL = modelURL;
|
||||
if (jointName.isEmpty()) {
|
||||
attachment.jointName = settings->value("jointName").toString();
|
||||
} else {
|
||||
attachment.jointName = jointName;
|
||||
}
|
||||
settings->beginGroup(attachment.jointName);
|
||||
if (settings->contains("translation_x")) {
|
||||
attachment.translation.x = loadSetting(settings, "translation_x", 0.0f);
|
||||
attachment.translation.y = loadSetting(settings, "translation_y", 0.0f);
|
||||
attachment.translation.z = loadSetting(settings, "translation_z", 0.0f);
|
||||
glm::vec3 eulers;
|
||||
eulers.x = loadSetting(settings, "rotation_x", 0.0f);
|
||||
eulers.y = loadSetting(settings, "rotation_y", 0.0f);
|
||||
eulers.z = loadSetting(settings, "rotation_z", 0.0f);
|
||||
attachment.rotation = glm::quat(eulers);
|
||||
attachment.scale = loadSetting(settings, "scale", 1.0f);
|
||||
} else {
|
||||
attachment = AttachmentData();
|
||||
}
|
||||
|
||||
settings->endGroup();
|
||||
settings->endGroup();
|
||||
settings->endGroup();
|
||||
settings->endGroup();
|
||||
Application::getInstance()->unlockSettings();
|
||||
|
||||
return attachment;
|
||||
}
|
||||
|
||||
int MyAvatar::parseDataAtOffset(const QByteArray& packet, int offset) {
|
||||
qDebug() << "Error: ignoring update packet for MyAvatar"
|
||||
<< " packetLength = " << packet.size()
|
||||
|
@ -573,6 +655,31 @@ void MyAvatar::setSkeletonModelURL(const QUrl& skeletonModelURL) {
|
|||
_billboardValid = false;
|
||||
}
|
||||
|
||||
void MyAvatar::setAttachmentData(const QVector<AttachmentData>& attachmentData) {
|
||||
Avatar::setAttachmentData(attachmentData);
|
||||
if (QThread::currentThread() != thread()) {
|
||||
return;
|
||||
}
|
||||
_billboardValid = false;
|
||||
}
|
||||
|
||||
void MyAvatar::attach(const QString& modelURL, const QString& jointName, const glm::vec3& translation,
|
||||
const glm::quat& rotation, float scale, bool allowDuplicates, bool useSaved) {
|
||||
if (QThread::currentThread() != thread()) {
|
||||
Avatar::attach(modelURL, jointName, translation, rotation, scale, allowDuplicates, useSaved);
|
||||
return;
|
||||
}
|
||||
if (useSaved) {
|
||||
AttachmentData attachment = loadAttachmentData(modelURL, jointName);
|
||||
if (attachment.isValid()) {
|
||||
Avatar::attach(modelURL, attachment.jointName, attachment.translation,
|
||||
attachment.rotation, attachment.scale, allowDuplicates, useSaved);
|
||||
return;
|
||||
}
|
||||
}
|
||||
Avatar::attach(modelURL, jointName, translation, rotation, scale, allowDuplicates, useSaved);
|
||||
}
|
||||
|
||||
void MyAvatar::renderBody(RenderMode renderMode, float glowLevel) {
|
||||
if (!(_skeletonModel.isRenderable() && getHead()->getFaceModel().isRenderable())) {
|
||||
return; // wait until both models are loaded
|
||||
|
@ -582,7 +689,7 @@ void MyAvatar::renderBody(RenderMode renderMode, float glowLevel) {
|
|||
Model::RenderMode modelRenderMode = (renderMode == SHADOW_RENDER_MODE) ?
|
||||
Model::SHADOW_RENDER_MODE : Model::DEFAULT_RENDER_MODE;
|
||||
_skeletonModel.render(1.0f, modelRenderMode);
|
||||
renderAttachments(modelRenderMode);
|
||||
renderAttachments(renderMode);
|
||||
|
||||
// Render head so long as the camera isn't inside it
|
||||
if (shouldRenderHead(Application::getInstance()->getCamera()->getPosition(), renderMode)) {
|
||||
|
@ -699,8 +806,6 @@ void MyAvatar::updateMotorFromKeyboard(float deltaTime, bool walking) {
|
|||
if (directionLength > EPSILON) {
|
||||
direction /= directionLength;
|
||||
// the finalMotorSpeed depends on whether we are walking or not
|
||||
const float MIN_KEYBOARD_CONTROL_SPEED = 2.0f;
|
||||
const float MAX_WALKING_SPEED = 3.0f * MIN_KEYBOARD_CONTROL_SPEED;
|
||||
float finalMaxMotorSpeed = walking ? MAX_WALKING_SPEED : _maxMotorSpeed;
|
||||
|
||||
float motorLength = glm::length(_motorVelocity);
|
||||
|
@ -911,40 +1016,6 @@ void MyAvatar::updateThrust(float deltaTime) {
|
|||
}
|
||||
*/
|
||||
|
||||
void MyAvatar::updateHandMovementAndTouching(float deltaTime) {
|
||||
glm::quat orientation = getOrientation();
|
||||
|
||||
// reset hand and arm positions according to hand movement
|
||||
glm::vec3 up = orientation * IDENTITY_UP;
|
||||
|
||||
bool pointing = false;
|
||||
if (glm::length(_mouseRayDirection) > EPSILON && !Application::getInstance()->isMouseHidden()) {
|
||||
// confine to the approximate shoulder plane
|
||||
glm::vec3 pointDirection = _mouseRayDirection;
|
||||
if (glm::dot(_mouseRayDirection, up) > 0.0f) {
|
||||
glm::vec3 projectedVector = glm::cross(up, glm::cross(_mouseRayDirection, up));
|
||||
if (glm::length(projectedVector) > EPSILON) {
|
||||
pointDirection = glm::normalize(projectedVector);
|
||||
}
|
||||
}
|
||||
glm::vec3 shoulderPosition;
|
||||
if (_skeletonModel.getRightShoulderPosition(shoulderPosition)) {
|
||||
glm::vec3 farVector = _mouseRayOrigin + pointDirection * (float)TREE_SCALE - shoulderPosition;
|
||||
const float ARM_RETRACTION = 0.75f;
|
||||
float retractedLength = _skeletonModel.getRightArmLength() * ARM_RETRACTION;
|
||||
setHandPosition(shoulderPosition + glm::normalize(farVector) * retractedLength);
|
||||
pointing = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (_mousePressed) {
|
||||
_handState = HAND_STATE_GRASPING;
|
||||
} else if (pointing) {
|
||||
_handState = HAND_STATE_POINTING;
|
||||
} else {
|
||||
_handState = HAND_STATE_NULL;
|
||||
}
|
||||
}
|
||||
|
||||
void MyAvatar::updateCollisionWithEnvironment(float deltaTime, float radius) {
|
||||
glm::vec3 up = getBodyUpDirection();
|
||||
|
@ -964,48 +1035,102 @@ void MyAvatar::updateCollisionWithEnvironment(float deltaTime, float radius) {
|
|||
static CollisionList myCollisions(64);
|
||||
|
||||
void MyAvatar::updateCollisionWithVoxels(float deltaTime, float radius) {
|
||||
const float MIN_STUCK_SPEED = 100.0f;
|
||||
const float MAX_VOXEL_COLLISION_SPEED = 100.0f;
|
||||
float speed = glm::length(_velocity);
|
||||
if (speed > MIN_STUCK_SPEED) {
|
||||
if (speed > MAX_VOXEL_COLLISION_SPEED) {
|
||||
// don't even bother to try to collide against voxles when moving very fast
|
||||
_trapDuration = 0.0f;
|
||||
return;
|
||||
}
|
||||
bool isTrapped = false;
|
||||
myCollisions.clear();
|
||||
const CapsuleShape& boundingShape = _skeletonModel.getBoundingShape();
|
||||
if (Application::getInstance()->getVoxelTree()->findShapeCollisions(&boundingShape, myCollisions)) {
|
||||
if (Application::getInstance()->getVoxelTree()->findShapeCollisions(&boundingShape, myCollisions, Octree::TryLock)) {
|
||||
const float VOXEL_ELASTICITY = 0.0f;
|
||||
const float VOXEL_DAMPING = 0.0f;
|
||||
float capsuleRadius = boundingShape.getRadius();
|
||||
|
||||
float capsuleHalfHeight = boundingShape.getHalfHeight();
|
||||
const float MAX_STEP_HEIGHT = capsuleRadius + capsuleHalfHeight;
|
||||
const float MIN_STEP_HEIGHT = 0.0f;
|
||||
glm::vec3 footBase = boundingShape.getPosition() - (capsuleRadius + capsuleHalfHeight) * _worldUpDirection;
|
||||
float highestStep = 0.0f;
|
||||
glm::vec3 stepPenetration(0.0f);
|
||||
glm::vec3 totalPenetration(0.0f);
|
||||
bool isStuck = false;
|
||||
|
||||
for (int i = 0; i < myCollisions.size(); ++i) {
|
||||
CollisionInfo* collision = myCollisions[i];
|
||||
float depth = glm::length(collision->_penetration);
|
||||
if (depth > capsuleRadius) {
|
||||
isStuck = true;
|
||||
if (_wasStuck) {
|
||||
glm::vec3 cubeCenter = collision->_vecData;
|
||||
float cubeSide = collision->_floatData;
|
||||
glm::vec3 cubeCenter = collision->_vecData;
|
||||
float cubeSide = collision->_floatData;
|
||||
float verticalDepth = glm::dot(collision->_penetration, _worldUpDirection);
|
||||
float horizontalDepth = glm::length(collision->_penetration - verticalDepth * _worldUpDirection);
|
||||
const float MAX_TRAP_PERIOD = 0.125f;
|
||||
if (horizontalDepth > capsuleRadius || fabsf(verticalDepth) > MAX_STEP_HEIGHT) {
|
||||
isTrapped = true;
|
||||
if (_trapDuration > MAX_TRAP_PERIOD) {
|
||||
float distance = glm::dot(boundingShape.getPosition() - cubeCenter, _worldUpDirection);
|
||||
if (distance < 0.0f) {
|
||||
distance = fabsf(distance) + 0.5f * cubeSide;
|
||||
}
|
||||
distance += capsuleRadius + boundingShape.getHalfHeight();
|
||||
distance += capsuleRadius + capsuleHalfHeight;
|
||||
totalPenetration = addPenetrations(totalPenetration, - distance * _worldUpDirection);
|
||||
continue;
|
||||
}
|
||||
} else if (_trapDuration > MAX_TRAP_PERIOD) {
|
||||
// we're trapped, ignore this collision
|
||||
continue;
|
||||
}
|
||||
totalPenetration = addPenetrations(totalPenetration, collision->_penetration);
|
||||
if (glm::dot(collision->_penetration, _velocity) >= 0.0f) {
|
||||
glm::vec3 cubeTop = cubeCenter + (0.5f * cubeSide) * _worldUpDirection;
|
||||
float stepHeight = glm::dot(_worldUpDirection, cubeTop - footBase);
|
||||
if (stepHeight > highestStep) {
|
||||
highestStep = stepHeight;
|
||||
stepPenetration = collision->_penetration;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
float penetrationLength = glm::length(totalPenetration);
|
||||
if (penetrationLength < EPSILON) {
|
||||
_trapDuration = 0.0f;
|
||||
return;
|
||||
}
|
||||
float verticalPenetration = glm::dot(totalPenetration, _worldUpDirection);
|
||||
if (highestStep > MIN_STEP_HEIGHT && highestStep < MAX_STEP_HEIGHT && verticalPenetration <= 0.0f) {
|
||||
// we're colliding against an edge
|
||||
glm::vec3 targetVelocity = _motorVelocity;
|
||||
if (_motionBehaviors & AVATAR_MOTION_MOTOR_USE_LOCAL_FRAME) {
|
||||
// rotate _motorVelocity into world frame
|
||||
glm::quat rotation = getHead()->getCameraOrientation();
|
||||
targetVelocity = rotation * _motorVelocity;
|
||||
}
|
||||
if (_wasPushing && glm::dot(targetVelocity, totalPenetration) > EPSILON) {
|
||||
// we're puhing into the edge, so we want to lift
|
||||
|
||||
// remove unhelpful horizontal component of the step's penetration
|
||||
totalPenetration -= stepPenetration - (glm::dot(stepPenetration, _worldUpDirection) * _worldUpDirection);
|
||||
|
||||
// further adjust penetration to help lift
|
||||
float liftSpeed = glm::max(MAX_WALKING_SPEED, speed);
|
||||
float thisStep = glm::min(liftSpeed * deltaTime, highestStep);
|
||||
float extraStep = glm::dot(totalPenetration, _worldUpDirection) + thisStep;
|
||||
if (extraStep > 0.0f) {
|
||||
totalPenetration -= extraStep * _worldUpDirection;
|
||||
}
|
||||
|
||||
_position -= totalPenetration;
|
||||
} else {
|
||||
// we're not pushing into the edge, so let the avatar fall
|
||||
applyHardCollision(totalPenetration, VOXEL_ELASTICITY, VOXEL_DAMPING);
|
||||
}
|
||||
} else {
|
||||
applyHardCollision(totalPenetration, VOXEL_ELASTICITY, VOXEL_DAMPING);
|
||||
}
|
||||
applyHardCollision(totalPenetration, VOXEL_ELASTICITY, VOXEL_DAMPING);
|
||||
_wasStuck = isStuck;
|
||||
|
||||
const float VOXEL_COLLISION_FREQUENCY = 0.5f;
|
||||
updateCollisionSound(myCollisions[0]->_penetration, deltaTime, VOXEL_COLLISION_FREQUENCY);
|
||||
} else {
|
||||
_wasStuck = false;
|
||||
}
|
||||
}
|
||||
_trapDuration = isTrapped ? _trapDuration + deltaTime : 0.0f;
|
||||
}
|
||||
|
||||
void MyAvatar::applyHardCollision(const glm::vec3& penetration, float elasticity, float damping) {
|
||||
|
@ -1270,6 +1395,11 @@ void MyAvatar::maybeUpdateBillboard() {
|
|||
if (_billboardValid || !(_skeletonModel.isLoadedWithTextures() && getHead()->getFaceModel().isLoadedWithTextures())) {
|
||||
return;
|
||||
}
|
||||
foreach (Model* model, _attachmentModels) {
|
||||
if (!model->isLoadedWithTextures()) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
QImage image = Application::getInstance()->renderAvatarBillboard();
|
||||
_billboard.clear();
|
||||
QBuffer buffer(&_billboard);
|
||||
|
@ -1379,6 +1509,23 @@ void MyAvatar::updateMotionBehaviorsFromMenu() {
|
|||
}
|
||||
}
|
||||
|
||||
void MyAvatar::renderAttachments(RenderMode renderMode) {
|
||||
if (Application::getInstance()->getCamera()->getMode() != CAMERA_MODE_FIRST_PERSON || renderMode == MIRROR_RENDER_MODE) {
|
||||
Avatar::renderAttachments(renderMode);
|
||||
return;
|
||||
}
|
||||
const FBXGeometry& geometry = _skeletonModel.getGeometry()->getFBXGeometry();
|
||||
QString headJointName = (geometry.headJointIndex == -1) ? QString() : geometry.joints.at(geometry.headJointIndex).name;
|
||||
Model::RenderMode modelRenderMode = (renderMode == SHADOW_RENDER_MODE) ?
|
||||
Model::SHADOW_RENDER_MODE : Model::DEFAULT_RENDER_MODE;
|
||||
for (int i = 0; i < _attachmentData.size(); i++) {
|
||||
const QString& jointName = _attachmentData.at(i).jointName;
|
||||
if (jointName != headJointName && jointName != "Head") {
|
||||
_attachmentModels.at(i)->render(1.0f, modelRenderMode);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void MyAvatar::setCollisionGroups(quint32 collisionGroups) {
|
||||
Avatar::setCollisionGroups(collisionGroups & VALID_COLLISION_GROUPS);
|
||||
Menu* menu = Menu::getInstance();
|
||||
|
|
|
@ -38,7 +38,7 @@ public:
|
|||
void reset();
|
||||
void update(float deltaTime);
|
||||
void simulate(float deltaTime);
|
||||
void updateFromGyros(float deltaTime);
|
||||
void updateFromTrackers(float deltaTime);
|
||||
void moveWithLean();
|
||||
|
||||
void render(const glm::vec3& cameraPosition, RenderMode renderMode = NORMAL_RENDER_MODE);
|
||||
|
@ -66,6 +66,9 @@ public:
|
|||
void saveData(QSettings* settings);
|
||||
void loadData(QSettings* settings);
|
||||
|
||||
void saveAttachmentData(const AttachmentData& attachment) const;
|
||||
AttachmentData loadAttachmentData(const QUrl& modelURL, const QString& jointName = QString()) const;
|
||||
|
||||
// Set what driving keys are being pressed to control thrust levels
|
||||
void setDriveKeys(int key, float val) { _driveKeys[key] = val; };
|
||||
bool getDriveKeys(int key) { return _driveKeys[key] != 0.f; };
|
||||
|
@ -86,7 +89,12 @@ public:
|
|||
virtual void clearJointData(int index);
|
||||
virtual void setFaceModelURL(const QUrl& faceModelURL);
|
||||
virtual void setSkeletonModelURL(const QUrl& skeletonModelURL);
|
||||
|
||||
virtual void setAttachmentData(const QVector<AttachmentData>& attachmentData);
|
||||
|
||||
virtual void attach(const QString& modelURL, const QString& jointName = QString(),
|
||||
const glm::vec3& translation = glm::vec3(), const glm::quat& rotation = glm::quat(), float scale = 1.0f,
|
||||
bool allowDuplicates = false, bool useSaved = true);
|
||||
|
||||
virtual void setCollisionGroups(quint32 collisionGroups);
|
||||
|
||||
void setMotionBehaviorsByScript(quint32 flags);
|
||||
|
@ -113,6 +121,9 @@ public slots:
|
|||
signals:
|
||||
void transformChanged();
|
||||
|
||||
protected:
|
||||
virtual void renderAttachments(RenderMode renderMode);
|
||||
|
||||
private:
|
||||
bool _mousePressed;
|
||||
float _bodyPitchDelta; // degrees
|
||||
|
@ -125,7 +136,7 @@ private:
|
|||
|
||||
bool _wasPushing;
|
||||
bool _isPushing;
|
||||
bool _wasStuck;
|
||||
float _trapDuration; // seconds that avatar has been trapped by collisions
|
||||
glm::vec3 _thrust; // final acceleration from outside sources for the current frame
|
||||
|
||||
glm::vec3 _motorVelocity; // intended velocity of avatar motion
|
||||
|
@ -147,7 +158,6 @@ private:
|
|||
float computeMotorTimescale();
|
||||
void applyMotor(float deltaTime);
|
||||
void applyThrust(float deltaTime);
|
||||
void updateHandMovementAndTouching(float deltaTime);
|
||||
void updateCollisionWithAvatars(float deltaTime);
|
||||
void updateCollisionWithEnvironment(float deltaTime, float radius);
|
||||
void updateCollisionWithVoxels(float deltaTime, float radius);
|
||||
|
|
|
@ -33,35 +33,45 @@ void SkeletonModel::simulate(float deltaTime, bool fullUpdate) {
|
|||
return; // only simulate for own avatar
|
||||
}
|
||||
|
||||
// find the left and rightmost active Leap palms
|
||||
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_PERIOD = 1.f; // seconds
|
||||
float handRestorePercent = glm::clamp(deltaTime / HAND_RESTORATION_PERIOD, 0.f, 1.f);
|
||||
|
||||
const FBXGeometry& geometry = _geometry->getFBXGeometry();
|
||||
const float HAND_RESTORATION_RATE = 0.25f;
|
||||
if (leftPalmIndex == -1) {
|
||||
// no Leap data; set hands from mouse
|
||||
// palms are not yet set, use mouse
|
||||
if (_owningAvatar->getHandState() == HAND_STATE_NULL) {
|
||||
restoreRightHandPosition(handRestorePercent);
|
||||
restoreRightHandPosition(HAND_RESTORATION_RATE);
|
||||
} else {
|
||||
applyHandPosition(geometry.rightHandJointIndex, _owningAvatar->getHandPosition());
|
||||
}
|
||||
restoreLeftHandPosition(handRestorePercent);
|
||||
restoreLeftHandPosition(HAND_RESTORATION_RATE);
|
||||
|
||||
} else if (leftPalmIndex == rightPalmIndex) {
|
||||
// right hand only
|
||||
applyPalmData(geometry.rightHandJointIndex, geometry.rightFingerJointIndices, geometry.rightFingertipJointIndices,
|
||||
hand->getPalms()[leftPalmIndex]);
|
||||
restoreLeftHandPosition(handRestorePercent);
|
||||
applyPalmData(geometry.rightHandJointIndex, hand->getPalms()[leftPalmIndex]);
|
||||
restoreLeftHandPosition(HAND_RESTORATION_RATE);
|
||||
|
||||
} else {
|
||||
applyPalmData(geometry.leftHandJointIndex, geometry.leftFingerJointIndices, geometry.leftFingertipJointIndices,
|
||||
hand->getPalms()[leftPalmIndex]);
|
||||
applyPalmData(geometry.rightHandJointIndex, geometry.rightFingerJointIndices, geometry.rightFingertipJointIndices,
|
||||
hand->getPalms()[rightPalmIndex]);
|
||||
applyPalmData(geometry.leftHandJointIndex, hand->getPalms()[leftPalmIndex]);
|
||||
applyPalmData(geometry.rightHandJointIndex, hand->getPalms()[rightPalmIndex]);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -141,8 +151,7 @@ void SkeletonModel::applyHandPosition(int jointIndex, const glm::vec3& position)
|
|||
applyRotationDelta(jointIndex, rotationBetween(handRotation * glm::vec3(-sign, 0.0f, 0.0f), forearmVector));
|
||||
}
|
||||
|
||||
void SkeletonModel::applyPalmData(int jointIndex, const QVector<int>& fingerJointIndices,
|
||||
const QVector<int>& fingertipJointIndices, PalmData& palm) {
|
||||
void SkeletonModel::applyPalmData(int jointIndex, PalmData& palm) {
|
||||
if (jointIndex == -1) {
|
||||
return;
|
||||
}
|
||||
|
@ -153,7 +162,7 @@ void SkeletonModel::applyPalmData(int jointIndex, const QVector<int>& fingerJoin
|
|||
return;
|
||||
}
|
||||
|
||||
// rotate palm to align with palm direction
|
||||
// rotate palm to align with its normal (normal points out of hand's palm)
|
||||
glm::quat palmRotation;
|
||||
if (Menu::getInstance()->isOptionChecked(MenuOption::AlignForearmsWithWrists)) {
|
||||
getJointRotation(parentJointIndex, palmRotation, true);
|
||||
|
@ -162,27 +171,9 @@ void SkeletonModel::applyPalmData(int jointIndex, const QVector<int>& fingerJoin
|
|||
}
|
||||
palmRotation = rotationBetween(palmRotation * geometry.palmDirection, palm.getNormal()) * palmRotation;
|
||||
|
||||
// sort the finger indices by raw x, get the average direction
|
||||
QVector<IndexValue> fingerIndices;
|
||||
glm::vec3 direction;
|
||||
for (size_t i = 0; i < palm.getNumFingers(); i++) {
|
||||
glm::vec3 fingerVector = palm.getFingers()[i].getTipPosition() - palm.getPosition();
|
||||
float length = glm::length(fingerVector);
|
||||
if (length > EPSILON) {
|
||||
direction += fingerVector / length;
|
||||
}
|
||||
fingerVector = glm::inverse(palmRotation) * fingerVector * -sign;
|
||||
IndexValue indexValue = { (int)i, atan2f(fingerVector.z, fingerVector.x) };
|
||||
fingerIndices.append(indexValue);
|
||||
}
|
||||
qSort(fingerIndices.begin(), fingerIndices.end());
|
||||
|
||||
// rotate forearm according to average finger direction
|
||||
float directionLength = glm::length(direction);
|
||||
const unsigned int MIN_ROTATION_FINGERS = 3;
|
||||
if (directionLength > EPSILON && palm.getNumFingers() >= MIN_ROTATION_FINGERS) {
|
||||
palmRotation = rotationBetween(palmRotation * glm::vec3(-sign, 0.0f, 0.0f), direction) * palmRotation;
|
||||
}
|
||||
// rotate palm to align with finger direction
|
||||
glm::vec3 direction = palm.getFingerDirection();
|
||||
palmRotation = rotationBetween(palmRotation * glm::vec3(-sign, 0.0f, 0.0f), direction) * palmRotation;
|
||||
|
||||
// set hand position, rotation
|
||||
if (Menu::getInstance()->isOptionChecked(MenuOption::AlignForearmsWithWrists)) {
|
||||
|
@ -209,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
|
||||
|
|
|
@ -39,8 +39,7 @@ protected:
|
|||
|
||||
void applyHandPosition(int jointIndex, const glm::vec3& position);
|
||||
|
||||
void applyPalmData(int jointIndex, const QVector<int>& fingerJointIndices,
|
||||
const QVector<int>& fingertipJointIndices, PalmData& palm);
|
||||
void applyPalmData(int jointIndex, PalmData& palm);
|
||||
|
||||
/// Updates the state of the joint at the specified index.
|
||||
virtual void updateJointState(int index);
|
||||
|
|
117
interface/src/devices/PrioVR.cpp
Normal file
117
interface/src/devices/PrioVR.cpp
Normal 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), ×tamp);
|
||||
|
||||
// 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
|
||||
}
|
62
interface/src/devices/PrioVR.h
Normal file
62
interface/src/devices/PrioVR.h
Normal 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
|
|
@ -22,14 +22,15 @@ const int CALIBRATION_STATE_Z = 3;
|
|||
const int CALIBRATION_STATE_COMPLETE = 4;
|
||||
|
||||
// default (expected) location of neck in sixense space
|
||||
const float NECK_X = 250.f; // millimeters
|
||||
const float NECK_Y = 300.f; // millimeters
|
||||
const float NECK_Z = 300.f; // millimeters
|
||||
const float NECK_X = 0.25f; // meters
|
||||
const float NECK_Y = 0.3f; // meters
|
||||
const float NECK_Z = 0.3f; // meters
|
||||
#endif
|
||||
|
||||
SixenseManager::SixenseManager() {
|
||||
#ifdef HAVE_SIXENSE
|
||||
_lastMovement = 0;
|
||||
_amountMoved = glm::vec3(0.0f);
|
||||
|
||||
_calibrationState = CALIBRATION_STATE_IDLE;
|
||||
// By default we assume the _neckBase (in orb frame) is as high above the orb
|
||||
|
@ -106,7 +107,10 @@ void SixenseManager::update(float deltaTime) {
|
|||
palm->setTrigger(data->trigger);
|
||||
palm->setJoystick(data->joystick_x, data->joystick_y);
|
||||
|
||||
// NOTE: Sixense API returns pos data in millimeters but we IMMEDIATELY convert to meters.
|
||||
glm::vec3 position(data->pos[0], data->pos[1], data->pos[2]);
|
||||
position *= METERS_PER_MILLIMETER;
|
||||
|
||||
// Transform the measured position into body frame.
|
||||
glm::vec3 neck = _neckBase;
|
||||
// Zeroing y component of the "neck" effectively raises the measured position a little bit.
|
||||
|
@ -116,41 +120,37 @@ void SixenseManager::update(float deltaTime) {
|
|||
// Rotation of Palm
|
||||
glm::quat rotation(data->rot_quat[3], -data->rot_quat[0], data->rot_quat[1], -data->rot_quat[2]);
|
||||
rotation = glm::angleAxis(PI, glm::vec3(0.f, 1.f, 0.f)) * _orbRotation * rotation;
|
||||
const glm::vec3 PALM_VECTOR(0.0f, -1.0f, 0.0f);
|
||||
glm::vec3 newNormal = rotation * PALM_VECTOR;
|
||||
palm->setRawNormal(newNormal);
|
||||
palm->setRawRotation(rotation);
|
||||
|
||||
// Compute current velocity from position change
|
||||
glm::vec3 rawVelocity = (position - palm->getRawPosition()) / deltaTime / 1000.f;
|
||||
glm::vec3 rawVelocity;
|
||||
if (deltaTime > 0.f) {
|
||||
rawVelocity = (position - palm->getRawPosition()) / deltaTime;
|
||||
} else {
|
||||
rawVelocity = glm::vec3(0.0f);
|
||||
}
|
||||
palm->setRawVelocity(rawVelocity); // meters/sec
|
||||
palm->setRawPosition(position);
|
||||
|
||||
// use the velocity to determine whether there's any movement (if the hand isn't new)
|
||||
const float MOVEMENT_SPEED_THRESHOLD = 0.05f;
|
||||
if (glm::length(rawVelocity) > MOVEMENT_SPEED_THRESHOLD && foundHand) {
|
||||
const float MOVEMENT_DISTANCE_THRESHOLD = 0.003f;
|
||||
_amountMoved += rawVelocity * deltaTime;
|
||||
if (glm::length(_amountMoved) > MOVEMENT_DISTANCE_THRESHOLD && foundHand) {
|
||||
_lastMovement = usecTimestampNow();
|
||||
_amountMoved = glm::vec3(0.0f);
|
||||
}
|
||||
|
||||
// initialize the "finger" based on the direction
|
||||
FingerData finger(palm, hand);
|
||||
finger.setActive(true);
|
||||
finger.setRawRootPosition(position);
|
||||
const float FINGER_LENGTH = 300.0f; // Millimeters
|
||||
// Store the one fingertip in the palm structure so we can track velocity
|
||||
const float FINGER_LENGTH = 0.3f; // meters
|
||||
const glm::vec3 FINGER_VECTOR(0.0f, 0.0f, FINGER_LENGTH);
|
||||
const glm::vec3 newTipPosition = position + rotation * FINGER_VECTOR;
|
||||
finger.setRawTipPosition(position + rotation * FINGER_VECTOR);
|
||||
|
||||
// Store the one fingertip in the palm structure so we can track velocity
|
||||
glm::vec3 oldTipPosition = palm->getTipRawPosition();
|
||||
palm->setTipVelocity((newTipPosition - oldTipPosition) / deltaTime / 1000.f);
|
||||
if (deltaTime > 0.f) {
|
||||
palm->setTipVelocity((newTipPosition - oldTipPosition) / deltaTime);
|
||||
} else {
|
||||
palm->setTipVelocity(glm::vec3(0.f));
|
||||
}
|
||||
palm->setTipPosition(newTipPosition);
|
||||
|
||||
// three fingers indicates to the skeleton that we have enough data to determine direction
|
||||
palm->getFingers().clear();
|
||||
palm->getFingers().push_back(finger);
|
||||
palm->getFingers().push_back(finger);
|
||||
palm->getFingers().push_back(finger);
|
||||
}
|
||||
|
||||
if (numActiveControllers == 2) {
|
||||
|
@ -158,8 +158,8 @@ void SixenseManager::update(float deltaTime) {
|
|||
}
|
||||
|
||||
// if the controllers haven't been moved in a while, disable
|
||||
const unsigned int MOVEMENT_DISABLE_DURATION = 30 * 1000 * 1000;
|
||||
if (usecTimestampNow() - _lastMovement > MOVEMENT_DISABLE_DURATION) {
|
||||
const unsigned int MOVEMENT_DISABLE_SECONDS = 3;
|
||||
if (usecTimestampNow() - _lastMovement > (MOVEMENT_DISABLE_SECONDS * USECS_PER_SECOND)) {
|
||||
for (std::vector<PalmData>::iterator it = hand->getPalms().begin(); it != hand->getPalms().end(); it++) {
|
||||
it->setActive(false);
|
||||
}
|
||||
|
@ -176,8 +176,8 @@ void SixenseManager::update(float deltaTime) {
|
|||
// (4) move arms a bit forward (Z)
|
||||
// (5) release BUTTON_FWD on both hands
|
||||
|
||||
const float MINIMUM_ARM_REACH = 300.f; // millimeters
|
||||
const float MAXIMUM_NOISE_LEVEL = 50.f; // millimeters
|
||||
const float MINIMUM_ARM_REACH = 0.3f; // meters
|
||||
const float MAXIMUM_NOISE_LEVEL = 0.05f; // meters
|
||||
const quint64 LOCK_DURATION = USECS_PER_SECOND / 4; // time for lock to be acquired
|
||||
|
||||
void SixenseManager::updateCalibration(const sixenseControllerData* controllers) {
|
||||
|
@ -217,14 +217,17 @@ void SixenseManager::updateCalibration(const sixenseControllerData* controllers)
|
|||
return;
|
||||
}
|
||||
|
||||
// NOTE: Sixense API returns pos data in millimeters but we IMMEDIATELY convert to meters.
|
||||
const float* pos = dataLeft->pos;
|
||||
glm::vec3 positionLeft(pos[0], pos[1], pos[2]);
|
||||
positionLeft *= METERS_PER_MILLIMETER;
|
||||
pos = dataRight->pos;
|
||||
glm::vec3 positionRight(pos[0], pos[1], pos[2]);
|
||||
positionRight *= METERS_PER_MILLIMETER;
|
||||
|
||||
if (_calibrationState == CALIBRATION_STATE_IDLE) {
|
||||
float reach = glm::distance(positionLeft, positionRight);
|
||||
if (reach > 2.f * MINIMUM_ARM_REACH) {
|
||||
if (reach > 2.0f * MINIMUM_ARM_REACH) {
|
||||
qDebug("started: sixense calibration");
|
||||
_averageLeft = positionLeft;
|
||||
_averageRight = positionRight;
|
||||
|
|
|
@ -64,6 +64,7 @@ private:
|
|||
|
||||
#endif
|
||||
quint64 _lastMovement;
|
||||
glm::vec3 _amountMoved;
|
||||
};
|
||||
|
||||
#endif // hifi_SixenseManager_h
|
||||
|
|
|
@ -41,7 +41,11 @@ Visage::Visage() :
|
|||
_headOrigin(DEFAULT_HEAD_ORIGIN) {
|
||||
|
||||
#ifdef HAVE_VISAGE
|
||||
#ifdef WIN32
|
||||
QByteArray licensePath = Application::resourcesPath().toLatin1() + "visage";
|
||||
#else
|
||||
QByteArray licensePath = Application::resourcesPath().toLatin1() + "visage/license.vlc";
|
||||
#endif
|
||||
initializeLicenseManager(licensePath.data());
|
||||
_tracker = new VisageTracker2(Application::resourcesPath().toLatin1() + "visage/tracker.cfg");
|
||||
_data = new FaceData();
|
||||
|
@ -51,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
|
||||
}
|
||||
|
|
|
@ -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,17 +48,27 @@ 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;
|
||||
}
|
||||
|
@ -63,7 +78,7 @@ void ModelTreeRenderer::renderElement(OctreeElement* element, RenderArgs* args)
|
|||
// 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 +154,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);
|
||||
|
@ -156,7 +171,7 @@ void ModelTreeRenderer::renderElement(OctreeElement* element, RenderArgs* args)
|
|||
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 +182,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?
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -506,6 +506,15 @@ void GeometryReader::run() {
|
|||
_reply->deleteLater();
|
||||
}
|
||||
|
||||
void NetworkGeometry::init() {
|
||||
_mapping = QVariantHash();
|
||||
_geometry = FBXGeometry();
|
||||
_meshes.clear();
|
||||
_lods.clear();
|
||||
_request.setUrl(_url);
|
||||
Resource::init();
|
||||
}
|
||||
|
||||
void NetworkGeometry::downloadFinished(QNetworkReply* reply) {
|
||||
QUrl url = reply->url();
|
||||
if (url.path().toLower().endsWith(".fst")) {
|
||||
|
|
|
@ -96,6 +96,7 @@ public:
|
|||
|
||||
protected:
|
||||
|
||||
virtual void init();
|
||||
virtual void downloadFinished(QNetworkReply* reply);
|
||||
virtual void reinsert();
|
||||
|
||||
|
|
|
@ -128,6 +128,7 @@ QVector<Model::JointState> Model::createJointStates(const FBXGeometry& geometry)
|
|||
jointIsSet.fill(false, numJoints);
|
||||
int numJointsSet = 0;
|
||||
int lastNumJointsSet = -1;
|
||||
glm::mat4 baseTransform = glm::mat4_cast(_rotation) * glm::scale(_scale) * glm::translate(_offset);
|
||||
while (numJointsSet < numJoints && numJointsSet != lastNumJointsSet) {
|
||||
lastNumJointsSet = numJointsSet;
|
||||
for (int i = 0; i < numJoints; ++i) {
|
||||
|
@ -138,7 +139,6 @@ QVector<Model::JointState> Model::createJointStates(const FBXGeometry& geometry)
|
|||
const FBXJoint& joint = geometry.joints[i];
|
||||
int parentIndex = joint.parentIndex;
|
||||
if (parentIndex == -1) {
|
||||
glm::mat4 baseTransform = glm::mat4_cast(_rotation) * glm::scale(_scale) * glm::translate(_offset);
|
||||
glm::quat combinedRotation = joint.preRotation * state.rotation * joint.postRotation;
|
||||
state.transform = baseTransform * geometry.offset * glm::translate(state.translation) * joint.preTransform *
|
||||
glm::mat4_cast(combinedRotation) * joint.postTransform;
|
||||
|
@ -319,6 +319,9 @@ bool Model::updateGeometry() {
|
|||
_jointStates = createJointStates(fbxGeometry);
|
||||
needToRebuild = true;
|
||||
}
|
||||
} else if (!geometry->isLoaded()) {
|
||||
deleteGeometry();
|
||||
_dilatedTextures.clear();
|
||||
}
|
||||
_geometry->setLoadPriority(this, -_lodDistance);
|
||||
_geometry->ensureLoading();
|
||||
|
@ -430,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;
|
||||
}
|
||||
|
||||
|
@ -438,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 {
|
||||
|
@ -573,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];
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -366,10 +366,10 @@ void NetworkTexture::setImage(const QImage& image, bool translucent) {
|
|||
imageLoaded(image);
|
||||
glBindTexture(GL_TEXTURE_2D, getID());
|
||||
if (image.hasAlphaChannel()) {
|
||||
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, image.width(), image.height(), 1,
|
||||
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, image.width(), image.height(), 0,
|
||||
GL_BGRA, GL_UNSIGNED_BYTE, image.constBits());
|
||||
} else {
|
||||
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, image.width(), image.height(), 1,
|
||||
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, image.width(), image.height(), 0,
|
||||
GL_RGB, GL_UNSIGNED_BYTE, image.constBits());
|
||||
}
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
|
||||
|
@ -404,10 +404,10 @@ QSharedPointer<Texture> DilatableNetworkTexture::getDilatedTexture(float dilatio
|
|||
|
||||
glBindTexture(GL_TEXTURE_2D, texture->getID());
|
||||
if (dilatedImage.hasAlphaChannel()) {
|
||||
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, dilatedImage.width(), dilatedImage.height(), 1,
|
||||
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, dilatedImage.width(), dilatedImage.height(), 0,
|
||||
GL_BGRA, GL_UNSIGNED_BYTE, dilatedImage.constBits());
|
||||
} else {
|
||||
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, dilatedImage.width(), dilatedImage.height(), 1,
|
||||
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, dilatedImage.width(), dilatedImage.height(), 0,
|
||||
GL_RGB, GL_UNSIGNED_BYTE, dilatedImage.constBits());
|
||||
}
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
|
||||
|
|
|
@ -200,7 +200,7 @@ glm::vec3 ControllerScriptingInterface::getSpatialControlNormal(int controlIndex
|
|||
case PALM_SPATIALCONTROL:
|
||||
return palmData->getNormal();
|
||||
case TIP_SPATIALCONTROL:
|
||||
return palmData->getNormal(); // currently the tip doesn't have a unique normal, use the palm normal
|
||||
return palmData->getFingerDirection();
|
||||
}
|
||||
}
|
||||
return glm::vec3(0); // bad index
|
||||
|
|
|
@ -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());
|
||||
|
|
|
@ -76,27 +76,29 @@ void AttachmentsDialog::addAttachment(const AttachmentData& data) {
|
|||
_attachments->insertWidget(_attachments->count() - 1, new AttachmentPanel(this, data));
|
||||
}
|
||||
|
||||
static QDoubleSpinBox* createTranslationBox(AttachmentsDialog* dialog, float value) {
|
||||
static QDoubleSpinBox* createTranslationBox(AttachmentPanel* panel, float value) {
|
||||
QDoubleSpinBox* box = new QDoubleSpinBox();
|
||||
box->setSingleStep(0.01);
|
||||
box->setMinimum(-FLT_MAX);
|
||||
box->setMaximum(FLT_MAX);
|
||||
box->setValue(value);
|
||||
dialog->connect(box, SIGNAL(valueChanged(double)), SLOT(updateAttachmentData()));
|
||||
panel->connect(box, SIGNAL(valueChanged(double)), SLOT(updateAttachmentData()));
|
||||
return box;
|
||||
}
|
||||
|
||||
static QDoubleSpinBox* createRotationBox(AttachmentsDialog* dialog, float value) {
|
||||
static QDoubleSpinBox* createRotationBox(AttachmentPanel* panel, float value) {
|
||||
QDoubleSpinBox* box = new QDoubleSpinBox();
|
||||
box->setMinimum(-180.0);
|
||||
box->setMaximum(180.0);
|
||||
box->setWrapping(true);
|
||||
box->setValue(value);
|
||||
dialog->connect(box, SIGNAL(valueChanged(double)), SLOT(updateAttachmentData()));
|
||||
panel->connect(box, SIGNAL(valueChanged(double)), SLOT(updateAttachmentData()));
|
||||
return box;
|
||||
}
|
||||
|
||||
AttachmentPanel::AttachmentPanel(AttachmentsDialog* dialog, const AttachmentData& data) {
|
||||
AttachmentPanel::AttachmentPanel(AttachmentsDialog* dialog, const AttachmentData& data) :
|
||||
_dialog(dialog),
|
||||
_applying(false) {
|
||||
setFrameStyle(QFrame::StyledPanel);
|
||||
|
||||
QFormLayout* layout = new QFormLayout();
|
||||
|
@ -107,7 +109,7 @@ AttachmentPanel::AttachmentPanel(AttachmentsDialog* dialog, const AttachmentData
|
|||
layout->addRow("Model URL:", urlBox);
|
||||
urlBox->addWidget(_modelURL = new QLineEdit(data.modelURL.toString()), 1);
|
||||
_modelURL->setText(data.modelURL.toString());
|
||||
dialog->connect(_modelURL, SIGNAL(returnPressed()), SLOT(updateAttachmentData()));
|
||||
connect(_modelURL, SIGNAL(returnPressed()), SLOT(modelURLChanged()));
|
||||
QPushButton* chooseURL = new QPushButton("Choose");
|
||||
urlBox->addWidget(chooseURL);
|
||||
connect(chooseURL, SIGNAL(clicked(bool)), SLOT(chooseModelURL()));
|
||||
|
@ -120,26 +122,26 @@ AttachmentPanel::AttachmentPanel(AttachmentsDialog* dialog, const AttachmentData
|
|||
}
|
||||
}
|
||||
_jointName->setCurrentText(data.jointName);
|
||||
dialog->connect(_jointName, SIGNAL(currentIndexChanged(int)), SLOT(updateAttachmentData()));
|
||||
connect(_jointName, SIGNAL(currentIndexChanged(int)), SLOT(jointNameChanged()));
|
||||
|
||||
QHBoxLayout* translationBox = new QHBoxLayout();
|
||||
translationBox->addWidget(_translationX = createTranslationBox(dialog, data.translation.x));
|
||||
translationBox->addWidget(_translationY = createTranslationBox(dialog, data.translation.y));
|
||||
translationBox->addWidget(_translationZ = createTranslationBox(dialog, data.translation.z));
|
||||
translationBox->addWidget(_translationX = createTranslationBox(this, data.translation.x));
|
||||
translationBox->addWidget(_translationY = createTranslationBox(this, data.translation.y));
|
||||
translationBox->addWidget(_translationZ = createTranslationBox(this, data.translation.z));
|
||||
layout->addRow("Translation:", translationBox);
|
||||
|
||||
QHBoxLayout* rotationBox = new QHBoxLayout();
|
||||
glm::vec3 eulers = glm::degrees(safeEulerAngles(data.rotation));
|
||||
rotationBox->addWidget(_rotationX = createRotationBox(dialog, eulers.x));
|
||||
rotationBox->addWidget(_rotationY = createRotationBox(dialog, eulers.y));
|
||||
rotationBox->addWidget(_rotationZ = createRotationBox(dialog, eulers.z));
|
||||
rotationBox->addWidget(_rotationX = createRotationBox(this, eulers.x));
|
||||
rotationBox->addWidget(_rotationY = createRotationBox(this, eulers.y));
|
||||
rotationBox->addWidget(_rotationZ = createRotationBox(this, eulers.z));
|
||||
layout->addRow("Rotation:", rotationBox);
|
||||
|
||||
layout->addRow("Scale:", _scale = new QDoubleSpinBox());
|
||||
_scale->setSingleStep(0.01);
|
||||
_scale->setMaximum(FLT_MAX);
|
||||
_scale->setValue(data.scale);
|
||||
dialog->connect(_scale, SIGNAL(valueChanged(double)), SLOT(updateAttachmentData()));
|
||||
connect(_scale, SIGNAL(valueChanged(double)), SLOT(updateAttachmentData()));
|
||||
|
||||
QPushButton* remove = new QPushButton("Delete");
|
||||
layout->addRow(remove);
|
||||
|
@ -165,5 +167,62 @@ void AttachmentPanel::chooseModelURL() {
|
|||
|
||||
void AttachmentPanel::setModelURL(const QString& url) {
|
||||
_modelURL->setText(url);
|
||||
emit _modelURL->returnPressed();
|
||||
modelURLChanged();
|
||||
}
|
||||
|
||||
void AttachmentPanel::modelURLChanged() {
|
||||
// check for saved attachment data
|
||||
if (_modelURL->text().isEmpty()) {
|
||||
_dialog->updateAttachmentData();
|
||||
return;
|
||||
}
|
||||
AttachmentData attachment = Application::getInstance()->getAvatar()->loadAttachmentData(_modelURL->text());
|
||||
if (attachment.isValid()) {
|
||||
_applying = true;
|
||||
_jointName->setCurrentText(attachment.jointName);
|
||||
applyAttachmentData(attachment);
|
||||
}
|
||||
_dialog->updateAttachmentData();
|
||||
}
|
||||
|
||||
void AttachmentPanel::jointNameChanged() {
|
||||
if (_applying) {
|
||||
return;
|
||||
}
|
||||
// check for saved attachment data specific to this joint
|
||||
if (_modelURL->text().isEmpty()) {
|
||||
_dialog->updateAttachmentData();
|
||||
return;
|
||||
}
|
||||
AttachmentData attachment = Application::getInstance()->getAvatar()->loadAttachmentData(
|
||||
_modelURL->text(), _jointName->currentText());
|
||||
if (attachment.isValid()) {
|
||||
applyAttachmentData(attachment);
|
||||
}
|
||||
updateAttachmentData();
|
||||
}
|
||||
|
||||
void AttachmentPanel::updateAttachmentData() {
|
||||
if (_applying) {
|
||||
return;
|
||||
}
|
||||
// save the attachment data under the model URL (if any)
|
||||
if (!_modelURL->text().isEmpty()) {
|
||||
Application::getInstance()->getAvatar()->saveAttachmentData(getAttachmentData());
|
||||
}
|
||||
_dialog->updateAttachmentData();
|
||||
}
|
||||
|
||||
void AttachmentPanel::applyAttachmentData(const AttachmentData& attachment) {
|
||||
_applying = true;
|
||||
_translationX->setValue(attachment.translation.x);
|
||||
_translationY->setValue(attachment.translation.y);
|
||||
_translationZ->setValue(attachment.translation.z);
|
||||
glm::vec3 eulers = glm::degrees(safeEulerAngles(attachment.rotation));
|
||||
_rotationX->setValue(eulers.x);
|
||||
_rotationY->setValue(eulers.y);
|
||||
_rotationZ->setValue(eulers.z);
|
||||
_scale->setValue(attachment.scale);
|
||||
_applying = false;
|
||||
_dialog->updateAttachmentData();
|
||||
}
|
||||
|
|
|
@ -60,9 +60,15 @@ private slots:
|
|||
|
||||
void chooseModelURL();
|
||||
void setModelURL(const QString& url);
|
||||
void modelURLChanged();
|
||||
void jointNameChanged();
|
||||
void updateAttachmentData();
|
||||
|
||||
private:
|
||||
|
||||
void applyAttachmentData(const AttachmentData& attachment);
|
||||
|
||||
AttachmentsDialog* _dialog;
|
||||
QLineEdit* _modelURL;
|
||||
QComboBox* _jointName;
|
||||
QDoubleSpinBox* _translationX;
|
||||
|
@ -72,6 +78,7 @@ private:
|
|||
QDoubleSpinBox* _rotationY;
|
||||
QDoubleSpinBox* _rotationZ;
|
||||
QDoubleSpinBox* _scale;
|
||||
bool _applying;
|
||||
};
|
||||
|
||||
#endif // hifi_AttachmentsDialog_h
|
||||
|
|
|
@ -30,9 +30,9 @@
|
|||
|
||||
const int NUM_MESSAGES_TO_TIME_STAMP = 20;
|
||||
|
||||
const QRegularExpression regexLinks("((?:(?:ftp)|(?:https?))://\\S+)");
|
||||
const QRegularExpression regexLinks("((?:(?:ftp)|(?:https?)|(?:hifi))://\\S+)");
|
||||
const QRegularExpression regexHifiLinks("([#@]\\S+)");
|
||||
const QString mentionSoundsPath("/sounds/mention/");
|
||||
const QString mentionSoundsPath("/mention-sounds/");
|
||||
const QString mentionRegex("@(\\b%1\\b)");
|
||||
|
||||
ChatWindow::ChatWindow(QWidget* parent) :
|
||||
|
|
|
@ -59,7 +59,7 @@ void OAuthWebViewHandler::displayWebviewForAuthorizationURL(const QUrl& authoriz
|
|||
_activeWebView = new QWebView;
|
||||
|
||||
// keep the window on top and delete it when it closes
|
||||
_activeWebView->setWindowFlags(Qt::WindowStaysOnTopHint);
|
||||
_activeWebView->setWindowFlags(Qt::Sheet | Qt::WindowStaysOnTopHint);
|
||||
_activeWebView->setAttribute(Qt::WA_DeleteOnClose);
|
||||
|
||||
qDebug() << "Displaying QWebView for OAuth authorization at" << authorizationURL.toString();
|
||||
|
|
|
@ -157,6 +157,10 @@ void RunningScriptsWidget::paintEvent(QPaintEvent* event) {
|
|||
painter.end();
|
||||
}
|
||||
|
||||
void RunningScriptsWidget::scriptStopped(const QString& scriptName) {
|
||||
_recentlyLoadedScripts.prepend(scriptName);
|
||||
}
|
||||
|
||||
void RunningScriptsWidget::stopScript(int row, int column) {
|
||||
if (column == 1) { // make sure the user has clicked on the close icon
|
||||
_lastStoppedScript = _runningScriptsTable->item(row, 0)->toolTip();
|
||||
|
@ -169,11 +173,6 @@ void RunningScriptsWidget::loadScript(int row, int column) {
|
|||
}
|
||||
|
||||
void RunningScriptsWidget::allScriptsStopped() {
|
||||
QStringList list = Application::getInstance()->getRunningScripts();
|
||||
for (int i = 0; i < list.size(); ++i) {
|
||||
_recentlyLoadedScripts.prepend(list.at(i));
|
||||
}
|
||||
|
||||
Application::getInstance()->stopAllScripts();
|
||||
}
|
||||
|
||||
|
|
|
@ -36,6 +36,7 @@ protected:
|
|||
virtual void paintEvent(QPaintEvent* event);
|
||||
|
||||
public slots:
|
||||
void scriptStopped(const QString& scriptName);
|
||||
void setBoundary(const QRect& rect);
|
||||
|
||||
private slots:
|
||||
|
|
|
@ -64,7 +64,7 @@ SnapshotMetaData* Snapshot::parseSnapshotData(QString snapshotPath) {
|
|||
return data;
|
||||
}
|
||||
|
||||
void Snapshot::saveSnapshot(QGLWidget* widget, Avatar* avatar) {
|
||||
QString Snapshot::saveSnapshot(QGLWidget* widget, Avatar* avatar) {
|
||||
QImage shot = widget->grabFrameBuffer();
|
||||
|
||||
glm::vec3 location = avatar->getPosition();
|
||||
|
@ -99,6 +99,8 @@ void Snapshot::saveSnapshot(QGLWidget* widget, Avatar* avatar) {
|
|||
|
||||
fileName.append(QString(FILENAME_PATH_FORMAT.arg(username, now.toString(DATETIME_FORMAT), formattedLocation)));
|
||||
shot.save(fileName, 0, 100);
|
||||
|
||||
return fileName;
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -41,7 +41,7 @@ private:
|
|||
class Snapshot {
|
||||
|
||||
public:
|
||||
static void saveSnapshot(QGLWidget* widget, Avatar* avatar);
|
||||
static QString saveSnapshot(QGLWidget* widget, Avatar* avatar);
|
||||
static SnapshotMetaData* parseSnapshotData(QString snapshotPath);
|
||||
};
|
||||
|
||||
|
|
198
interface/src/ui/SnapshotShareDialog.cpp
Normal file
198
interface/src/ui/SnapshotShareDialog.cpp
Normal file
|
@ -0,0 +1,198 @@
|
|||
//
|
||||
// SnapshotShareDialog.cpp
|
||||
// interface/src/ui
|
||||
//
|
||||
// Created by Stojce Slavkovski on 2/16/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 "SnapshotShareDialog.h"
|
||||
#include "AccountManager.h"
|
||||
|
||||
#include <QFile>
|
||||
#include <QHttpMultiPart>
|
||||
#include <QJsonArray>
|
||||
#include <QJsonDocument>
|
||||
#include <QMessageBox>
|
||||
#include <QUrlQuery>
|
||||
|
||||
|
||||
const int NARROW_SNAPSHOT_DIALOG_SIZE = 500;
|
||||
const int WIDE_SNAPSHOT_DIALOG_WIDTH = 650;
|
||||
const int SUCCESS_LABEL_HEIGHT = 140;
|
||||
|
||||
const QString FORUM_URL = "https://alphas.highfidelity.io";
|
||||
const QString FORUM_UPLOADS_URL = FORUM_URL + "/uploads";
|
||||
const QString FORUM_POST_URL = FORUM_URL + "/posts";
|
||||
const QString FORUM_REPLY_TO_TOPIC = "244";
|
||||
const QString FORUM_POST_TEMPLATE = "<img src='%1'/><p>%2</p>";
|
||||
const QString SHARE_DEFAULT_ERROR = "The server isn't responding. Please try again in a few minutes.";
|
||||
const QString SUCCESS_LABEL_TEMPLATE = "Success!!! Go check out your image ...<br/><a style='color:#333;text-decoration:none' href='%1'>%1</a>";
|
||||
|
||||
Q_DECLARE_METATYPE(QNetworkAccessManager::Operation)
|
||||
|
||||
SnapshotShareDialog::SnapshotShareDialog(QString fileName, QWidget* parent) :
|
||||
QDialog(parent),
|
||||
_fileName(fileName),
|
||||
_networkAccessManager(NULL)
|
||||
{
|
||||
|
||||
setAttribute(Qt::WA_DeleteOnClose);
|
||||
|
||||
_ui.setupUi(this);
|
||||
|
||||
QPixmap snaphsotPixmap(fileName);
|
||||
float snapshotRatio = static_cast<float>(snaphsotPixmap.size().width()) / snaphsotPixmap.size().height();
|
||||
|
||||
// narrow snapshot
|
||||
if (snapshotRatio > 1) {
|
||||
setFixedWidth(WIDE_SNAPSHOT_DIALOG_WIDTH);
|
||||
_ui.snapshotWidget->setFixedWidth(WIDE_SNAPSHOT_DIALOG_WIDTH);
|
||||
}
|
||||
|
||||
float labelRatio = static_cast<float>(_ui.snapshotWidget->size().width()) / _ui.snapshotWidget->size().height();
|
||||
|
||||
// set the same aspect ratio of label as of snapshot
|
||||
if (snapshotRatio > labelRatio) {
|
||||
int oldHeight = _ui.snapshotWidget->size().height();
|
||||
_ui.snapshotWidget->setFixedHeight((int) (_ui.snapshotWidget->size().width() / snapshotRatio));
|
||||
|
||||
// if height is less then original, resize the window as well
|
||||
if (_ui.snapshotWidget->size().height() < NARROW_SNAPSHOT_DIALOG_SIZE) {
|
||||
setFixedHeight(size().height() - (oldHeight - _ui.snapshotWidget->size().height()));
|
||||
}
|
||||
} else {
|
||||
_ui.snapshotWidget->setFixedWidth((int) (_ui.snapshotWidget->size().height() * snapshotRatio));
|
||||
}
|
||||
|
||||
_ui.snapshotWidget->setPixmap(snaphsotPixmap);
|
||||
_ui.snapshotWidget->adjustSize();
|
||||
}
|
||||
|
||||
void SnapshotShareDialog::accept() {
|
||||
uploadSnapshot();
|
||||
}
|
||||
|
||||
void SnapshotShareDialog::uploadSnapshot() {
|
||||
|
||||
if (AccountManager::getInstance().getAccountInfo().getDiscourseApiKey().isEmpty()) {
|
||||
QMessageBox::warning(this, "",
|
||||
"Your Discourse API key is missing, you cannot share snapshots. Please try to relog.");
|
||||
return;
|
||||
}
|
||||
|
||||
if (!_networkAccessManager) {
|
||||
_networkAccessManager = new QNetworkAccessManager(this);
|
||||
}
|
||||
|
||||
QHttpMultiPart* multiPart = new QHttpMultiPart(QHttpMultiPart::FormDataType);
|
||||
|
||||
QHttpPart apiKeyPart;
|
||||
apiKeyPart.setHeader(QNetworkRequest::ContentDispositionHeader, QVariant("form-data; name=\"api_key\""));
|
||||
apiKeyPart.setBody(AccountManager::getInstance().getAccountInfo().getDiscourseApiKey().toLatin1());
|
||||
|
||||
QFile* file = new QFile(_fileName);
|
||||
file->open(QIODevice::ReadOnly);
|
||||
|
||||
QHttpPart imagePart;
|
||||
imagePart.setHeader(QNetworkRequest::ContentTypeHeader, QVariant("image/jpeg"));
|
||||
imagePart.setHeader(QNetworkRequest::ContentDispositionHeader,
|
||||
QVariant("form-data; name=\"file\"; filename=\"" + file->fileName() +"\""));
|
||||
imagePart.setBodyDevice(file);
|
||||
file->setParent(multiPart); // we cannot delete the file now, so delete it with the multiPart
|
||||
|
||||
multiPart->append(apiKeyPart);
|
||||
multiPart->append(imagePart);
|
||||
|
||||
QUrl url(FORUM_UPLOADS_URL);
|
||||
QNetworkRequest request(url);
|
||||
|
||||
QNetworkReply* reply = _networkAccessManager->post(request, multiPart);
|
||||
|
||||
|
||||
connect(reply, &QNetworkReply::finished, this, &SnapshotShareDialog::uploadRequestFinished);
|
||||
|
||||
QEventLoop loop;
|
||||
connect(reply, &QNetworkReply::finished, &loop, &QEventLoop::quit);
|
||||
loop.exec();
|
||||
}
|
||||
|
||||
void SnapshotShareDialog::sendForumPost(QString snapshotPath) {
|
||||
|
||||
if (!_networkAccessManager) {
|
||||
_networkAccessManager = new QNetworkAccessManager(this);
|
||||
}
|
||||
|
||||
// post to Discourse forum
|
||||
QNetworkRequest request;
|
||||
QUrl forumUrl(FORUM_POST_URL);
|
||||
|
||||
QUrlQuery query;
|
||||
query.addQueryItem("api_key", AccountManager::getInstance().getAccountInfo().getDiscourseApiKey());
|
||||
query.addQueryItem("topic_id", FORUM_REPLY_TO_TOPIC);
|
||||
query.addQueryItem("raw", FORUM_POST_TEMPLATE.arg(snapshotPath, _ui.textEdit->toPlainText()));
|
||||
forumUrl.setQuery(query);
|
||||
|
||||
QByteArray postData = forumUrl.toEncoded(QUrl::RemoveFragment);
|
||||
request.setUrl(forumUrl);
|
||||
request.setHeader(QNetworkRequest::ContentTypeHeader, "application/x-www-form-urlencoded");
|
||||
|
||||
QNetworkReply* requestReply = _networkAccessManager->post(request, postData);
|
||||
connect(requestReply, &QNetworkReply::finished, this, &SnapshotShareDialog::postRequestFinished);
|
||||
|
||||
QEventLoop loop;
|
||||
connect(requestReply, &QNetworkReply::finished, &loop, &QEventLoop::quit);
|
||||
loop.exec();
|
||||
}
|
||||
|
||||
void SnapshotShareDialog::postRequestFinished() {
|
||||
|
||||
QNetworkReply* requestReply = reinterpret_cast<QNetworkReply*>(sender());
|
||||
QJsonDocument jsonResponse = QJsonDocument::fromJson(requestReply->readAll());
|
||||
const QJsonObject& responseObject = jsonResponse.object();
|
||||
|
||||
if (responseObject.contains("id")) {
|
||||
_ui.textEdit->setHtml("");
|
||||
const QString urlTemplate = "%1/t/%2/%3/%4";
|
||||
QString link = urlTemplate.arg(FORUM_URL,
|
||||
responseObject["topic_slug"].toString(),
|
||||
QString::number(responseObject["topic_id"].toDouble()),
|
||||
QString::number(responseObject["post_number"].toDouble()));
|
||||
|
||||
_ui.successLabel->setText(SUCCESS_LABEL_TEMPLATE.arg(link));
|
||||
_ui.successLabel->setFixedHeight(SUCCESS_LABEL_HEIGHT);
|
||||
|
||||
// hide input widgets
|
||||
_ui.shareButton->hide();
|
||||
_ui.textEdit->hide();
|
||||
_ui.labelNotes->hide();
|
||||
|
||||
} else {
|
||||
QString errorMessage(SHARE_DEFAULT_ERROR);
|
||||
if (responseObject.contains("errors")) {
|
||||
QJsonArray errorArray = responseObject["errors"].toArray();
|
||||
if (!errorArray.first().toString().isEmpty()) {
|
||||
errorMessage = errorArray.first().toString();
|
||||
}
|
||||
}
|
||||
QMessageBox::warning(this, "", errorMessage);
|
||||
}
|
||||
}
|
||||
|
||||
void SnapshotShareDialog::uploadRequestFinished() {
|
||||
|
||||
QNetworkReply* requestReply = reinterpret_cast<QNetworkReply*>(sender());
|
||||
QJsonDocument jsonResponse = QJsonDocument::fromJson(requestReply->readAll());
|
||||
const QJsonObject& responseObject = jsonResponse.object();
|
||||
|
||||
if (responseObject.contains("url")) {
|
||||
sendForumPost(responseObject["url"].toString());
|
||||
} else {
|
||||
QMessageBox::warning(this, "", SHARE_DEFAULT_ERROR);
|
||||
}
|
||||
|
||||
delete requestReply;
|
||||
}
|
41
interface/src/ui/SnapshotShareDialog.h
Normal file
41
interface/src/ui/SnapshotShareDialog.h
Normal file
|
@ -0,0 +1,41 @@
|
|||
//
|
||||
// SnapshotShareDialog.h
|
||||
// interface/src/ui
|
||||
//
|
||||
// Created by Stojce Slavkovski on 2/16/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_snapshotShareDialog
|
||||
#define hifi_snapshotShareDialog
|
||||
|
||||
#include "ui_shareSnapshot.h"
|
||||
|
||||
#include <QNetworkAccessManager>
|
||||
#include <QNetworkReply>
|
||||
#include <QUrl>
|
||||
|
||||
class SnapshotShareDialog : public QDialog {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
SnapshotShareDialog(QString fileName, QWidget* parent = 0);
|
||||
|
||||
private:
|
||||
QString _fileName;
|
||||
QNetworkAccessManager* _networkAccessManager;
|
||||
Ui_SnapshotShareDialog _ui;
|
||||
|
||||
void uploadSnapshot();
|
||||
void sendForumPost(QString snapshotPath);
|
||||
|
||||
private slots:
|
||||
void uploadRequestFinished();
|
||||
void postRequestFinished();
|
||||
void accept();
|
||||
};
|
||||
|
||||
#endif // hifi_snapshotShareDialog
|
|
@ -1711,38 +1711,6 @@ bool VoxelSystem::inspectForExteriorOcclusionsOperation(OctreeElement* element,
|
|||
return true;
|
||||
}
|
||||
|
||||
|
||||
void VoxelSystem::cullSharedFaces() {
|
||||
|
||||
if (Menu::getInstance()->isOptionChecked(MenuOption::CullSharedFaces)) {
|
||||
_useVoxelShader = false;
|
||||
_usePrimitiveRenderer = true;
|
||||
inspectForOcclusions();
|
||||
} else {
|
||||
_usePrimitiveRenderer = false;
|
||||
clearAllNodesBufferIndex();
|
||||
}
|
||||
_writeRenderFullVBO = true;
|
||||
_tree->setDirtyBit();
|
||||
setupNewVoxelsForDrawing();
|
||||
}
|
||||
|
||||
void VoxelSystem::showCulledSharedFaces() {
|
||||
|
||||
_tree->lockForRead();
|
||||
if (Menu::getInstance()->isOptionChecked(MenuOption::ShowCulledSharedFaces)) {
|
||||
_showCulledSharedFaces = true;
|
||||
} else {
|
||||
_showCulledSharedFaces = false;
|
||||
}
|
||||
_tree->unlock();
|
||||
if (Menu::getInstance()->isOptionChecked(MenuOption::CullSharedFaces)) {
|
||||
_writeRenderFullVBO = true;
|
||||
_tree->setDirtyBit();
|
||||
setupNewVoxelsForDrawing();
|
||||
}
|
||||
}
|
||||
|
||||
void VoxelSystem::inspectForOcclusions() {
|
||||
|
||||
if (_inOcclusions) {
|
||||
|
|
|
@ -95,9 +95,7 @@ public slots:
|
|||
// Methods that recurse tree
|
||||
void forceRedrawEntireTree();
|
||||
void clearAllNodesBufferIndex();
|
||||
void cullSharedFaces();
|
||||
void showCulledSharedFaces();
|
||||
|
||||
|
||||
void setDisableFastVoxelPipeline(bool disableFastVoxelPipeline);
|
||||
void setUseVoxelShader(bool useVoxelShader);
|
||||
void setVoxelsAsPoints(bool voxelsAsPoints);
|
||||
|
|
|
@ -88,10 +88,6 @@ padding-top: 3px;</string>
|
|||
<property name="text">
|
||||
<string>Reload all</string>
|
||||
</property>
|
||||
<property name="icon">
|
||||
<iconset>
|
||||
<normaloff>../resources/images/reload.svg</normaloff>../resources/images/reload.svg</iconset>
|
||||
</property>
|
||||
</widget>
|
||||
<widget class="QPushButton" name="stopAllButton">
|
||||
<property name="geometry">
|
||||
|
@ -115,10 +111,6 @@ padding-top: 3px;</string>
|
|||
<property name="text">
|
||||
<string>Stop all</string>
|
||||
</property>
|
||||
<property name="icon">
|
||||
<iconset>
|
||||
<normaloff>../resources/images/stop.svg</normaloff>../resources/images/stop.svg</iconset>
|
||||
</property>
|
||||
</widget>
|
||||
<widget class="QLabel" name="recentlyLoadedLabel">
|
||||
<property name="geometry">
|
||||
|
@ -251,10 +243,6 @@ padding-top: 3px;</string>
|
|||
<property name="text">
|
||||
<string>Load script</string>
|
||||
</property>
|
||||
<property name="icon">
|
||||
<iconset>
|
||||
<normaloff>../resources/images/plus.svg</normaloff>../resources/images/plus.svg</iconset>
|
||||
</property>
|
||||
</widget>
|
||||
<zorder>widgetTitle</zorder>
|
||||
<zorder>currentlyRunningLabel</zorder>
|
||||
|
|
377
interface/ui/shareSnapshot.ui
Normal file
377
interface/ui/shareSnapshot.ui
Normal file
|
@ -0,0 +1,377 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<ui version="4.0">
|
||||
<class>SnapshotShareDialog</class>
|
||||
<widget class="QDialog" name="SnapshotShareDialog">
|
||||
<property name="windowModality">
|
||||
<enum>Qt::NonModal</enum>
|
||||
</property>
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>502</width>
|
||||
<height>616</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Fixed" vsizetype="MinimumExpanding">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="minimumSize">
|
||||
<size>
|
||||
<width>500</width>
|
||||
<height>616</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="maximumSize">
|
||||
<size>
|
||||
<width>502</width>
|
||||
<height>616</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="windowTitle">
|
||||
<string>Share with Alphas</string>
|
||||
</property>
|
||||
<property name="styleSheet">
|
||||
<string notr="true">background-color: rgb(255, 255, 255);</string>
|
||||
</property>
|
||||
<property name="modal">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="verticalLayout_2">
|
||||
<property name="spacing">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="leftMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="topMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="rightMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="bottomMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<item>
|
||||
<layout class="QVBoxLayout" name="verticalLayout">
|
||||
<property name="spacing">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="sizeConstraint">
|
||||
<enum>QLayout::SetMinAndMaxSize</enum>
|
||||
</property>
|
||||
<item alignment="Qt::AlignHCenter|Qt::AlignTop">
|
||||
<widget class="QLabel" name="snapshotWidget">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="MinimumExpanding" vsizetype="Fixed">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="minimumSize">
|
||||
<size>
|
||||
<width>500</width>
|
||||
<height>500</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="maximumSize">
|
||||
<size>
|
||||
<width>30</width>
|
||||
<height>2000</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="styleSheet">
|
||||
<string notr="true">background-color: #ccc;</string>
|
||||
</property>
|
||||
<property name="lineWidth">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="scaledContents">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<property name="alignment">
|
||||
<set>Qt::AlignCenter</set>
|
||||
</property>
|
||||
<property name="indent">
|
||||
<number>0</number>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<spacer name="verticalSpacer">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Vertical</enum>
|
||||
</property>
|
||||
<property name="sizeType">
|
||||
<enum>QSizePolicy::Fixed</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>20</width>
|
||||
<height>9</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QLabel" name="successLabel">
|
||||
<property name="enabled">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="MinimumExpanding" vsizetype="Fixed">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="minimumSize">
|
||||
<size>
|
||||
<width>0</width>
|
||||
<height>0</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="maximumSize">
|
||||
<size>
|
||||
<width>16777215</width>
|
||||
<height>0</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="font">
|
||||
<font>
|
||||
<family>Arial</family>
|
||||
</font>
|
||||
</property>
|
||||
<property name="styleSheet">
|
||||
<string notr="true">color: #666</string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string/>
|
||||
</property>
|
||||
<property name="textFormat">
|
||||
<enum>Qt::RichText</enum>
|
||||
</property>
|
||||
<property name="alignment">
|
||||
<set>Qt::AlignCenter</set>
|
||||
</property>
|
||||
<property name="openExternalLinks">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<property name="textInteractionFlags">
|
||||
<set>Qt::TextBrowserInteraction</set>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QLabel" name="labelNotes">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Fixed" vsizetype="Fixed">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="minimumSize">
|
||||
<size>
|
||||
<width>0</width>
|
||||
<height>19</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="font">
|
||||
<font>
|
||||
<family>Helvetica</family>
|
||||
<pointsize>14</pointsize>
|
||||
<weight>75</weight>
|
||||
<bold>true</bold>
|
||||
</font>
|
||||
</property>
|
||||
<property name="styleSheet">
|
||||
<string notr="true">color: #666;
|
||||
padding-left:20px;</string>
|
||||
</property>
|
||||
<property name="frameShape">
|
||||
<enum>QFrame::NoFrame</enum>
|
||||
</property>
|
||||
<property name="lineWidth">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Notes about this image</string>
|
||||
</property>
|
||||
<property name="alignment">
|
||||
<set>Qt::AlignBottom|Qt::AlignLeading|Qt::AlignLeft</set>
|
||||
</property>
|
||||
<property name="margin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="indent">
|
||||
<number>0</number>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<layout class="QHBoxLayout" name="horizontalLayout">
|
||||
<property name="spacing">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="sizeConstraint">
|
||||
<enum>QLayout::SetFixedSize</enum>
|
||||
</property>
|
||||
<property name="leftMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="rightMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<item>
|
||||
<spacer name="horizontalSpacer_2">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
</property>
|
||||
<property name="sizeType">
|
||||
<enum>QSizePolicy::Fixed</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>19</width>
|
||||
<height>20</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QTextEdit" name="textEdit">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Expanding" vsizetype="Preferred">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="maximumSize">
|
||||
<size>
|
||||
<width>16777215</width>
|
||||
<height>60</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="font">
|
||||
<font>
|
||||
<family>Helvetica</family>
|
||||
<pointsize>14</pointsize>
|
||||
</font>
|
||||
</property>
|
||||
<property name="styleSheet">
|
||||
<string notr="true">border: 1px solid #c5c5c5;</string>
|
||||
</property>
|
||||
<property name="frameShape">
|
||||
<enum>QFrame::NoFrame</enum>
|
||||
</property>
|
||||
<property name="frameShadow">
|
||||
<enum>QFrame::Plain</enum>
|
||||
</property>
|
||||
<property name="lineWidth">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="html">
|
||||
<string><!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd">
|
||||
<html><head><meta name="qrichtext" content="1" /><style type="text/css">
|
||||
p, li { white-space: pre-wrap; }
|
||||
</style></head><body style=" font-family:'Helvetica'; font-size:14pt; font-weight:400; font-style:normal;">
|
||||
<p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><br /></p></body></html></string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<spacer name="horizontalSpacer">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
</property>
|
||||
<property name="sizeType">
|
||||
<enum>QSizePolicy::Fixed</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>25</width>
|
||||
<height>19</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QPushButton" name="shareButton">
|
||||
<property name="styleSheet">
|
||||
<string notr="true"> background-color: #333333;
|
||||
border-width: 0;
|
||||
border-radius: 9px;
|
||||
border-radius: 9px;
|
||||
font-family: Arial;
|
||||
font-size: 18px;
|
||||
font-weight: 100;
|
||||
color: #FFFFFF;
|
||||
width: 120px;
|
||||
height: 50px;</string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Share</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<spacer name="horizontalSpacer_3">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
</property>
|
||||
<property name="sizeType">
|
||||
<enum>QSizePolicy::Fixed</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>25</width>
|
||||
<height>20</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item>
|
||||
<spacer name="verticalSpacer_2">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Vertical</enum>
|
||||
</property>
|
||||
<property name="sizeType">
|
||||
<enum>QSizePolicy::Fixed</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>20</width>
|
||||
<height>11</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<resources/>
|
||||
<connections>
|
||||
<connection>
|
||||
<sender>shareButton</sender>
|
||||
<signal>clicked()</signal>
|
||||
<receiver>SnapshotShareDialog</receiver>
|
||||
<slot>accept()</slot>
|
||||
<hints>
|
||||
<hint type="sourcelabel">
|
||||
<x>420</x>
|
||||
<y>565</y>
|
||||
</hint>
|
||||
<hint type="destinationlabel">
|
||||
<x>250</x>
|
||||
<y>307</y>
|
||||
</hint>
|
||||
</hints>
|
||||
</connection>
|
||||
</connections>
|
||||
</ui>
|
37
libraries/animation/CMakeLists.txt
Normal file
37
libraries/animation/CMakeLists.txt
Normal 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)
|
|
@ -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) {
|
|
@ -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
|
|
@ -22,14 +22,18 @@
|
|||
#include "AudioInjector.h"
|
||||
|
||||
AudioInjector::AudioInjector(QObject* parent) :
|
||||
QObject(parent)
|
||||
QObject(parent),
|
||||
_sound(NULL),
|
||||
_options(),
|
||||
_shouldStop(false)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
AudioInjector::AudioInjector(Sound* sound, const AudioInjectorOptions& injectorOptions) :
|
||||
_sound(sound),
|
||||
_options(injectorOptions)
|
||||
_options(injectorOptions),
|
||||
_shouldStop(false)
|
||||
{
|
||||
|
||||
}
|
||||
|
|
|
@ -661,16 +661,87 @@ void AvatarData::setSkeletonModelURL(const QUrl& skeletonModelURL) {
|
|||
updateJointMappings();
|
||||
}
|
||||
|
||||
void AvatarData::setAttachmentData(const QVector<AttachmentData>& attachmentData) {
|
||||
_attachmentData = attachmentData;
|
||||
}
|
||||
|
||||
void AvatarData::setDisplayName(const QString& displayName) {
|
||||
_displayName = displayName;
|
||||
|
||||
qDebug() << "Changing display name for avatar to" << displayName;
|
||||
}
|
||||
|
||||
QVector<AttachmentData> AvatarData::getAttachmentData() const {
|
||||
if (QThread::currentThread() != thread()) {
|
||||
QVector<AttachmentData> result;
|
||||
QMetaObject::invokeMethod(const_cast<AvatarData*>(this), "getAttachmentData", Qt::BlockingQueuedConnection,
|
||||
Q_RETURN_ARG(QVector<AttachmentData>, result));
|
||||
return result;
|
||||
}
|
||||
return _attachmentData;
|
||||
}
|
||||
|
||||
void AvatarData::setAttachmentData(const QVector<AttachmentData>& attachmentData) {
|
||||
if (QThread::currentThread() != thread()) {
|
||||
QMetaObject::invokeMethod(this, "setAttachmentData", Q_ARG(const QVector<AttachmentData>&, attachmentData));
|
||||
return;
|
||||
}
|
||||
_attachmentData = attachmentData;
|
||||
}
|
||||
|
||||
void AvatarData::attach(const QString& modelURL, const QString& jointName, const glm::vec3& translation,
|
||||
const glm::quat& rotation, float scale, bool allowDuplicates, bool useSaved) {
|
||||
if (QThread::currentThread() != thread()) {
|
||||
QMetaObject::invokeMethod(this, "attach", Q_ARG(const QString&, modelURL), Q_ARG(const QString&, jointName),
|
||||
Q_ARG(const glm::vec3&, translation), Q_ARG(const glm::quat&, rotation),
|
||||
Q_ARG(float, scale), Q_ARG(bool, allowDuplicates), Q_ARG(bool, useSaved));
|
||||
return;
|
||||
}
|
||||
QVector<AttachmentData> attachmentData = getAttachmentData();
|
||||
if (!allowDuplicates) {
|
||||
foreach (const AttachmentData& data, attachmentData) {
|
||||
if (data.modelURL == modelURL && (jointName.isEmpty() || data.jointName == jointName)) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
AttachmentData data;
|
||||
data.modelURL = modelURL;
|
||||
data.jointName = jointName;
|
||||
data.translation = translation;
|
||||
data.rotation = rotation;
|
||||
data.scale = scale;
|
||||
attachmentData.append(data);
|
||||
setAttachmentData(attachmentData);
|
||||
}
|
||||
|
||||
void AvatarData::detachOne(const QString& modelURL, const QString& jointName) {
|
||||
if (QThread::currentThread() != thread()) {
|
||||
QMetaObject::invokeMethod(this, "detachOne", Q_ARG(const QString&, modelURL), Q_ARG(const QString&, jointName));
|
||||
return;
|
||||
}
|
||||
QVector<AttachmentData> attachmentData = getAttachmentData();
|
||||
for (QVector<AttachmentData>::iterator it = attachmentData.begin(); it != attachmentData.end(); it++) {
|
||||
if (it->modelURL == modelURL && (jointName.isEmpty() || it->jointName == jointName)) {
|
||||
attachmentData.erase(it);
|
||||
setAttachmentData(attachmentData);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void AvatarData::detachAll(const QString& modelURL, const QString& jointName) {
|
||||
if (QThread::currentThread() != thread()) {
|
||||
QMetaObject::invokeMethod(this, "detachAll", Q_ARG(const QString&, modelURL), Q_ARG(const QString&, jointName));
|
||||
return;
|
||||
}
|
||||
QVector<AttachmentData> attachmentData = getAttachmentData();
|
||||
for (QVector<AttachmentData>::iterator it = attachmentData.begin(); it != attachmentData.end(); ) {
|
||||
if (it->modelURL == modelURL && (jointName.isEmpty() || it->jointName == jointName)) {
|
||||
it = attachmentData.erase(it);
|
||||
} else {
|
||||
it++;
|
||||
}
|
||||
}
|
||||
setAttachmentData(attachmentData);
|
||||
}
|
||||
|
||||
void AvatarData::setBillboard(const QByteArray& billboard) {
|
||||
_billboard = billboard;
|
||||
|
||||
|
@ -793,3 +864,59 @@ QDataStream& operator>>(QDataStream& in, AttachmentData& attachment) {
|
|||
attachment.translation >> attachment.rotation >> attachment.scale;
|
||||
}
|
||||
|
||||
void AttachmentDataObject::setModelURL(const QString& modelURL) const {
|
||||
AttachmentData data = qscriptvalue_cast<AttachmentData>(thisObject());
|
||||
data.modelURL = modelURL;
|
||||
thisObject() = engine()->toScriptValue(data);
|
||||
}
|
||||
|
||||
QString AttachmentDataObject::getModelURL() const {
|
||||
return qscriptvalue_cast<AttachmentData>(thisObject()).modelURL.toString();
|
||||
}
|
||||
|
||||
void AttachmentDataObject::setJointName(const QString& jointName) const {
|
||||
AttachmentData data = qscriptvalue_cast<AttachmentData>(thisObject());
|
||||
data.jointName = jointName;
|
||||
thisObject() = engine()->toScriptValue(data);
|
||||
}
|
||||
|
||||
QString AttachmentDataObject::getJointName() const {
|
||||
return qscriptvalue_cast<AttachmentData>(thisObject()).jointName;
|
||||
}
|
||||
|
||||
void AttachmentDataObject::setTranslation(const glm::vec3& translation) const {
|
||||
AttachmentData data = qscriptvalue_cast<AttachmentData>(thisObject());
|
||||
data.translation = translation;
|
||||
thisObject() = engine()->toScriptValue(data);
|
||||
}
|
||||
|
||||
glm::vec3 AttachmentDataObject::getTranslation() const {
|
||||
return qscriptvalue_cast<AttachmentData>(thisObject()).translation;
|
||||
}
|
||||
|
||||
void AttachmentDataObject::setRotation(const glm::quat& rotation) const {
|
||||
AttachmentData data = qscriptvalue_cast<AttachmentData>(thisObject());
|
||||
data.rotation = rotation;
|
||||
thisObject() = engine()->toScriptValue(data);
|
||||
}
|
||||
|
||||
glm::quat AttachmentDataObject::getRotation() const {
|
||||
return qscriptvalue_cast<AttachmentData>(thisObject()).rotation;
|
||||
}
|
||||
|
||||
void AttachmentDataObject::setScale(float scale) const {
|
||||
AttachmentData data = qscriptvalue_cast<AttachmentData>(thisObject());
|
||||
data.scale = scale;
|
||||
thisObject() = engine()->toScriptValue(data);
|
||||
}
|
||||
|
||||
float AttachmentDataObject::getScale() const {
|
||||
return qscriptvalue_cast<AttachmentData>(thisObject()).scale;
|
||||
}
|
||||
|
||||
void registerAvatarTypes(QScriptEngine* engine) {
|
||||
qScriptRegisterSequenceMetaType<QVector<AttachmentData> >(engine);
|
||||
engine->setDefaultPrototype(qMetaTypeId<AttachmentData>(), engine->newQObject(
|
||||
new AttachmentDataObject(), QScriptEngine::ScriptOwnership));
|
||||
}
|
||||
|
||||
|
|
|
@ -41,6 +41,7 @@ typedef unsigned long long quint64;
|
|||
#include <QtCore/QVector>
|
||||
#include <QtCore/QVariantMap>
|
||||
#include <QRect>
|
||||
#include <QScriptable>
|
||||
#include <QUuid>
|
||||
|
||||
#include <CollisionInfo.h>
|
||||
|
@ -123,6 +124,7 @@ class AvatarData : public QObject {
|
|||
Q_PROPERTY(QString displayName READ getDisplayName WRITE setDisplayName)
|
||||
Q_PROPERTY(QString faceModelURL READ getFaceModelURLFromScript WRITE setFaceModelURLFromScript)
|
||||
Q_PROPERTY(QString skeletonModelURL READ getSkeletonModelURLFromScript WRITE setSkeletonModelURLFromScript)
|
||||
Q_PROPERTY(QVector<AttachmentData> attachmentData READ getAttachmentData WRITE setAttachmentData)
|
||||
Q_PROPERTY(QString billboardURL READ getBillboardURL WRITE setBillboardFromURL)
|
||||
|
||||
Q_PROPERTY(QStringList jointNames READ getJointNames)
|
||||
|
@ -229,13 +231,22 @@ public:
|
|||
const QUrl& getFaceModelURL() const { return _faceModelURL; }
|
||||
QString getFaceModelURLString() const { return _faceModelURL.toString(); }
|
||||
const QUrl& getSkeletonModelURL() const { return _skeletonModelURL; }
|
||||
const QVector<AttachmentData>& getAttachmentData() const { return _attachmentData; }
|
||||
const QString& getDisplayName() const { return _displayName; }
|
||||
virtual void setFaceModelURL(const QUrl& faceModelURL);
|
||||
virtual void setSkeletonModelURL(const QUrl& skeletonModelURL);
|
||||
virtual void setAttachmentData(const QVector<AttachmentData>& attachmentData);
|
||||
|
||||
virtual void setDisplayName(const QString& displayName);
|
||||
|
||||
Q_INVOKABLE QVector<AttachmentData> getAttachmentData() const;
|
||||
Q_INVOKABLE virtual void setAttachmentData(const QVector<AttachmentData>& attachmentData);
|
||||
|
||||
Q_INVOKABLE virtual void attach(const QString& modelURL, const QString& jointName = QString(),
|
||||
const glm::vec3& translation = glm::vec3(), const glm::quat& rotation = glm::quat(), float scale = 1.0f,
|
||||
bool allowDuplicates = false, bool useSaved = true);
|
||||
|
||||
Q_INVOKABLE void detachOne(const QString& modelURL, const QString& jointName = QString());
|
||||
Q_INVOKABLE void detachAll(const QString& modelURL, const QString& jointName = QString());
|
||||
|
||||
virtual void setBillboard(const QByteArray& billboard);
|
||||
const QByteArray& getBillboard() const { return _billboard; }
|
||||
|
||||
|
@ -341,10 +352,44 @@ public:
|
|||
|
||||
AttachmentData();
|
||||
|
||||
bool isValid() const { return modelURL.isValid(); }
|
||||
|
||||
bool operator==(const AttachmentData& other) const;
|
||||
};
|
||||
|
||||
QDataStream& operator<<(QDataStream& out, const AttachmentData& attachment);
|
||||
QDataStream& operator>>(QDataStream& in, AttachmentData& attachment);
|
||||
|
||||
Q_DECLARE_METATYPE(AttachmentData)
|
||||
Q_DECLARE_METATYPE(QVector<AttachmentData>)
|
||||
|
||||
/// Scriptable wrapper for attachments.
|
||||
class AttachmentDataObject : public QObject, protected QScriptable {
|
||||
Q_OBJECT
|
||||
Q_PROPERTY(QString modelURL READ getModelURL WRITE setModelURL)
|
||||
Q_PROPERTY(QString jointName READ getJointName WRITE setJointName)
|
||||
Q_PROPERTY(glm::vec3 translation READ getTranslation WRITE setTranslation)
|
||||
Q_PROPERTY(glm::quat rotation READ getRotation WRITE setRotation)
|
||||
Q_PROPERTY(float scale READ getScale WRITE setScale)
|
||||
|
||||
public:
|
||||
|
||||
Q_INVOKABLE void setModelURL(const QString& modelURL) const;
|
||||
Q_INVOKABLE QString getModelURL() const;
|
||||
|
||||
Q_INVOKABLE void setJointName(const QString& jointName) const;
|
||||
Q_INVOKABLE QString getJointName() const;
|
||||
|
||||
Q_INVOKABLE void setTranslation(const glm::vec3& translation) const;
|
||||
Q_INVOKABLE glm::vec3 getTranslation() const;
|
||||
|
||||
Q_INVOKABLE void setRotation(const glm::quat& rotation) const;
|
||||
Q_INVOKABLE glm::quat getRotation() const;
|
||||
|
||||
Q_INVOKABLE void setScale(float scale) const;
|
||||
Q_INVOKABLE float getScale() const;
|
||||
};
|
||||
|
||||
void registerAvatarTypes(QScriptEngine* engine);
|
||||
|
||||
#endif // hifi_AvatarData_h
|
||||
|
|
|
@ -26,8 +26,8 @@ HandData::HandData(AvatarData* owningAvatar) :
|
|||
addNewPalm();
|
||||
}
|
||||
|
||||
glm::vec3 HandData::worldVectorToLeapVector(const glm::vec3& worldVector) const {
|
||||
return glm::inverse(getBaseOrientation()) * worldVector / LEAP_UNIT_SCALE;
|
||||
glm::vec3 HandData::worldToLocalVector(const glm::vec3& worldVector) const {
|
||||
return glm::inverse(getBaseOrientation()) * worldVector;
|
||||
}
|
||||
|
||||
PalmData& HandData::addNewPalm() {
|
||||
|
@ -66,69 +66,21 @@ void HandData::getLeftRightPalmIndices(int& leftPalmIndex, int& rightPalmIndex)
|
|||
PalmData::PalmData(HandData* owningHandData) :
|
||||
_rawRotation(0.f, 0.f, 0.f, 1.f),
|
||||
_rawPosition(0.f),
|
||||
_rawNormal(0.f, 1.f, 0.f),
|
||||
_rawVelocity(0.f),
|
||||
_rotationalVelocity(0.f),
|
||||
_totalPenetration(0.f),
|
||||
_controllerButtons(0),
|
||||
_isActive(false),
|
||||
_leapID(LEAPID_INVALID),
|
||||
_sixenseID(SIXENSEID_INVALID),
|
||||
_numFramesWithoutData(0),
|
||||
_owningHandData(owningHandData),
|
||||
_isCollidingWithVoxel(false),
|
||||
_isCollidingWithPalm(false),
|
||||
_collisionlessPaddleExpiry(0)
|
||||
{
|
||||
for (int i = 0; i < NUM_FINGERS_PER_HAND; ++i) {
|
||||
_fingers.push_back(FingerData(this, owningHandData));
|
||||
}
|
||||
_collisionlessPaddleExpiry(0) {
|
||||
}
|
||||
|
||||
void PalmData::addToPosition(const glm::vec3& delta) {
|
||||
// convert to Leap coordinates, then add to palm and finger positions
|
||||
glm::vec3 leapDelta = _owningHandData->worldVectorToLeapVector(delta);
|
||||
_rawPosition += leapDelta;
|
||||
for (size_t i = 0; i < getNumFingers(); i++) {
|
||||
FingerData& finger = _fingers[i];
|
||||
if (finger.isActive()) {
|
||||
finger.setRawTipPosition(finger.getTipRawPosition() + leapDelta);
|
||||
finger.setRawRootPosition(finger.getRootRawPosition() + leapDelta);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
FingerData::FingerData(PalmData* owningPalmData, HandData* owningHandData) :
|
||||
_tipRawPosition(0, 0, 0),
|
||||
_rootRawPosition(0, 0, 0),
|
||||
_isActive(false),
|
||||
_leapID(LEAPID_INVALID),
|
||||
_numFramesWithoutData(0),
|
||||
_owningPalmData(owningPalmData),
|
||||
_owningHandData(owningHandData)
|
||||
{
|
||||
const int standardTrailLength = 10;
|
||||
setTrailLength(standardTrailLength);
|
||||
}
|
||||
|
||||
void HandData::setFingerTrailLength(unsigned int length) {
|
||||
for (size_t i = 0; i < getNumPalms(); ++i) {
|
||||
PalmData& palm = getPalms()[i];
|
||||
for (size_t f = 0; f < palm.getNumFingers(); ++f) {
|
||||
FingerData& finger = palm.getFingers()[f];
|
||||
finger.setTrailLength(length);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void HandData::updateFingerTrails() {
|
||||
for (size_t i = 0; i < getNumPalms(); ++i) {
|
||||
PalmData& palm = getPalms()[i];
|
||||
for (size_t f = 0; f < palm.getNumFingers(); ++f) {
|
||||
FingerData& finger = palm.getFingers()[f];
|
||||
finger.updateTrail();
|
||||
}
|
||||
}
|
||||
_rawPosition += _owningHandData->worldToLocalVector(delta);
|
||||
}
|
||||
|
||||
bool HandData::findSpherePenetration(const glm::vec3& penetratorCenter, float penetratorRadius, glm::vec3& penetration,
|
||||
|
@ -157,54 +109,20 @@ glm::vec3 HandData::getBasePosition() const {
|
|||
return _owningAvatarData->getPosition();
|
||||
}
|
||||
|
||||
void FingerData::setTrailLength(unsigned int length) {
|
||||
_tipTrailPositions.resize(length);
|
||||
_tipTrailCurrentStartIndex = 0;
|
||||
_tipTrailCurrentValidLength = 0;
|
||||
glm::vec3 PalmData::getFingerTipPosition() const {
|
||||
glm::vec3 fingerOffset(0.0f, 0.0f, 0.3f);
|
||||
glm::vec3 palmOffset(0.0f, -0.08f, 0.0f);
|
||||
return getPosition() + _owningHandData->localToWorldDirection(_rawRotation * (fingerOffset + palmOffset));
|
||||
}
|
||||
|
||||
void FingerData::updateTrail() {
|
||||
if (_tipTrailPositions.size() == 0)
|
||||
return;
|
||||
|
||||
if (_isActive) {
|
||||
// Add the next point in the trail.
|
||||
_tipTrailCurrentStartIndex--;
|
||||
if (_tipTrailCurrentStartIndex < 0)
|
||||
_tipTrailCurrentStartIndex = _tipTrailPositions.size() - 1;
|
||||
|
||||
_tipTrailPositions[_tipTrailCurrentStartIndex] = getTipPosition();
|
||||
|
||||
if (_tipTrailCurrentValidLength < (int)_tipTrailPositions.size())
|
||||
_tipTrailCurrentValidLength++;
|
||||
}
|
||||
else {
|
||||
// It's not active, so just kill the trail.
|
||||
_tipTrailCurrentValidLength = 0;
|
||||
}
|
||||
glm::vec3 PalmData::getFingerDirection() const {
|
||||
const glm::vec3 LOCAL_FINGER_DIRECTION(0.0f, 0.0f, 1.0f);
|
||||
return _owningHandData->localToWorldDirection(_rawRotation * LOCAL_FINGER_DIRECTION);
|
||||
}
|
||||
|
||||
int FingerData::getTrailNumPositions() {
|
||||
return _tipTrailCurrentValidLength;
|
||||
}
|
||||
|
||||
const glm::vec3& FingerData::getTrailPosition(int index) {
|
||||
if (index >= _tipTrailCurrentValidLength) {
|
||||
static glm::vec3 zero(0,0,0);
|
||||
return zero;
|
||||
}
|
||||
int posIndex = (index + _tipTrailCurrentStartIndex) % _tipTrailCurrentValidLength;
|
||||
return _tipTrailPositions[posIndex];
|
||||
}
|
||||
|
||||
void PalmData::getBallHoldPosition(glm::vec3& position) const {
|
||||
const float BALL_FORWARD_OFFSET = 0.08f; // put the ball a bit forward of fingers
|
||||
position = BALL_FORWARD_OFFSET * getNormal();
|
||||
if (_fingers.size() > 0) {
|
||||
position += _fingers[0].getTipPosition();
|
||||
} else {
|
||||
position += getPosition();
|
||||
}
|
||||
glm::vec3 PalmData::getNormal() const {
|
||||
const glm::vec3 LOCAL_PALM_DIRECTION(0.0f, -1.0f, 0.0f);
|
||||
return _owningHandData->localToWorldDirection(_rawRotation * LOCAL_PALM_DIRECTION);
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -21,7 +21,6 @@
|
|||
#include "SharedUtil.h"
|
||||
|
||||
class AvatarData;
|
||||
class FingerData;
|
||||
class PalmData;
|
||||
|
||||
const int NUM_HANDS = 2;
|
||||
|
@ -31,8 +30,6 @@ const int NUM_FINGERS = NUM_HANDS * NUM_FINGERS_PER_HAND;
|
|||
const int LEAPID_INVALID = -1;
|
||||
const int SIXENSEID_INVALID = -1;
|
||||
|
||||
const float LEAP_UNIT_SCALE = 0.001f; ///< convert mm to meters
|
||||
|
||||
const int SIXENSE_CONTROLLER_ID_LEFT_HAND = 0;
|
||||
const int SIXENSE_CONTROLLER_ID_RIGHT_HAND = 1;
|
||||
|
||||
|
@ -41,17 +38,16 @@ public:
|
|||
HandData(AvatarData* owningAvatar);
|
||||
virtual ~HandData() {}
|
||||
|
||||
// These methods return the positions in Leap-relative space.
|
||||
// To convert to world coordinates, use Hand::leapPositionToWorldPosition.
|
||||
|
||||
// position conversion
|
||||
glm::vec3 leapPositionToWorldPosition(const glm::vec3& leapPosition) {
|
||||
return getBasePosition() + getBaseOrientation() * (leapPosition * LEAP_UNIT_SCALE);
|
||||
glm::vec3 localToWorldPosition(const glm::vec3& localPosition) {
|
||||
return getBasePosition() + getBaseOrientation() * localPosition;
|
||||
}
|
||||
glm::vec3 leapDirectionToWorldDirection(const glm::vec3& leapDirection) {
|
||||
return getBaseOrientation() * leapDirection;
|
||||
|
||||
glm::vec3 localToWorldDirection(const glm::vec3& localVector) {
|
||||
return getBaseOrientation() * localVector;
|
||||
}
|
||||
glm::vec3 worldVectorToLeapVector(const glm::vec3& worldVector) const;
|
||||
|
||||
glm::vec3 worldToLocalVector(const glm::vec3& worldVector) const;
|
||||
|
||||
std::vector<PalmData>& getPalms() { return _palms; }
|
||||
const std::vector<PalmData>& getPalms() const { return _palms; }
|
||||
|
@ -63,9 +59,6 @@ public:
|
|||
/// both is not found.
|
||||
void getLeftRightPalmIndices(int& leftPalmIndex, int& rightPalmIndex) const;
|
||||
|
||||
void setFingerTrailLength(unsigned int length);
|
||||
void updateFingerTrails();
|
||||
|
||||
/// Checks for penetration between the described sphere and the hand.
|
||||
/// \param penetratorCenter the center of the penetration test sphere
|
||||
/// \param penetratorRadius the radius of the penetration test sphere
|
||||
|
@ -89,71 +82,23 @@ private:
|
|||
HandData& operator= (const HandData&);
|
||||
};
|
||||
|
||||
class FingerData {
|
||||
public:
|
||||
FingerData(PalmData* owningPalmData, HandData* owningHandData);
|
||||
|
||||
glm::vec3 getTipPosition() const { return _owningHandData->leapPositionToWorldPosition(_tipRawPosition); }
|
||||
glm::vec3 getRootPosition() const { return _owningHandData->leapPositionToWorldPosition(_rootRawPosition); }
|
||||
const glm::vec3& getTipRawPosition() const { return _tipRawPosition; }
|
||||
const glm::vec3& getRootRawPosition() const { return _rootRawPosition; }
|
||||
bool isActive() const { return _isActive; }
|
||||
int getLeapID() const { return _leapID; }
|
||||
|
||||
void setActive(bool active) { _isActive = active; }
|
||||
void setLeapID(int id) { _leapID = id; }
|
||||
void setRawTipPosition(const glm::vec3& pos) { _tipRawPosition = pos; }
|
||||
void setRawRootPosition(const glm::vec3& pos) { _rootRawPosition = pos; }
|
||||
|
||||
void setTrailLength(unsigned int length);
|
||||
void updateTrail();
|
||||
|
||||
int getTrailNumPositions();
|
||||
const glm::vec3& getTrailPosition(int index);
|
||||
|
||||
void incrementFramesWithoutData() { _numFramesWithoutData++; }
|
||||
void resetFramesWithoutData() { _numFramesWithoutData = 0; }
|
||||
int getFramesWithoutData() const { return _numFramesWithoutData; }
|
||||
|
||||
private:
|
||||
glm::vec3 _tipRawPosition;
|
||||
glm::vec3 _rootRawPosition;
|
||||
bool _isActive; // This has current valid data
|
||||
int _leapID; // the Leap's serial id for this tracked object
|
||||
int _numFramesWithoutData; // after too many frames without data, this tracked object assumed lost.
|
||||
std::vector<glm::vec3> _tipTrailPositions;
|
||||
int _tipTrailCurrentStartIndex;
|
||||
int _tipTrailCurrentValidLength;
|
||||
PalmData* _owningPalmData;
|
||||
HandData* _owningHandData;
|
||||
};
|
||||
|
||||
class PalmData {
|
||||
public:
|
||||
PalmData(HandData* owningHandData);
|
||||
glm::vec3 getPosition() const { return _owningHandData->leapPositionToWorldPosition(_rawPosition); }
|
||||
glm::vec3 getNormal() const { return _owningHandData->leapDirectionToWorldDirection(_rawNormal); }
|
||||
glm::vec3 getVelocity() const { return _owningHandData->leapDirectionToWorldDirection(_rawVelocity); }
|
||||
glm::vec3 getPosition() const { return _owningHandData->localToWorldPosition(_rawPosition); }
|
||||
glm::vec3 getVelocity() const { return _owningHandData->localToWorldDirection(_rawVelocity); }
|
||||
|
||||
const glm::vec3& getRawPosition() const { return _rawPosition; }
|
||||
const glm::vec3& getRawNormal() const { return _rawNormal; }
|
||||
bool isActive() const { return _isActive; }
|
||||
int getLeapID() const { return _leapID; }
|
||||
int getSixenseID() const { return _sixenseID; }
|
||||
|
||||
|
||||
std::vector<FingerData>& getFingers() { return _fingers; }
|
||||
const std::vector<FingerData>& getFingers() const { return _fingers; }
|
||||
size_t getNumFingers() const { return _fingers.size(); }
|
||||
|
||||
void setActive(bool active) { _isActive = active; }
|
||||
void setLeapID(int id) { _leapID = id; }
|
||||
void setSixenseID(int id) { _sixenseID = id; }
|
||||
|
||||
void setRawRotation(const glm::quat rawRotation) { _rawRotation = rawRotation; };
|
||||
glm::quat getRawRotation() const { return _rawRotation; }
|
||||
void setRawPosition(const glm::vec3& pos) { _rawPosition = pos; }
|
||||
void setRawNormal(const glm::vec3& normal) { _rawNormal = normal; }
|
||||
void setRawVelocity(const glm::vec3& velocity) { _rawVelocity = velocity; }
|
||||
const glm::vec3& getRawVelocity() const { return _rawVelocity; }
|
||||
void addToPosition(const glm::vec3& delta);
|
||||
|
@ -162,11 +107,11 @@ public:
|
|||
void resolvePenetrations() { addToPosition(-_totalPenetration); _totalPenetration = glm::vec3(0.f); }
|
||||
|
||||
void setTipPosition(const glm::vec3& position) { _tipPosition = position; }
|
||||
const glm::vec3 getTipPosition() const { return _owningHandData->leapPositionToWorldPosition(_tipPosition); }
|
||||
const glm::vec3 getTipPosition() const { return _owningHandData->localToWorldPosition(_tipPosition); }
|
||||
const glm::vec3& getTipRawPosition() const { return _tipPosition; }
|
||||
|
||||
void setTipVelocity(const glm::vec3& velocity) { _tipVelocity = velocity; }
|
||||
const glm::vec3 getTipVelocity() const { return _owningHandData->leapDirectionToWorldDirection(_tipVelocity); }
|
||||
const glm::vec3 getTipVelocity() const { return _owningHandData->localToWorldDirection(_tipVelocity); }
|
||||
const glm::vec3& getTipRawVelocity() const { return _tipVelocity; }
|
||||
|
||||
void incrementFramesWithoutData() { _numFramesWithoutData++; }
|
||||
|
@ -198,11 +143,14 @@ public:
|
|||
/// Store position where the palm holds the ball.
|
||||
void getBallHoldPosition(glm::vec3& position) const;
|
||||
|
||||
// return world-frame:
|
||||
glm::vec3 getFingerTipPosition() const;
|
||||
glm::vec3 getFingerDirection() const;
|
||||
glm::vec3 getNormal() const;
|
||||
|
||||
private:
|
||||
std::vector<FingerData> _fingers;
|
||||
glm::quat _rawRotation;
|
||||
glm::vec3 _rawPosition;
|
||||
glm::vec3 _rawNormal;
|
||||
glm::vec3 _rawVelocity;
|
||||
glm::vec3 _rotationalVelocity;
|
||||
glm::quat _lastRotation;
|
||||
|
@ -216,7 +164,6 @@ private:
|
|||
float _joystickX, _joystickY;
|
||||
|
||||
bool _isActive; // This has current valid data
|
||||
int _leapID; // the Leap's serial id for this tracked object
|
||||
int _sixenseID; // Sixense controller ID for this palm
|
||||
int _numFramesWithoutData; // after too many frames without data, this tracked object assumed lost.
|
||||
HandData* _owningHandData;
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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}")
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -106,6 +106,10 @@ void ModelTreeElement::update(ModelTreeUpdateArgs& args) {
|
|||
QList<ModelItem>::iterator modelItr = _modelItems->begin();
|
||||
while(modelItr != _modelItems->end()) {
|
||||
ModelItem& model = (*modelItr);
|
||||
|
||||
// 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
|
||||
|
@ -324,7 +328,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;
|
||||
|
|
|
@ -105,7 +105,6 @@ ModelItemID ModelsScriptingInterface::editModel(ModelItemID modelID, const Model
|
|||
_modelTree->updateModel(modelID, properties);
|
||||
_modelTree->unlock();
|
||||
}
|
||||
|
||||
return modelID;
|
||||
}
|
||||
|
||||
|
|
|
@ -16,7 +16,8 @@
|
|||
DataServerAccountInfo::DataServerAccountInfo() :
|
||||
_accessToken(),
|
||||
_username(),
|
||||
_xmppPassword()
|
||||
_xmppPassword(),
|
||||
_discourseApiKey()
|
||||
{
|
||||
|
||||
}
|
||||
|
@ -29,12 +30,14 @@ DataServerAccountInfo::DataServerAccountInfo(const QJsonObject& jsonObject) :
|
|||
QJsonObject userJSONObject = jsonObject["user"].toObject();
|
||||
setUsername(userJSONObject["username"].toString());
|
||||
setXMPPPassword(userJSONObject["xmpp_password"].toString());
|
||||
setDiscourseApiKey(userJSONObject["discourse_api_key"].toString());
|
||||
}
|
||||
|
||||
DataServerAccountInfo::DataServerAccountInfo(const DataServerAccountInfo& otherInfo) {
|
||||
_accessToken = otherInfo._accessToken;
|
||||
_username = otherInfo._username;
|
||||
_xmppPassword = otherInfo._xmppPassword;
|
||||
_discourseApiKey = otherInfo._discourseApiKey;
|
||||
}
|
||||
|
||||
DataServerAccountInfo& DataServerAccountInfo::operator=(const DataServerAccountInfo& otherInfo) {
|
||||
|
@ -49,6 +52,7 @@ void DataServerAccountInfo::swap(DataServerAccountInfo& otherInfo) {
|
|||
swap(_accessToken, otherInfo._accessToken);
|
||||
swap(_username, otherInfo._username);
|
||||
swap(_xmppPassword, otherInfo._xmppPassword);
|
||||
swap(_discourseApiKey, otherInfo._discourseApiKey);
|
||||
}
|
||||
|
||||
void DataServerAccountInfo::setUsername(const QString& username) {
|
||||
|
@ -65,12 +69,18 @@ void DataServerAccountInfo::setXMPPPassword(const QString& xmppPassword) {
|
|||
}
|
||||
}
|
||||
|
||||
void DataServerAccountInfo::setDiscourseApiKey(const QString& discourseApiKey) {
|
||||
if (_discourseApiKey != discourseApiKey) {
|
||||
_discourseApiKey = discourseApiKey;
|
||||
}
|
||||
}
|
||||
|
||||
QDataStream& operator<<(QDataStream &out, const DataServerAccountInfo& info) {
|
||||
out << info._accessToken << info._username << info._xmppPassword;
|
||||
out << info._accessToken << info._username << info._xmppPassword << info._discourseApiKey;
|
||||
return out;
|
||||
}
|
||||
|
||||
QDataStream& operator>>(QDataStream &in, DataServerAccountInfo& info) {
|
||||
in >> info._accessToken >> info._username >> info._xmppPassword;
|
||||
in >> info._accessToken >> info._username >> info._xmppPassword >> info._discourseApiKey;
|
||||
return in;
|
||||
}
|
||||
|
|
|
@ -31,7 +31,10 @@ public:
|
|||
|
||||
const QString& getXMPPPassword() const { return _xmppPassword; }
|
||||
void setXMPPPassword(const QString& xmppPassword);
|
||||
|
||||
|
||||
const QString& getDiscourseApiKey() const { return _discourseApiKey; }
|
||||
void setDiscourseApiKey(const QString& discourseApiKey);
|
||||
|
||||
friend QDataStream& operator<<(QDataStream &out, const DataServerAccountInfo& info);
|
||||
friend QDataStream& operator>>(QDataStream &in, DataServerAccountInfo& info);
|
||||
private:
|
||||
|
@ -40,6 +43,7 @@ private:
|
|||
OAuthAccessToken _accessToken;
|
||||
QString _username;
|
||||
QString _xmppPassword;
|
||||
QString _discourseApiKey;
|
||||
};
|
||||
|
||||
#endif // hifi_DataServerAccountInfo_h
|
||||
|
|
|
@ -21,7 +21,7 @@
|
|||
#include "DTLSClientSession.h"
|
||||
#include "HifiSockAddr.h"
|
||||
|
||||
const QString DEFAULT_DOMAIN_HOSTNAME = "alpha.highfidelity.io";
|
||||
const QString DEFAULT_DOMAIN_HOSTNAME = "sandbox.highfidelity.io";
|
||||
|
||||
const unsigned short DEFAULT_DOMAIN_SERVER_PORT = 40102;
|
||||
const unsigned short DEFAULT_DOMAIN_SERVER_DTLS_PORT = 40103;
|
||||
|
|
|
@ -66,6 +66,8 @@ PacketVersion versionForPacketType(PacketType type) {
|
|||
return 1;
|
||||
case PacketTypeOctreeStats:
|
||||
return 1;
|
||||
case PacketTypeModelData:
|
||||
return 1;
|
||||
default:
|
||||
return 0;
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
|
||||
|
@ -210,6 +216,7 @@ void ScriptEngine::init() {
|
|||
registerEventTypes(&_engine);
|
||||
registerMenuItemProperties(&_engine);
|
||||
registerAnimationTypes(&_engine);
|
||||
registerAvatarTypes(&_engine);
|
||||
|
||||
qScriptRegisterMetaType(&_engine, ParticlePropertiesToScriptValue, ParticlePropertiesFromScriptValue);
|
||||
qScriptRegisterMetaType(&_engine, ParticleIDtoScriptValue, ParticleIDfromScriptValue);
|
||||
|
@ -223,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);
|
||||
|
||||
|
|
|
@ -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"
|
||||
|
|
233
libraries/script-engine/src/XMLHttpRequestClass.cpp
Normal file
233
libraries/script-engine/src/XMLHttpRequestClass.cpp
Normal 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()));
|
||||
}
|
129
libraries/script-engine/src/XMLHttpRequestClass.h
Normal file
129
libraries/script-engine/src/XMLHttpRequestClass.h
Normal 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
|
|
@ -30,6 +30,13 @@ ResourceCache::~ResourceCache() {
|
|||
}
|
||||
}
|
||||
|
||||
void ResourceCache::refresh(const QUrl& url) {
|
||||
QSharedPointer<Resource> resource = _resources.value(url);
|
||||
if (!resource.isNull()) {
|
||||
resource->refresh();
|
||||
}
|
||||
}
|
||||
|
||||
QSharedPointer<Resource> ResourceCache::getResource(const QUrl& url, const QUrl& fallback, bool delayLoad, void* extra) {
|
||||
if (!url.isValid() && !url.isEmpty() && fallback.isValid()) {
|
||||
return getResource(fallback, QUrl(), delayLoad);
|
||||
|
@ -107,25 +114,15 @@ QList<Resource*> ResourceCache::_loadingRequests;
|
|||
Resource::Resource(const QUrl& url, bool delayLoad) :
|
||||
_url(url),
|
||||
_request(url),
|
||||
_startedLoading(false),
|
||||
_failedToLoad(false),
|
||||
_loaded(false),
|
||||
_lruKey(0),
|
||||
_reply(NULL),
|
||||
_attempts(0) {
|
||||
_reply(NULL) {
|
||||
|
||||
init();
|
||||
|
||||
if (url.isEmpty()) {
|
||||
_startedLoading = _loaded = true;
|
||||
return;
|
||||
|
||||
} else if (!(url.isValid() && ResourceCache::getNetworkAccessManager())) {
|
||||
_startedLoading = _failedToLoad = true;
|
||||
return;
|
||||
}
|
||||
_request.setAttribute(QNetworkRequest::CacheLoadControlAttribute, QNetworkRequest::PreferCache);
|
||||
|
||||
// start loading immediately unless instructed otherwise
|
||||
if (!delayLoad) {
|
||||
if (!(_startedLoading || delayLoad)) {
|
||||
attemptRequest();
|
||||
}
|
||||
}
|
||||
|
@ -178,6 +175,22 @@ float Resource::getLoadPriority() {
|
|||
return highestPriority;
|
||||
}
|
||||
|
||||
void Resource::refresh() {
|
||||
if (_reply == NULL && !(_loaded || _failedToLoad)) {
|
||||
return;
|
||||
}
|
||||
if (_reply) {
|
||||
ResourceCache::requestCompleted(this);
|
||||
delete _reply;
|
||||
_reply = NULL;
|
||||
}
|
||||
init();
|
||||
_request.setAttribute(QNetworkRequest::CacheLoadControlAttribute, QNetworkRequest::AlwaysNetwork);
|
||||
if (!_startedLoading) {
|
||||
attemptRequest();
|
||||
}
|
||||
}
|
||||
|
||||
void Resource::allReferencesCleared() {
|
||||
if (QThread::currentThread() != thread()) {
|
||||
QMetaObject::invokeMethod(this, "allReferencesCleared");
|
||||
|
@ -197,6 +210,20 @@ void Resource::allReferencesCleared() {
|
|||
}
|
||||
}
|
||||
|
||||
void Resource::init() {
|
||||
_startedLoading = false;
|
||||
_failedToLoad = false;
|
||||
_loaded = false;
|
||||
_attempts = 0;
|
||||
|
||||
if (_url.isEmpty()) {
|
||||
_startedLoading = _loaded = true;
|
||||
|
||||
} else if (!(_url.isValid() && ResourceCache::getNetworkAccessManager())) {
|
||||
_startedLoading = _failedToLoad = true;
|
||||
}
|
||||
}
|
||||
|
||||
void Resource::attemptRequest() {
|
||||
_startedLoading = true;
|
||||
ResourceCache::attemptRequest(this);
|
||||
|
|
|
@ -47,6 +47,8 @@ public:
|
|||
ResourceCache(QObject* parent = NULL);
|
||||
virtual ~ResourceCache();
|
||||
|
||||
void refresh(const QUrl& url);
|
||||
|
||||
protected:
|
||||
|
||||
QMap<int, QSharedPointer<Resource> > _unusedResources;
|
||||
|
@ -119,6 +121,9 @@ public:
|
|||
/// For loading resources, returns the load progress.
|
||||
float getProgress() const { return (_bytesTotal == 0) ? 0.0f : (float)_bytesReceived / _bytesTotal; }
|
||||
|
||||
/// Refreshes the resource.
|
||||
void refresh();
|
||||
|
||||
void setSelf(const QWeakPointer<Resource>& self) { _self = self; }
|
||||
|
||||
void setCache(ResourceCache* cache) { _cache = cache; }
|
||||
|
@ -131,6 +136,8 @@ protected slots:
|
|||
|
||||
protected:
|
||||
|
||||
virtual void init();
|
||||
|
||||
/// Called when the download has finished. The recipient should delete the reply when done with it.
|
||||
virtual void downloadFinished(QNetworkReply* reply) = 0;
|
||||
|
||||
|
|
|
@ -54,6 +54,7 @@ static const float SQUARE_ROOT_OF_3 = (float)sqrt(3.f);
|
|||
static const float METERS_PER_DECIMETER = 0.1f;
|
||||
static const float METERS_PER_CENTIMETER = 0.01f;
|
||||
static const float METERS_PER_MILLIMETER = 0.001f;
|
||||
static const float MILLIMETERS_PER_METER = 1000.0f;
|
||||
static const quint64 USECS_PER_MSEC = 1000;
|
||||
static const quint64 MSECS_PER_SECOND = 1000;
|
||||
static const quint64 USECS_PER_SECOND = USECS_PER_MSEC * MSECS_PER_SECOND;
|
||||
|
|
Loading…
Reference in a new issue