Merge branch 'master' of https://github.com/highfidelity/hifi
5
BUILD.md
|
@ -262,3 +262,8 @@ If you need to debug Interface, you can run interface from within Visual Studio
|
|||
* In the Solution Explorer, right click interface and click Set as StartUp Project
|
||||
* Set the "Working Directory" for the Interface debugging sessions to the Debug output directory so that your application can load resources. Do this: right click interface and click Properties, choose Debugging from Configuration Properties, set Working Directory to .\Debug
|
||||
* Now you can run and debug interface through Visual Studio
|
||||
|
||||
#### Devices
|
||||
|
||||
You can support external input/output devices such as Leap Motion, Faceplus, Faceshift PrioVR, RTmidi, SixSense and more by adding each individual SDK in the visible building path. Refer to the readme file available in each device folder in /hifi/interface/external/ for the detailed explanation of the requirements to use the device.
|
||||
|
||||
|
|
47
cmake/modules/FindLeapMotion.cmake
Normal file
|
@ -0,0 +1,47 @@
|
|||
# Try to find the LeapMotion library
|
||||
#
|
||||
# You must provide a LEAPMOTION_ROOT_DIR which contains lib and include directories
|
||||
#
|
||||
# Once done this will define
|
||||
#
|
||||
# LEAPMOTION_FOUND - system found LEAPMOTION
|
||||
# LEAPMOTION_INCLUDE_DIRS - the LEAPMOTION include directory
|
||||
# LEAPMOTION_LIBRARIES - Link this to use LEAPMOTION
|
||||
#
|
||||
# Created on 6/2/2014 by Sam Cake
|
||||
# Copyright (c) 2014 High Fidelity
|
||||
#
|
||||
|
||||
if (LEAPMOTION_LIBRARIES AND LEAPMOTION_INCLUDE_DIRS)
|
||||
# in cache already
|
||||
set(LEAPMOTION_FOUND TRUE)
|
||||
else (LEAPMOTION_LIBRARIES AND LEAPMOTION_INCLUDE_DIRS)
|
||||
set(LEAPMOTION_SEARCH_DIRS "${LEAPMOTION_ROOT_DIR}" "$ENV{HIFI_LIB_DIR}/leapmotion")
|
||||
|
||||
find_path(LEAPMOTION_INCLUDE_DIRS Leap.h PATH_SUFFIXES include HINTS ${LEAPMOTION_SEARCH_DIRS})
|
||||
|
||||
if (WIN32)
|
||||
find_library(LEAPMOTION_LIBRARIES "lib/x86/Leap.lib" HINTS ${LEAPMOTION_SEARCH_DIRS})
|
||||
endif (WIN32)
|
||||
if (APPLE)
|
||||
find_library(LEAPMOTION_LIBRARIES "lib/libLeap.dylib" HINTS ${LEAPMOTION_SEARCH_DIRS})
|
||||
endif (APPLE)
|
||||
|
||||
if (LEAPMOTION_INCLUDE_DIRS AND LEAPMOTION_LIBRARIES)
|
||||
set(LEAPMOTION_FOUND TRUE)
|
||||
endif (LEAPMOTION_INCLUDE_DIRS AND LEAPMOTION_LIBRARIES)
|
||||
|
||||
if (LEAPMOTION_FOUND)
|
||||
if (NOT LEAPMOTION_FIND_QUIETLY)
|
||||
message(STATUS "Found LEAPMOTION... ${LEAPMOTION_LIBRARIES}")
|
||||
endif (NOT LEAPMOTION_FIND_QUIETLY)
|
||||
else ()
|
||||
if (LEAPMOTION_FIND_REQUIRED)
|
||||
message(FATAL_ERROR "Could not find LEAPMOTION")
|
||||
endif (LEAPMOTION_FIND_REQUIRED)
|
||||
endif ()
|
||||
|
||||
# show the LEAPMOTION_INCLUDE_DIRS and LEAPMOTION_LIBRARIES variables only in the advanced view
|
||||
mark_as_advanced(LEAPMOTION_INCLUDE_DIRS LEAPMOTION_LIBRARIES)
|
||||
|
||||
endif (LEAPMOTION_LIBRARIES AND LEAPMOTION_INCLUDE_DIRS)
|
|
@ -17,7 +17,7 @@ var currentSelection = 0;
|
|||
var currentNumLights = 1;
|
||||
var maxNumLights = 2;
|
||||
var currentNumAvatars = 0;
|
||||
var avatarHashIDs = [];
|
||||
var changeDelta = 0.1;
|
||||
|
||||
function keyPressEvent(event) {
|
||||
|
||||
|
@ -40,7 +40,7 @@ function keyPressEvent(event) {
|
|||
print("light selection = " + currentSelection);
|
||||
}
|
||||
else if (event.text == "5" ) {
|
||||
localLightColors[currentSelection].x += 0.01;
|
||||
localLightColors[currentSelection].x += changeDelta;
|
||||
if ( localLightColors[currentSelection].x > 1.0) {
|
||||
localLightColors[currentSelection].x = 0.0;
|
||||
}
|
||||
|
@ -49,7 +49,7 @@ function keyPressEvent(event) {
|
|||
print("CHANGE RED light " + currentSelection + " color (" + localLightColors[currentSelection].x + ", " + localLightColors[currentSelection].y + ", " + localLightColors[currentSelection].z + " )" );
|
||||
}
|
||||
else if (event.text == "6" ) {
|
||||
localLightColors[currentSelection].y += 0.01;
|
||||
localLightColors[currentSelection].y += changeDelta;
|
||||
if ( localLightColors[currentSelection].y > 1.0) {
|
||||
localLightColors[currentSelection].y = 0.0;
|
||||
}
|
||||
|
@ -58,7 +58,7 @@ function keyPressEvent(event) {
|
|||
print("CHANGE GREEN light " + currentSelection + " color (" + localLightColors[currentSelection].x + ", " + localLightColors[currentSelection].y + ", " + localLightColors[currentSelection].z + " )" );
|
||||
}
|
||||
else if (event.text == "7" ) {
|
||||
localLightColors[currentSelection].z += 0.01;
|
||||
localLightColors[currentSelection].z += changeDelta;
|
||||
if ( localLightColors[currentSelection].z > 1.0) {
|
||||
localLightColors[currentSelection].z = 0.0;
|
||||
}
|
||||
|
@ -67,7 +67,7 @@ function keyPressEvent(event) {
|
|||
print("CHANGE BLUE light " + currentSelection + " color (" + localLightColors[currentSelection].x + ", " + localLightColors[currentSelection].y + ", " + localLightColors[currentSelection].z + " )" );
|
||||
}
|
||||
else if (event.text == "8" ) {
|
||||
localLightDirections[currentSelection].x += 0.01;
|
||||
localLightDirections[currentSelection].x += changeDelta;
|
||||
if (localLightDirections[currentSelection].x > 1.0) {
|
||||
localLightDirections[currentSelection].x = -1.0;
|
||||
}
|
||||
|
@ -76,7 +76,7 @@ function keyPressEvent(event) {
|
|||
print("PLUS X light " + currentSelection + " direction (" + localLightDirections[currentSelection].x + ", " + localLightDirections[currentSelection].y + ", " + localLightDirections[currentSelection].z + " )" );
|
||||
}
|
||||
else if (event.text == "9" ) {
|
||||
localLightDirections[currentSelection].x -= 0.01;
|
||||
localLightDirections[currentSelection].x -= changeDelta;
|
||||
if (localLightDirections[currentSelection].x < -1.0) {
|
||||
localLightDirections[currentSelection].x = 1.0;
|
||||
}
|
||||
|
@ -85,7 +85,7 @@ function keyPressEvent(event) {
|
|||
print("MINUS X light " + currentSelection + " direction (" + localLightDirections[currentSelection].x + ", " + localLightDirections[currentSelection].y + ", " + localLightDirections[currentSelection].z + " )" );
|
||||
}
|
||||
else if (event.text == "0" ) {
|
||||
localLightDirections[currentSelection].y += 0.01;
|
||||
localLightDirections[currentSelection].y += changeDelta;
|
||||
if (localLightDirections[currentSelection].y > 1.0) {
|
||||
localLightDirections[currentSelection].y = -1.0;
|
||||
}
|
||||
|
@ -94,7 +94,7 @@ function keyPressEvent(event) {
|
|||
print("PLUS Y light " + currentSelection + " direction (" + localLightDirections[currentSelection].x + ", " + localLightDirections[currentSelection].y + ", " + localLightDirections[currentSelection].z + " )" );
|
||||
}
|
||||
else if (event.text == "-" ) {
|
||||
localLightDirections[currentSelection].y -= 0.01;
|
||||
localLightDirections[currentSelection].y -= changeDelta;
|
||||
if (localLightDirections[currentSelection].y < -1.0) {
|
||||
localLightDirections[currentSelection].y = 1.0;
|
||||
}
|
||||
|
@ -142,23 +142,12 @@ function updateLocalLights()
|
|||
// new avatars, so add lights
|
||||
var numAvatars = AvatarManager.getNumAvatars();
|
||||
if (numAvatars != currentNumAvatars) {
|
||||
|
||||
|
||||
for (var i = 0; i < numAvatars; i++) {
|
||||
var id = AvatarManager.getAvatarHashKey(i);
|
||||
var numLights = AvatarManager.getNumLightsInAvatar(i);
|
||||
|
||||
// check if avatar has already been registered
|
||||
var hasRegistered = false;
|
||||
for (var j = 0; j < numAvatars; j++) {
|
||||
if (avatarHashIDs[j] == id) {
|
||||
hasRegistered = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// add new id and set light params
|
||||
if (!hasRegistered) {
|
||||
|
||||
avatarHashIDs.push(id);
|
||||
// check if new avatar has lights
|
||||
if (numLights <= 0) {
|
||||
AvatarManager.addAvatarLocalLight(i);
|
||||
|
||||
// set color and direction for new avatar
|
||||
|
|
|
@ -1118,14 +1118,35 @@ function handeMenuEvent(menuItem){
|
|||
}
|
||||
if (editModelID != -1) {
|
||||
print(" Edit Properties.... about to edit properties...");
|
||||
var propertyName = Window.prompt("Which property would you like to change?", "modelURL");
|
||||
var properties = Models.getModelProperties(editModelID);
|
||||
var oldValue = properties[propertyName];
|
||||
var newValue = Window.prompt("New value for: " + propertyName, oldValue);
|
||||
if (newValue != "") {
|
||||
properties[propertyName] = newValue;
|
||||
Models.editModel(editModelID, properties);
|
||||
}
|
||||
var array = new Array();
|
||||
var decimals = 3;
|
||||
array.push({ label: "Model URL:", value: selectedModelProperties.modelURL });
|
||||
array.push({ label: "Animation URL:", value: selectedModelProperties.animationURL });
|
||||
array.push({ label: "X:", value: selectedModelProperties.position.x.toFixed(decimals) });
|
||||
array.push({ label: "Y:", value: selectedModelProperties.position.y.toFixed(decimals) });
|
||||
array.push({ label: "Z:", value: selectedModelProperties.position.z.toFixed(decimals) });
|
||||
var angles = Quat.safeEulerAngles(selectedModelProperties.modelRotation);
|
||||
array.push({ label: "Pitch:", value: angles.x.toFixed(decimals) });
|
||||
array.push({ label: "Yaw:", value: angles.y.toFixed(decimals) });
|
||||
array.push({ label: "Roll:", value: angles.z.toFixed(decimals) });
|
||||
array.push({ label: "Scale:", value: 2 * selectedModelProperties.radius.toFixed(decimals) });
|
||||
|
||||
var propertyName = Window.form("Edit Properties", array);
|
||||
modelSelected = false;
|
||||
|
||||
selectedModelProperties.modelURL = array[0].value;
|
||||
selectedModelProperties.animationURL = array[1].value;
|
||||
selectedModelProperties.position.x = array[2].value;
|
||||
selectedModelProperties.position.y = array[3].value;
|
||||
selectedModelProperties.position.z = array[4].value;
|
||||
angles.x = array[5].value;
|
||||
angles.y = array[6].value;
|
||||
angles.z = array[7].value;
|
||||
selectedModelProperties.modelRotation = Quat.fromVec3Degrees(angles);
|
||||
selectedModelProperties.radius = array[8].value / 2;
|
||||
print(selectedModelProperties.radius);
|
||||
|
||||
Models.editModel(selectedModelID, selectedModelProperties);
|
||||
}
|
||||
}
|
||||
tooltip.show(false);
|
||||
|
|
646
examples/fallingSand.js
Normal file
|
@ -0,0 +1,646 @@
|
|||
//
|
||||
// fallingSand.js
|
||||
// examples
|
||||
//
|
||||
// Created by Ben Arnold on 7/14/14.
|
||||
// Copyright 2014 High Fidelity, Inc.
|
||||
//
|
||||
// This sample script allows the user to place sand voxels that will undergo
|
||||
// cellular automata physics.
|
||||
//
|
||||
// Distributed under the Apache License, Version 2.0.
|
||||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
//
|
||||
|
||||
var zFightingSizeAdjust = 0.002; // used to adjust preview voxels to prevent z fighting
|
||||
var previewLineWidth = 2.0;
|
||||
|
||||
var voxelSize = 1;
|
||||
var windowDimensions = Controller.getViewportDimensions();
|
||||
var toolIconUrl = "http://highfidelity-public.s3-us-west-1.amazonaws.com/images/tools/";
|
||||
|
||||
var MAX_VOXEL_SCALE_POWER = 5;
|
||||
var MIN_VOXEL_SCALE_POWER = -8;
|
||||
var MAX_VOXEL_SCALE = Math.pow(2.0, MAX_VOXEL_SCALE_POWER);
|
||||
var MIN_VOXEL_SCALE = Math.pow(2.0, MIN_VOXEL_SCALE_POWER);
|
||||
|
||||
|
||||
var linePreviewTop = Overlays.addOverlay("line3d", {
|
||||
position: { x: 0, y: 0, z: 0},
|
||||
end: { x: 0, y: 0, z: 0},
|
||||
color: { red: 0, green: 0, blue: 255},
|
||||
alpha: 1,
|
||||
visible: false,
|
||||
lineWidth: previewLineWidth
|
||||
});
|
||||
|
||||
var linePreviewBottom = Overlays.addOverlay("line3d", {
|
||||
position: { x: 0, y: 0, z: 0},
|
||||
end: { x: 0, y: 0, z: 0},
|
||||
color: { red: 0, green: 0, blue: 255},
|
||||
alpha: 1,
|
||||
visible: false,
|
||||
lineWidth: previewLineWidth
|
||||
});
|
||||
|
||||
var linePreviewLeft = Overlays.addOverlay("line3d", {
|
||||
position: { x: 0, y: 0, z: 0},
|
||||
end: { x: 0, y: 0, z: 0},
|
||||
color: { red: 0, green: 0, blue: 255},
|
||||
alpha: 1,
|
||||
visible: false,
|
||||
lineWidth: previewLineWidth
|
||||
});
|
||||
|
||||
var linePreviewRight = Overlays.addOverlay("line3d", {
|
||||
position: { x: 0, y: 0, z: 0},
|
||||
end: { x: 0, y: 0, z: 0},
|
||||
color: { red: 0, green: 0, blue: 255},
|
||||
alpha: 1,
|
||||
visible: false,
|
||||
lineWidth: previewLineWidth
|
||||
});
|
||||
|
||||
|
||||
var UIColor = { red: 119, green: 103, blue: 53};
|
||||
var activeUIColor = { red: 234, green: 206, blue: 106};
|
||||
|
||||
var toolHeight = 50;
|
||||
var toolWidth = 50;
|
||||
|
||||
var editToolsOn = true;
|
||||
|
||||
var voxelToolSelected = false;
|
||||
|
||||
var scaleSelectorWidth = 144;
|
||||
var scaleSelectorHeight = 37;
|
||||
|
||||
var scaleSelectorX = windowDimensions.x / 5.0;
|
||||
var scaleSelectorY = windowDimensions.y - scaleSelectorHeight;
|
||||
|
||||
var voxelTool = Overlays.addOverlay("image", {
|
||||
x: scaleSelectorX + scaleSelectorWidth + 1, y: windowDimensions.y - toolHeight, width: toolWidth, height: toolHeight,
|
||||
subImage: { x: 0, y: toolHeight, width: toolWidth, height: toolHeight },
|
||||
imageURL: toolIconUrl + "voxel-tool.svg",
|
||||
visible: editToolsOn,
|
||||
color: UIColor,
|
||||
alpha: 0.9
|
||||
});
|
||||
|
||||
var copyScale = true;
|
||||
function ScaleSelector() {
|
||||
this.x = scaleSelectorX;
|
||||
this.y = scaleSelectorY;
|
||||
this.width = scaleSelectorWidth;
|
||||
this.height = scaleSelectorHeight;
|
||||
|
||||
this.displayPower = false;
|
||||
this.scale = 1.0;
|
||||
this.power = 0;
|
||||
|
||||
this.FIRST_PART = this.width * 40.0 / 100.0;
|
||||
this.SECOND_PART = this.width * 37.0 / 100.0;
|
||||
|
||||
this.buttonsOverlay = Overlays.addOverlay("image", {
|
||||
x: this.x, y: this.y,
|
||||
width: this.width, height: this.height,
|
||||
//subImage: { x: 0, y: toolHeight, width: toolWidth, height: toolHeight },
|
||||
imageURL: toolIconUrl + "voxel-size-selector.svg",
|
||||
alpha: 0.9,
|
||||
visible: editToolsOn,
|
||||
color: activeUIColor
|
||||
});
|
||||
this.textOverlay = Overlays.addOverlay("text", {
|
||||
x: this.x + this.FIRST_PART, y: this.y,
|
||||
width: this.SECOND_PART, height: this.height,
|
||||
topMargin: 13,
|
||||
text: this.scale.toString(),
|
||||
alpha: 0.0,
|
||||
visible: editToolsOn,
|
||||
color: activeUIColor
|
||||
});
|
||||
this.powerOverlay = Overlays.addOverlay("text", {
|
||||
x: this.x + this.FIRST_PART, y: this.y,
|
||||
width: this.SECOND_PART, height: this.height,
|
||||
leftMargin: 28,
|
||||
text: this.power.toString(),
|
||||
alpha: 0.0,
|
||||
visible: false,
|
||||
color: activeUIColor
|
||||
});
|
||||
this.setScale = function(scale) {
|
||||
if (scale > MAX_VOXEL_SCALE) {
|
||||
scale = MAX_VOXEL_SCALE;
|
||||
}
|
||||
if (scale < MIN_VOXEL_SCALE) {
|
||||
scale = MIN_VOXEL_SCALE;
|
||||
}
|
||||
|
||||
this.scale = scale;
|
||||
this.power = Math.floor(Math.log(scale) / Math.log(2));
|
||||
this.update();
|
||||
}
|
||||
|
||||
this.show = function(doShow) {
|
||||
Overlays.editOverlay(this.buttonsOverlay, {visible: doShow});
|
||||
Overlays.editOverlay(this.textOverlay, {visible: doShow});
|
||||
Overlays.editOverlay(this.powerOverlay, {visible: doShow && this.displayPower});
|
||||
}
|
||||
|
||||
this.move = function() {
|
||||
this.x = swatchesX + swatchesWidth;
|
||||
this.y = swatchesY;
|
||||
|
||||
Overlays.editOverlay(this.buttonsOverlay, {
|
||||
x: this.x, y: this.y,
|
||||
});
|
||||
Overlays.editOverlay(this.textOverlay, {
|
||||
x: this.x + this.FIRST_PART, y: this.y,
|
||||
});
|
||||
Overlays.editOverlay(this.powerOverlay, {
|
||||
x: this.x + this.FIRST_PART, y: this.y,
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
this.switchDisplay = function() {
|
||||
this.displayPower = !this.displayPower;
|
||||
|
||||
if (this.displayPower) {
|
||||
Overlays.editOverlay(this.textOverlay, {
|
||||
leftMargin: 18,
|
||||
text: "2"
|
||||
});
|
||||
Overlays.editOverlay(this.powerOverlay, {
|
||||
text: this.power.toString(),
|
||||
visible: editToolsOn
|
||||
});
|
||||
} else {
|
||||
Overlays.editOverlay(this.textOverlay, {
|
||||
leftMargin: 13,
|
||||
text: this.scale.toString()
|
||||
});
|
||||
Overlays.editOverlay(this.powerOverlay, {
|
||||
visible: false
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
this.update = function() {
|
||||
if (this.displayPower) {
|
||||
Overlays.editOverlay(this.powerOverlay, {text: this.power.toString()});
|
||||
} else {
|
||||
Overlays.editOverlay(this.textOverlay, {text: this.scale.toString()});
|
||||
}
|
||||
}
|
||||
|
||||
this.incrementScale = function() {
|
||||
copyScale = false;
|
||||
if (this.power < MAX_VOXEL_SCALE_POWER) {
|
||||
++this.power;
|
||||
this.scale *= 2.0;
|
||||
this.update();
|
||||
}
|
||||
}
|
||||
|
||||
this.decrementScale = function() {
|
||||
copyScale = false;
|
||||
if (MIN_VOXEL_SCALE_POWER < this.power) {
|
||||
--this.power;
|
||||
this.scale /= 2.0;
|
||||
this.update();
|
||||
}
|
||||
}
|
||||
|
||||
this.clicked = function(x, y) {
|
||||
if (this.x < x && x < this.x + this.width &&
|
||||
this.y < y && y < this.y + this.height) {
|
||||
|
||||
if (x < this.x + this.FIRST_PART) {
|
||||
this.decrementScale();
|
||||
} else if (x < this.x + this.FIRST_PART + this.SECOND_PART) {
|
||||
this.switchDisplay();
|
||||
} else {
|
||||
this.incrementScale();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
this.cleanup = function() {
|
||||
Overlays.deleteOverlay(this.buttonsOverlay);
|
||||
Overlays.deleteOverlay(this.textOverlay);
|
||||
Overlays.deleteOverlay(this.powerOverlay);
|
||||
}
|
||||
|
||||
}
|
||||
var scaleSelector = new ScaleSelector();
|
||||
|
||||
function calculateVoxelFromIntersection(intersection, operation) {
|
||||
|
||||
var resultVoxel;
|
||||
|
||||
var x;
|
||||
var y;
|
||||
var z;
|
||||
|
||||
// if our "target voxel size" is larger than the voxel we intersected with, then we need to find the closest
|
||||
// ancestor voxel of our target size that contains our intersected voxel.
|
||||
if (voxelSize > intersection.voxel.s) {
|
||||
x = Math.floor(intersection.voxel.x / voxelSize) * voxelSize;
|
||||
y = Math.floor(intersection.voxel.y / voxelSize) * voxelSize;
|
||||
z = Math.floor(intersection.voxel.z / voxelSize) * voxelSize;
|
||||
} else {
|
||||
// otherwise, calculate the enclosed voxel of size voxelSize that the intersection point falls inside of.
|
||||
// if you have a voxelSize that's smaller than the voxel you're intersecting, this calculation will result
|
||||
// in the subvoxel that the intersection point falls in, if the target voxelSize matches the intersecting
|
||||
// voxel this still works and results in returning the intersecting voxel which is what we want
|
||||
var adjustToCenter = Vec3.multiply(Voxels.getFaceVector(intersection.face), (voxelSize * -0.5));
|
||||
|
||||
var centerOfIntersectingVoxel = Vec3.sum(intersection.intersection, adjustToCenter);
|
||||
x = Math.floor(centerOfIntersectingVoxel.x / voxelSize) * voxelSize;
|
||||
y = Math.floor(centerOfIntersectingVoxel.y / voxelSize) * voxelSize;
|
||||
z = Math.floor(centerOfIntersectingVoxel.z / voxelSize) * voxelSize;
|
||||
}
|
||||
resultVoxel = { x: x, y: y, z: z, s: voxelSize };
|
||||
var highlightAt = { x: x, y: y, z: z, s: voxelSize };
|
||||
|
||||
|
||||
|
||||
// we only do the "add to the face we're pointing at" adjustment, if the operation is an add
|
||||
// operation, and the target voxel size is equal to or smaller than the intersecting voxel.
|
||||
var wantAddAdjust = (operation == "add" && (voxelSize <= intersection.voxel.s));
|
||||
|
||||
// now we also want to calculate the "edge square" for the face for this voxel
|
||||
if (intersection.face == "MIN_X_FACE") {
|
||||
|
||||
highlightAt.x = x - zFightingSizeAdjust;
|
||||
if (wantAddAdjust) {
|
||||
resultVoxel.x -= voxelSize;
|
||||
}
|
||||
|
||||
resultVoxel.bottomLeft = {x: highlightAt.x, y: highlightAt.y + zFightingSizeAdjust, z: highlightAt.z + zFightingSizeAdjust };
|
||||
resultVoxel.bottomRight = {x: highlightAt.x, y: highlightAt.y + zFightingSizeAdjust, z: highlightAt.z + voxelSize - zFightingSizeAdjust };
|
||||
resultVoxel.topLeft = {x: highlightAt.x, y: highlightAt.y + voxelSize - zFightingSizeAdjust, z: highlightAt.z + zFightingSizeAdjust };
|
||||
resultVoxel.topRight = {x: highlightAt.x, y: highlightAt.y + voxelSize - zFightingSizeAdjust, z: highlightAt.z + voxelSize - zFightingSizeAdjust };
|
||||
|
||||
} else if (intersection.face == "MAX_X_FACE") {
|
||||
|
||||
highlightAt.x = x + voxelSize + zFightingSizeAdjust;
|
||||
if (wantAddAdjust) {
|
||||
resultVoxel.x += resultVoxel.s;
|
||||
}
|
||||
|
||||
resultVoxel.bottomRight = {x: highlightAt.x, y: highlightAt.y + zFightingSizeAdjust, z: highlightAt.z + zFightingSizeAdjust };
|
||||
resultVoxel.bottomLeft = {x: highlightAt.x, y: highlightAt.y + zFightingSizeAdjust, z: highlightAt.z + voxelSize - zFightingSizeAdjust };
|
||||
resultVoxel.topRight = {x: highlightAt.x, y: highlightAt.y + voxelSize - zFightingSizeAdjust, z: highlightAt.z + zFightingSizeAdjust };
|
||||
resultVoxel.topLeft = {x: highlightAt.x, y: highlightAt.y + voxelSize - zFightingSizeAdjust, z: highlightAt.z + voxelSize - zFightingSizeAdjust };
|
||||
|
||||
} else if (intersection.face == "MIN_Y_FACE") {
|
||||
|
||||
highlightAt.y = y - zFightingSizeAdjust;
|
||||
if (wantAddAdjust) {
|
||||
resultVoxel.y -= voxelSize;
|
||||
}
|
||||
|
||||
resultVoxel.topRight = {x: highlightAt.x + zFightingSizeAdjust , y: highlightAt.y, z: highlightAt.z + zFightingSizeAdjust };
|
||||
resultVoxel.topLeft = {x: highlightAt.x + voxelSize - zFightingSizeAdjust, y: highlightAt.y, z: highlightAt.z + zFightingSizeAdjust };
|
||||
resultVoxel.bottomRight = {x: highlightAt.x + zFightingSizeAdjust , y: highlightAt.y, z: highlightAt.z + voxelSize - zFightingSizeAdjust };
|
||||
resultVoxel.bottomLeft = {x: highlightAt.x + voxelSize - zFightingSizeAdjust , y: highlightAt.y, z: highlightAt.z + voxelSize - zFightingSizeAdjust };
|
||||
|
||||
} else if (intersection.face == "MAX_Y_FACE") {
|
||||
|
||||
highlightAt.y = y + voxelSize + zFightingSizeAdjust;
|
||||
if (wantAddAdjust) {
|
||||
resultVoxel.y += voxelSize;
|
||||
}
|
||||
|
||||
resultVoxel.bottomRight = {x: highlightAt.x + zFightingSizeAdjust, y: highlightAt.y, z: highlightAt.z + zFightingSizeAdjust };
|
||||
resultVoxel.bottomLeft = {x: highlightAt.x + voxelSize - zFightingSizeAdjust, y: highlightAt.y, z: highlightAt.z + zFightingSizeAdjust};
|
||||
resultVoxel.topRight = {x: highlightAt.x + zFightingSizeAdjust, y: highlightAt.y, z: highlightAt.z + voxelSize - zFightingSizeAdjust};
|
||||
resultVoxel.topLeft = {x: highlightAt.x + voxelSize - zFightingSizeAdjust, y: highlightAt.y, z: highlightAt.z + voxelSize - zFightingSizeAdjust};
|
||||
|
||||
} else if (intersection.face == "MIN_Z_FACE") {
|
||||
|
||||
highlightAt.z = z - zFightingSizeAdjust;
|
||||
if (wantAddAdjust) {
|
||||
resultVoxel.z -= voxelSize;
|
||||
}
|
||||
|
||||
resultVoxel.bottomRight = {x: highlightAt.x + zFightingSizeAdjust, y: highlightAt.y + zFightingSizeAdjust, z: highlightAt.z };
|
||||
resultVoxel.bottomLeft = {x: highlightAt.x + voxelSize - zFightingSizeAdjust, y: highlightAt.y + zFightingSizeAdjust, z: highlightAt.z};
|
||||
resultVoxel.topRight = {x: highlightAt.x + zFightingSizeAdjust, y: highlightAt.y + voxelSize - zFightingSizeAdjust, z: highlightAt.z };
|
||||
resultVoxel.topLeft = {x: highlightAt.x + voxelSize - zFightingSizeAdjust, y: highlightAt.y + voxelSize - zFightingSizeAdjust, z: highlightAt.z};
|
||||
|
||||
} else if (intersection.face == "MAX_Z_FACE") {
|
||||
|
||||
highlightAt.z = z + voxelSize + zFightingSizeAdjust;
|
||||
if (wantAddAdjust) {
|
||||
resultVoxel.z += voxelSize;
|
||||
}
|
||||
|
||||
resultVoxel.bottomLeft = {x: highlightAt.x + zFightingSizeAdjust, y: highlightAt.y + zFightingSizeAdjust, z: highlightAt.z };
|
||||
resultVoxel.bottomRight = {x: highlightAt.x + voxelSize - zFightingSizeAdjust, y: highlightAt.y + zFightingSizeAdjust, z: highlightAt.z};
|
||||
resultVoxel.topLeft = {x: highlightAt.x + zFightingSizeAdjust, y: highlightAt.y + voxelSize - zFightingSizeAdjust, z: highlightAt.z };
|
||||
resultVoxel.topRight = {x: highlightAt.x + voxelSize - zFightingSizeAdjust, y: highlightAt.y + voxelSize - zFightingSizeAdjust, z: highlightAt.z};
|
||||
|
||||
}
|
||||
return resultVoxel;
|
||||
}
|
||||
|
||||
var trackLastMouseX = 0;
|
||||
var trackLastMouseY = 0;
|
||||
|
||||
function showPreviewLines() {
|
||||
|
||||
var pickRay = Camera.computePickRay(trackLastMouseX, trackLastMouseY);
|
||||
|
||||
var intersection = Voxels.findRayIntersection(pickRay);
|
||||
|
||||
if (intersection.intersects) {
|
||||
var resultVoxel = calculateVoxelFromIntersection(intersection, "");
|
||||
Overlays.editOverlay(linePreviewTop, { position: resultVoxel.topLeft, end: resultVoxel.topRight, visible: true });
|
||||
Overlays.editOverlay(linePreviewBottom, { position: resultVoxel.bottomLeft, end: resultVoxel.bottomRight, visible: true });
|
||||
Overlays.editOverlay(linePreviewLeft, { position: resultVoxel.topLeft, end: resultVoxel.bottomLeft, visible: true });
|
||||
Overlays.editOverlay(linePreviewRight, { position: resultVoxel.topRight, end: resultVoxel.bottomRight, visible: true });
|
||||
} else {
|
||||
Overlays.editOverlay(linePreviewTop, { visible: false });
|
||||
Overlays.editOverlay(linePreviewBottom, { visible: false });
|
||||
Overlays.editOverlay(linePreviewLeft, { visible: false });
|
||||
Overlays.editOverlay(linePreviewRight, { visible: false });
|
||||
}
|
||||
}
|
||||
|
||||
function mouseMoveEvent(event) {
|
||||
trackLastMouseX = event.x;
|
||||
trackLastMouseY = event.y;
|
||||
if (!voxelToolSelected) {
|
||||
return;
|
||||
}
|
||||
showPreviewLines();
|
||||
}
|
||||
|
||||
var BRUSH_RADIUS = 2;
|
||||
|
||||
function mousePressEvent(event) {
|
||||
var mouseX = event.x;
|
||||
var mouseY = event.y;
|
||||
|
||||
var clickedOnSomething = false;
|
||||
// Check if we clicked an overlay
|
||||
var clickedOverlay = Overlays.getOverlayAtPoint({x: mouseX, y: mouseY});
|
||||
|
||||
if (clickedOverlay == voxelTool) {
|
||||
voxelToolSelected = !voxelToolSelected;
|
||||
|
||||
if (voxelToolSelected == true) {
|
||||
Overlays.editOverlay(voxelTool, {
|
||||
color: activeUIColor
|
||||
});
|
||||
} else {
|
||||
Overlays.editOverlay(voxelTool, {
|
||||
color: UIColor
|
||||
});
|
||||
}
|
||||
|
||||
clickedOnSomething = true;
|
||||
} else if (scaleSelector.clicked(event.x, event.y)) {
|
||||
clickedOnSomething = true;
|
||||
voxelSize = scaleSelector.scale;
|
||||
}
|
||||
|
||||
// Return if we clicked on the UI or the voxel tool is disabled
|
||||
if (clickedOnSomething || !voxelToolSelected) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Compute the picking ray for the click
|
||||
var pickRay = Camera.computePickRay(event.x, event.y);
|
||||
var intersection = Voxels.findRayIntersection(pickRay);
|
||||
var resultVoxel = calculateVoxelFromIntersection(intersection, "add");
|
||||
|
||||
|
||||
//Add a clump of sand voxels
|
||||
makeSphere(resultVoxel.x, resultVoxel.y, resultVoxel.z, voxelSize * BRUSH_RADIUS, voxelSize);
|
||||
}
|
||||
|
||||
var sandArray = [];
|
||||
var numSand = 0;
|
||||
|
||||
|
||||
//These arrays are used to buffer add/remove operations so they can be batched together
|
||||
var addArray = [];
|
||||
var addArraySize = 0;
|
||||
var removeArray = [];
|
||||
var removeArraySize = 0;
|
||||
|
||||
//The colors must be different
|
||||
var activeSandColor = { r: 234, g: 206, b: 106};
|
||||
var inactiveSandColor = { r: 233, g: 206, b: 106};
|
||||
|
||||
//This is used as an optimization, so that we
|
||||
//will check our 6 neighbors at most once.
|
||||
var adjacentVoxels = [];
|
||||
var numAdjacentVoxels = 0;
|
||||
//Stores a list of voxels we need to activate
|
||||
var activateMap = {};
|
||||
|
||||
var UPDATES_PER_SECOND = 12.0; // frames per second
|
||||
var frameIndex = 0.0;
|
||||
var oldFrameIndex = 0;
|
||||
|
||||
function update(deltaTime) {
|
||||
frameIndex += deltaTime * UPDATES_PER_SECOND;
|
||||
if (Math.floor(frameIndex) == oldFrameIndex) {
|
||||
return;
|
||||
}
|
||||
oldFrameIndex++;
|
||||
|
||||
//Clear the activate map each frame
|
||||
activateMap = {};
|
||||
|
||||
//Update all sand in our sandArray
|
||||
var i = 0;
|
||||
while (i < numSand) {
|
||||
//Update the sand voxel and if it doesn't move, deactivate it
|
||||
if (updateSand(i) == false) {
|
||||
deactivateSand(i);
|
||||
} else {
|
||||
i++;
|
||||
}
|
||||
}
|
||||
|
||||
for (var i = 0; i < removeArraySize; i++) {
|
||||
var voxel = removeArray[i];
|
||||
Voxels.eraseVoxel(voxel.x, voxel.y, voxel.z, voxel.s);
|
||||
}
|
||||
removeArraySize = 0;
|
||||
|
||||
//Add all voxels that have moved
|
||||
for (var i = 0; i < addArraySize; i++) {
|
||||
var voxel = addArray[i];
|
||||
Voxels.setVoxel(voxel.x, voxel.y, voxel.z, voxel.s, voxel.r, voxel.g, voxel.b);
|
||||
}
|
||||
addArraySize = 0;
|
||||
|
||||
for (var key in activateMap) {
|
||||
var voxel = activateMap[key];
|
||||
Voxels.setVoxel(voxel.x, voxel.y, voxel.z, voxel.s, activeSandColor.r, activeSandColor.g, activeSandColor.b);
|
||||
sandArray[numSand++] = { x: voxel.x, y: voxel.y, z: voxel.z, s: voxel.s, r: activeSandColor.r, g: activeSandColor.g, b: activeSandColor.b };
|
||||
}
|
||||
}
|
||||
|
||||
//Adds a sphere of sand at the center cx,cy,cz
|
||||
function makeSphere(cx, cy, cz, r, voxelSize) {
|
||||
|
||||
var r2 = r * r;
|
||||
var distance2;
|
||||
var dx;
|
||||
var dy;
|
||||
var dz;
|
||||
|
||||
for (var x = cx - r; x <= cx + r; x += voxelSize) {
|
||||
for (var y = cy - r; y <= cy + r; y += voxelSize) {
|
||||
for (var z = cz - r; z <= cz + r; z += voxelSize) {
|
||||
dx = Math.abs(x - cx);
|
||||
dy = Math.abs(y - cy);
|
||||
dz = Math.abs(z - cz);
|
||||
distance2 = dx * dx + dy * dy + dz * dz;
|
||||
if (distance2 <= r2 && isVoxelEmpty(x, y, z, voxelSize)) {
|
||||
Voxels.setVoxel(x, y, z, voxelSize, activeSandColor.r, activeSandColor.g, activeSandColor.b);
|
||||
sandArray[numSand++] = { x: x, y: y, z: z, s: voxelSize, r: activeSandColor.r, g: activeSandColor.g, b: activeSandColor.b };
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//Check if a given voxel is empty
|
||||
function isVoxelEmpty(x, y, z, s, isAdjacent) {
|
||||
var halfSize = s / 2;
|
||||
var point = {x: x + halfSize, y: y + halfSize, z: z + halfSize };
|
||||
|
||||
var adjacent = Voxels.getVoxelEnclosingPointBlocking(point);
|
||||
//If color is all 0, we assume its air.
|
||||
|
||||
if (adjacent.red == 0 && adjacent.green == 0 && adjacent.blue == 0) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (isAdjacent) {
|
||||
adjacentVoxels[numAdjacentVoxels++] = adjacent;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
//Moves voxel to x,y,z if the space is empty
|
||||
function tryMoveVoxel(voxel, x, y, z) {
|
||||
//If the adjacent voxel is empty, we will move to it.
|
||||
if (isVoxelEmpty(x, y, z, voxel.s, false)) {
|
||||
var hsize = voxel.s / 2;
|
||||
for (var i = 0; i < 5; i++) {
|
||||
var point = {x: voxel.x + directionVecs[i].x * voxel.s + hsize, y: voxel.y + directionVecs[i].y * voxel.s + hsize, z: voxel.z + directionVecs[i].z * voxel.s + hsize };
|
||||
adjacentVoxels[numAdjacentVoxels++] = Voxels.getVoxelEnclosingPointBlocking(point);
|
||||
}
|
||||
moveVoxel(voxel, x, y, z);
|
||||
|
||||
//Get all adjacent voxels for activation
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
//Moves voxel to x,y,z
|
||||
function moveVoxel(voxel, x, y, z) {
|
||||
activateNeighbors();
|
||||
removeArray[removeArraySize++] = {x: voxel.x, y: voxel.y, z: voxel.z, s: voxel.s};
|
||||
addArray[addArraySize++] = {x: x, y: y, z: z, s: voxel.s, r: activeSandColor.r, g: activeSandColor.g, b: activeSandColor.b};
|
||||
voxel.x = x;
|
||||
voxel.y = y;
|
||||
voxel.z = z;
|
||||
}
|
||||
|
||||
var LEFT = 0;
|
||||
var BACK = 1;
|
||||
var RIGHT = 2;
|
||||
var FRONT = 3;
|
||||
var TOP = 4;
|
||||
|
||||
//These indicate the different directions to neighbor voxels, so we can iterate them
|
||||
var directionVecs = [];
|
||||
directionVecs[LEFT] = {x: -1, y: 0, z: 0}; //Left
|
||||
directionVecs[BACK] = {x: 0, y: 0, z: -1}; //Back
|
||||
directionVecs[RIGHT] = {x: 1, y: 0, z: 0}; //Right
|
||||
directionVecs[FRONT] = {x: 0, y: 0, z: 1}; //Front
|
||||
directionVecs[TOP] = {x: 0, y: 1, z: 0}; //Top
|
||||
|
||||
function updateSand(i) {
|
||||
var voxel = sandArray[i];
|
||||
var size = voxel.s;
|
||||
var hsize = size / 2;
|
||||
numAdjacentVoxels = 0;
|
||||
|
||||
//Down
|
||||
if (tryMoveVoxel(voxel, voxel.x, voxel.y - size, voxel.z)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
//Left, back, right, front
|
||||
for (var i = 0; i < 4; i++) {
|
||||
if (isVoxelEmpty(voxel.x + directionVecs[i].x * size, voxel.y + directionVecs[i].y * size, voxel.z + directionVecs[i].z * size, size, true)
|
||||
&& isVoxelEmpty(voxel.x + directionVecs[i].x * size, (voxel.y - size) + directionVecs[i].y * size, voxel.z + directionVecs[i].z * size, size, false)) {
|
||||
//get the rest of the adjacent voxels
|
||||
for (var j = i + 1; j < 5; j++) {
|
||||
var point = {x: voxel.x + directionVecs[j].x * size + hsize, y: voxel.y + directionVecs[j].y * size + hsize, z: voxel.z + directionVecs[j].z * size + hsize };
|
||||
adjacentVoxels[numAdjacentVoxels++] = Voxels.getVoxelEnclosingPointBlocking(point);
|
||||
}
|
||||
moveVoxel(voxel, voxel.x + directionVecs[i].x * size, voxel.y + directionVecs[i].y * size, voxel.z + directionVecs[i].z * size);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
function activateNeighbors() {
|
||||
for (var i = 0; i < numAdjacentVoxels; i++) {
|
||||
var voxel = adjacentVoxels[i];
|
||||
//Check if this neighbor is inactive, if so, activate it
|
||||
if (voxel.red == inactiveSandColor.r && voxel.green == inactiveSandColor.g && voxel.blue == inactiveSandColor.b) {
|
||||
activateMap[voxel.x.toString() + "," + voxel.y.toString() + ',' + voxel.z.toString()] = voxel;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//Deactivates a sand voxel to save processing power
|
||||
function deactivateSand(i) {
|
||||
var voxel = sandArray[i];
|
||||
addArray[addArraySize++] = {x: voxel.x, y: voxel.y, z: voxel.z, s: voxel.s, r: inactiveSandColor.r, g: inactiveSandColor.g, b: inactiveSandColor.b};
|
||||
sandArray[i] = sandArray[numSand-1];
|
||||
numSand--;
|
||||
}
|
||||
|
||||
//Cleanup
|
||||
function scriptEnding() {
|
||||
for (var i = 0; i < numSand; i++) {
|
||||
var voxel = sandArray[i];
|
||||
Voxels.eraseVoxel(voxel.x, voxel.y, voxel.z, voxel.s);
|
||||
}
|
||||
Overlays.deleteOverlay(linePreviewTop);
|
||||
Overlays.deleteOverlay(linePreviewBottom);
|
||||
Overlays.deleteOverlay(linePreviewLeft);
|
||||
Overlays.deleteOverlay(linePreviewRight);
|
||||
scaleSelector.cleanup();
|
||||
Overlays.deleteOverlay(voxelTool);
|
||||
}
|
||||
|
||||
Controller.mousePressEvent.connect(mousePressEvent);
|
||||
Controller.mouseMoveEvent.connect(mouseMoveEvent);
|
||||
|
||||
Script.update.connect(update);
|
||||
Script.scriptEnding.connect(scriptEnding);
|
||||
|
||||
Voxels.setMaxPacketSize(1); //this is needed or a bug occurs :(
|
||||
Voxels.setPacketsPerSecond(10000);
|
358
examples/grenadeLauncher.js
Normal file
|
@ -0,0 +1,358 @@
|
|||
//
|
||||
// grenadeLauncher.js
|
||||
// examples
|
||||
// Created by Ben Arnold on 7/11/14.
|
||||
// This is a modified version of gun.js by Brad Hefta-Gaub.
|
||||
//
|
||||
// Copyright 2013 High Fidelity, Inc.
|
||||
//
|
||||
// This is an example script that turns the hydra controllers and mouse into a particle gun.
|
||||
// It reads the controller, watches for trigger pulls, and launches particles.
|
||||
// When particles collide with voxels they blow big holes out of the voxels.
|
||||
//
|
||||
// Distributed under the Apache License, Version 2.0.
|
||||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
//
|
||||
|
||||
function getRandomFloat(min, max) {
|
||||
return Math.random() * (max - min) + min;
|
||||
}
|
||||
|
||||
var lastX = 0;
|
||||
var lastY = 0;
|
||||
var yawFromMouse = 0;
|
||||
var pitchFromMouse = 0;
|
||||
var isMouseDown = false;
|
||||
|
||||
var BULLET_VELOCITY = 3.0;
|
||||
var MIN_THROWER_DELAY = 1000;
|
||||
var MAX_THROWER_DELAY = 1000;
|
||||
var LEFT_BUTTON_1 = 1;
|
||||
var LEFT_BUTTON_3 = 3;
|
||||
var RELOAD_INTERVAL = 5;
|
||||
|
||||
var showScore = false;
|
||||
|
||||
// Load some sound to use for loading and firing
|
||||
var fireSound = new Sound("https://s3-us-west-1.amazonaws.com/highfidelity-public/sounds/Guns/GUN-SHOT2.raw");
|
||||
var loadSound = new Sound("https://s3-us-west-1.amazonaws.com/highfidelity-public/sounds/Guns/Gun_Reload_Weapon22.raw");
|
||||
var impactSound = new Sound("https://s3-us-west-1.amazonaws.com/highfidelity-public/sounds/Guns/BulletImpact2.raw");
|
||||
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://public.highfidelity.io/models/attachments/HaloGun.fst";
|
||||
|
||||
var audioOptions = new AudioInjectionOptions();
|
||||
audioOptions.volume = 0.9;
|
||||
|
||||
var shotsFired = 0;
|
||||
|
||||
var shotTime = new Date();
|
||||
|
||||
// initialize our triggers
|
||||
var triggerPulled = new Array();
|
||||
var numberOfTriggers = Controller.getNumberOfTriggers();
|
||||
for (t = 0; t < numberOfTriggers; t++) {
|
||||
triggerPulled[t] = false;
|
||||
}
|
||||
|
||||
var isLaunchButtonPressed = false;
|
||||
|
||||
var score = 0;
|
||||
|
||||
// Create a reticle image in center of screen
|
||||
var screenSize = Controller.getViewportDimensions();
|
||||
var reticle = Overlays.addOverlay("image", {
|
||||
x: screenSize.x / 2 - 16,
|
||||
y: screenSize.y / 2 - 16,
|
||||
width: 32,
|
||||
height: 32,
|
||||
imageURL: "https://s3-us-west-1.amazonaws.com/highfidelity-public/images/reticle.png",
|
||||
color: { red: 255, green: 255, blue: 255},
|
||||
alpha: 1
|
||||
});
|
||||
|
||||
if (showScore) {
|
||||
var text = Overlays.addOverlay("text", {
|
||||
x: screenSize.x / 2 - 100,
|
||||
y: screenSize.y / 2 - 50,
|
||||
width: 150,
|
||||
height: 50,
|
||||
color: { red: 0, green: 0, blue: 0},
|
||||
textColor: { red: 255, green: 0, blue: 0},
|
||||
topMargin: 4,
|
||||
leftMargin: 4,
|
||||
text: "Score: " + score
|
||||
});
|
||||
}
|
||||
|
||||
function printVector(string, vector) {
|
||||
print(string + " " + vector.x + ", " + vector.y + ", " + vector.z);
|
||||
}
|
||||
|
||||
function shootBullet(position, velocity) {
|
||||
var BULLET_SIZE = 0.1;
|
||||
var BULLET_GRAVITY = -3.0;
|
||||
//Creates a grenade with a reasonable lifetime so that one is less likely to accidentally blow up
|
||||
//far away voxels
|
||||
Particles.addParticle(
|
||||
{ position: position,
|
||||
radius: BULLET_SIZE,
|
||||
color: { red: 10, green: 10, blue: 10 },
|
||||
velocity: velocity,
|
||||
gravity: { x: 0, y: BULLET_GRAVITY, z: 0 },
|
||||
lifetime: 10.0,
|
||||
damping: 0 });
|
||||
|
||||
// Play firing sounds
|
||||
audioOptions.position = position;
|
||||
Audio.playSound(fireSound, audioOptions);
|
||||
shotsFired++;
|
||||
if ((shotsFired % RELOAD_INTERVAL) == 0) {
|
||||
Audio.playSound(loadSound, audioOptions);
|
||||
}
|
||||
}
|
||||
|
||||
function shootTarget() {
|
||||
var TARGET_SIZE = 0.25;
|
||||
var TARGET_GRAVITY = -0.6;
|
||||
var TARGET_UP_VELOCITY = 3.0;
|
||||
var TARGET_FWD_VELOCITY = 5.0;
|
||||
var DISTANCE_TO_LAUNCH_FROM = 3.0;
|
||||
var camera = Camera.getPosition();
|
||||
//printVector("camera", camera);
|
||||
var targetDirection = Quat.angleAxis(getRandomFloat(-20.0, 20.0), { x:0, y:1, z:0 });
|
||||
targetDirection = Quat.multiply(Camera.getOrientation(), targetDirection);
|
||||
var forwardVector = Quat.getFront(targetDirection);
|
||||
//printVector("forwardVector", forwardVector);
|
||||
var newPosition = Vec3.sum(camera, Vec3.multiply(forwardVector, DISTANCE_TO_LAUNCH_FROM));
|
||||
//printVector("newPosition", newPosition);
|
||||
var velocity = Vec3.multiply(forwardVector, TARGET_FWD_VELOCITY);
|
||||
velocity.y += TARGET_UP_VELOCITY;
|
||||
//printVector("velocity", velocity);
|
||||
|
||||
Particles.addParticle(
|
||||
{ position: newPosition,
|
||||
radius: TARGET_SIZE,
|
||||
color: { red: 0, green: 200, blue: 200 },
|
||||
velocity: velocity,
|
||||
gravity: { x: 0, y: TARGET_GRAVITY, z: 0 },
|
||||
lifetime: 1000.0,
|
||||
damping: 0.99 });
|
||||
|
||||
// Record start time
|
||||
shotTime = new Date();
|
||||
|
||||
// Play target shoot sound
|
||||
audioOptions.position = newPosition;
|
||||
Audio.playSound(targetLaunchSound, audioOptions);
|
||||
}
|
||||
|
||||
|
||||
|
||||
function particleCollisionWithVoxel(particle, voxel, collision) {
|
||||
var VOXEL_SIZE = 0.5;
|
||||
// Don't make this big. I mean it.
|
||||
var CRATER_RADIUS = 5;
|
||||
var particleProperties = Particles.getParticleProperties(particle);
|
||||
var position = particleProperties.position;
|
||||
Particles.deleteParticle(particle);
|
||||
|
||||
audioOptions.position = collision.contactPoint;
|
||||
Audio.playSound(impactSound, audioOptions);
|
||||
|
||||
// Make a crater
|
||||
var center = collision.contactPoint;
|
||||
var RADIUS = CRATER_RADIUS * VOXEL_SIZE;
|
||||
var RADIUS2 = RADIUS * RADIUS;
|
||||
var distance2;
|
||||
var dx;
|
||||
var dy;
|
||||
var dz;
|
||||
for (var x = center.x - RADIUS; x <= center.x + RADIUS; x += VOXEL_SIZE) {
|
||||
for (var y = center.y - RADIUS; y <= center.y + RADIUS; y += VOXEL_SIZE) {
|
||||
for (var z = center.z - RADIUS; z <= center.z + RADIUS; z += VOXEL_SIZE) {
|
||||
dx = Math.abs(x - center.x);
|
||||
dy = Math.abs(y - center.y);
|
||||
dz = Math.abs(z - center.z);
|
||||
distance2 = dx * dx + dy * dy + dz * dz;
|
||||
if (distance2 <= RADIUS2) {
|
||||
Voxels.eraseVoxel(x, y, z, VOXEL_SIZE);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function particleCollisionWithParticle(particle1, particle2, collision) {
|
||||
score++;
|
||||
if (showScore) {
|
||||
Overlays.editOverlay(text, { text: "Score: " + score } );
|
||||
}
|
||||
|
||||
// Record shot time
|
||||
var endTime = new Date();
|
||||
var msecs = endTime.valueOf() - shotTime.valueOf();
|
||||
//print("hit, msecs = " + msecs);
|
||||
//Vec3.print("penetration = ", collision.penetration);
|
||||
//Vec3.print("contactPoint = ", collision.contactPoint);
|
||||
Particles.deleteParticle(particle1);
|
||||
Particles.deleteParticle(particle2);
|
||||
// play the sound near the camera so the shooter can hear it
|
||||
audioOptions.position = Vec3.sum(Camera.getPosition(), Quat.getFront(Camera.getOrientation()));
|
||||
Audio.playSound(targetHitSound, audioOptions);
|
||||
}
|
||||
|
||||
function keyPressEvent(event) {
|
||||
// if our tools are off, then don't do anything
|
||||
if (event.text == "t") {
|
||||
var time = MIN_THROWER_DELAY + Math.random() * MAX_THROWER_DELAY;
|
||||
Script.setTimeout(shootTarget, time);
|
||||
} 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 controller or mouse
|
||||
var newOrientation = Quat.multiply(MyAvatar.orientation, Quat.fromVec3Radians( { x: 0, y: yawFromMouse, z: 0 } ));
|
||||
MyAvatar.orientation = newOrientation;
|
||||
yawFromMouse = 0;
|
||||
|
||||
// apply pitch from controller or mouse
|
||||
var newPitch = MyAvatar.headPitch + pitchFromMouse;
|
||||
MyAvatar.headPitch = newPitch;
|
||||
pitchFromMouse = 0;
|
||||
|
||||
// Check hydra controller for launch button press
|
||||
if (!isLaunchButtonPressed && Controller.isButtonPressed(LEFT_BUTTON_3)) {
|
||||
isLaunchButtonPressed = true;
|
||||
var time = MIN_THROWER_DELAY + Math.random() * MAX_THROWER_DELAY;
|
||||
Script.setTimeout(shootTarget, time);
|
||||
} else if (isLaunchButtonPressed && !Controller.isButtonPressed(LEFT_BUTTON_3)) {
|
||||
isLaunchButtonPressed = false;
|
||||
|
||||
}
|
||||
|
||||
// Check hydra controller for trigger press
|
||||
|
||||
var numberOfTriggers = Controller.getNumberOfTriggers();
|
||||
var numberOfSpatialControls = Controller.getNumberOfSpatialControls();
|
||||
var controllersPerTrigger = numberOfSpatialControls / numberOfTriggers;
|
||||
|
||||
// this is expected for hydras
|
||||
if (numberOfTriggers == 2 && controllersPerTrigger == 2) {
|
||||
for (var t = 0; t < numberOfTriggers; t++) {
|
||||
var shootABullet = false;
|
||||
var triggerValue = Controller.getTriggerValue(t);
|
||||
|
||||
if (triggerPulled[t]) {
|
||||
// must release to at least 0.1
|
||||
if (triggerValue < 0.1) {
|
||||
triggerPulled[t] = false; // unpulled
|
||||
}
|
||||
} else {
|
||||
// must pull to at least 0.9
|
||||
if (triggerValue > 0.9) {
|
||||
triggerPulled[t] = true; // pulled
|
||||
shootABullet = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (shootABullet) {
|
||||
|
||||
var palmController = t * controllersPerTrigger;
|
||||
var palmPosition = Controller.getSpatialControlPosition(palmController);
|
||||
|
||||
var fingerTipController = palmController + 1;
|
||||
var fingerTipPosition = Controller.getSpatialControlPosition(fingerTipController);
|
||||
|
||||
var palmToFingerTipVector =
|
||||
{ x: (fingerTipPosition.x - palmPosition.x),
|
||||
y: (fingerTipPosition.y - palmPosition.y),
|
||||
z: (fingerTipPosition.z - palmPosition.z) };
|
||||
|
||||
// just off the front of the finger tip
|
||||
var position = { x: fingerTipPosition.x + palmToFingerTipVector.x/2,
|
||||
y: fingerTipPosition.y + palmToFingerTipVector.y/2,
|
||||
z: fingerTipPosition.z + palmToFingerTipVector.z/2};
|
||||
|
||||
var linearVelocity = 25;
|
||||
|
||||
var velocity = { x: palmToFingerTipVector.x * linearVelocity,
|
||||
y: palmToFingerTipVector.y * linearVelocity,
|
||||
z: palmToFingerTipVector.z * linearVelocity };
|
||||
|
||||
shootBullet(position, velocity);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function mousePressEvent(event) {
|
||||
isMouseDown = true;
|
||||
lastX = event.x;
|
||||
lastY = event.y;
|
||||
//audioOptions.position = Vec3.sum(Camera.getPosition(), Quat.getFront(Camera.getOrientation()));
|
||||
//Audio.playSound(loadSound, audioOptions);
|
||||
}
|
||||
|
||||
function shootFromMouse() {
|
||||
var DISTANCE_FROM_CAMERA = 2.0;
|
||||
var camera = Camera.getPosition();
|
||||
var forwardVector = Quat.getFront(Camera.getOrientation());
|
||||
var newPosition = Vec3.sum(camera, Vec3.multiply(forwardVector, DISTANCE_FROM_CAMERA));
|
||||
var velocity = Vec3.multiply(forwardVector, BULLET_VELOCITY);
|
||||
shootBullet(newPosition, velocity);
|
||||
}
|
||||
|
||||
function mouseReleaseEvent(event) {
|
||||
// position
|
||||
isMouseDown = false;
|
||||
}
|
||||
|
||||
function mouseMoveEvent(event) {
|
||||
//Move the camera if LEFT_BUTTON_1 is pressed
|
||||
if (Controller.isButtonPressed(LEFT_BUTTON_1)) {
|
||||
var MOUSE_YAW_SCALE = -0.25;
|
||||
var MOUSE_PITCH_SCALE = -12.5;
|
||||
var FIXED_MOUSE_TIMESTEP = 0.016;
|
||||
yawFromMouse += ((event.x - lastX) * MOUSE_YAW_SCALE * FIXED_MOUSE_TIMESTEP);
|
||||
pitchFromMouse += ((event.y - lastY) * MOUSE_PITCH_SCALE * FIXED_MOUSE_TIMESTEP);
|
||||
lastX = event.x;
|
||||
lastY = event.y;
|
||||
}
|
||||
}
|
||||
|
||||
function scriptEnding() {
|
||||
Overlays.deleteOverlay(reticle);
|
||||
Overlays.deleteOverlay(text);
|
||||
MyAvatar.detachOne(gunModel);
|
||||
}
|
||||
|
||||
Particles.particleCollisionWithVoxel.connect(particleCollisionWithVoxel);
|
||||
Particles.particleCollisionWithParticle.connect(particleCollisionWithParticle);
|
||||
Script.scriptEnding.connect(scriptEnding);
|
||||
Script.update.connect(update);
|
||||
Controller.mousePressEvent.connect(mousePressEvent);
|
||||
Controller.mouseReleaseEvent.connect(mouseReleaseEvent);
|
||||
Controller.mouseMoveEvent.connect(mouseMoveEvent);
|
||||
Controller.keyPressEvent.connect(keyPressEvent);
|
||||
|
||||
|
||||
|
258
examples/leapOfFaith.js
Normal file
|
@ -0,0 +1,258 @@
|
|||
//
|
||||
// leapOfFaith.js
|
||||
// examples
|
||||
//
|
||||
// Created by Sam Cake on 6/22/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
|
||||
//
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
var jointList = MyAvatar.getJointNames();
|
||||
var jointMappings = "\n# Joint list start";
|
||||
for (var i = 0; i < jointList.length; i++) {
|
||||
jointMappings = jointMappings + "\njointIndex = " + jointList[i] + " = " + i;
|
||||
}
|
||||
print(jointMappings + "\n# Joint list end");
|
||||
|
||||
function vec3ToString( v ) {
|
||||
return ("(" + v.x +", " + v.y + ", " + v.z + ")" );
|
||||
}
|
||||
function quatToString( q ) {
|
||||
return ("(" + q.x +", " + q.y + ", " + q.z + ", " + q.w + ")" );
|
||||
}
|
||||
|
||||
function printSpatialEvent( label, spatialEvent ) {
|
||||
if ( false ){//label == "RightHandIndex1" ) {
|
||||
var dataString = label + " " +
|
||||
/*vec3ToString( spatialEvent.locTranslation ) + " " +
|
||||
quatToString( spatialEvent.locRotation ) + " " +*/
|
||||
vec3ToString( spatialEvent.absTranslation ) + " " +
|
||||
quatToString( spatialEvent.absRotation );
|
||||
print( dataString );
|
||||
}
|
||||
}
|
||||
|
||||
function avatarToWorldPos( apos ) {
|
||||
|
||||
// apply offset ?
|
||||
var offset = { x: 0, y: 0.5, z: -0.5 };
|
||||
var lpos = Vec3.sum(apos, offset);
|
||||
|
||||
var wpos = Vec3.sum( MyAvatar.position , Vec3.multiplyQbyV(MyAvatar.orientation, lpos) );
|
||||
|
||||
return wpos;
|
||||
}
|
||||
|
||||
function avatarToWorldQuat( aori) {
|
||||
|
||||
var wori = Quat.multiply(MyAvatar.orientation, aori);
|
||||
return wori;
|
||||
}
|
||||
|
||||
function controlerToSkeletonOri( jointName, isRightSide, event ) {
|
||||
|
||||
var qAvatarRootOffset = Quat.angleAxis( -180, {x:0, y:1, z:0});
|
||||
var qAxisOffset = Quat.angleAxis( -( 2 * isRightSide - 1) * 90, {x:0, y:1, z:0});
|
||||
var qAbsJoint = event.absRotation;
|
||||
|
||||
|
||||
return Quat.multiply( qAvatarRootOffset, Quat.multiply( qAbsJoint, qAxisOffset ) );
|
||||
}
|
||||
|
||||
|
||||
var jointParticles = [];
|
||||
function updateJointParticle( joint, pos, ori, look ) {
|
||||
/* print( "debug 1" );
|
||||
var jointID = jointParticles[ joint ];
|
||||
if ( jointID == null ) {
|
||||
print( "debug create " + joint );
|
||||
*/
|
||||
var radius = 0.005* look.r;
|
||||
var ballProperties = {
|
||||
position: pos,
|
||||
velocity: { x: 0, y: 0, z: 0},
|
||||
gravity: { x: 0, y: 0, z: 0 },
|
||||
damping: 0,
|
||||
radius : radius,
|
||||
color: look.c,
|
||||
lifetime: 0.05
|
||||
};
|
||||
var atomPos = Particles.addParticle(ballProperties);
|
||||
|
||||
/* // Zaxis
|
||||
var Zaxis = Vec3.multiply( Quat.getFront( ori ), - 1.5 * radius ) ;
|
||||
ballProperties.position = Vec3.sum(pos, Zaxis );
|
||||
ballProperties.radius = 0.35* radius;
|
||||
ballProperties.color= { red: 255, green: 255, blue: 255 };
|
||||
|
||||
var atomZ = Particles.addParticle(ballProperties);
|
||||
|
||||
var up = Vec3.multiply( Quat.getUp( ori ), 1.5 * radius ) ;
|
||||
ballProperties.position = Vec3.sum(pos, up) ;
|
||||
ballProperties.radius = 0.35* radius;
|
||||
ballProperties.color= { red: 0, green: 255, blue: 0 };
|
||||
|
||||
var atomY = Particles.addParticle(ballProperties);
|
||||
|
||||
var right = Vec3.multiply( Quat.getRight( ori ), 1.5 * radius ) ;
|
||||
ballProperties.position = Vec3.sum(pos, right) ;
|
||||
ballProperties.radius = 0.35* radius;
|
||||
ballProperties.color= { red: 255, green: 0, blue: 225 };
|
||||
|
||||
var atomX = Particles.addParticle(ballProperties);
|
||||
*/
|
||||
// jointParticles[ joint ] = { p: atomPos, x: atomX, y: atomY, z: atomZ };
|
||||
/*
|
||||
} else {
|
||||
//print( "debug update " + joint );
|
||||
|
||||
var p = Particles.getParticleProperties( jointID.p );
|
||||
p.position = pos;
|
||||
// p.lifetime = 1.0;
|
||||
Particles.editParticle( jointID.p, p );
|
||||
|
||||
|
||||
}*/
|
||||
}
|
||||
|
||||
function evalArmBoneLook( isRightSide, bone ) {
|
||||
return { c: { red: (255 * ( 1 - isRightSide )),
|
||||
green: 255 * ( ((bone)) / 2 ),
|
||||
blue: (255 * isRightSide) },
|
||||
r: 3 ,
|
||||
side: isRightSide };
|
||||
}
|
||||
|
||||
function evalFingerBoneLook( isRightSide, finger, bone ) {
|
||||
return { c: { red: (255 * ( 1 - isRightSide )),
|
||||
green: 255 * ( ((bone - 1)) / 3 ),
|
||||
blue: (255 * isRightSide) },
|
||||
r: (5 + (5 - (finger-1))) / 10.0,
|
||||
side: isRightSide };
|
||||
}
|
||||
|
||||
var leapJoints = [
|
||||
|
||||
{ n: "joint_L_elbow", l: evalArmBoneLook( 0, 2) },
|
||||
{ n: "joint_L_hand", l: evalArmBoneLook( 0, 1) },
|
||||
{ n: "joint_L_wrist", l: evalArmBoneLook( 0, 0) },
|
||||
|
||||
{ n: "joint_L_thumb2", l: evalFingerBoneLook( 0, 1, 2) },
|
||||
{ n: "joint_L_thumb3", l: evalFingerBoneLook( 0, 1, 3) },
|
||||
{ n: "joint_L_thumb4", l: evalFingerBoneLook( 0, 1, 4) },
|
||||
|
||||
{ n: "joint_L_index1", l: evalFingerBoneLook( 0, 2, 1) },
|
||||
{ n: "joint_L_index2", l: evalFingerBoneLook( 0, 2, 2) },
|
||||
{ n: "joint_L_index3", l: evalFingerBoneLook( 0, 2, 3) },
|
||||
{ n: "joint_L_index4", l: evalFingerBoneLook( 0, 2, 4) },
|
||||
|
||||
{ n: "joint_L_middle1", l: evalFingerBoneLook( 0, 3, 1) },
|
||||
{ n: "joint_L_middle2", l: evalFingerBoneLook( 0, 3, 2) },
|
||||
{ n: "joint_L_middle3", l: evalFingerBoneLook( 0, 3, 3) },
|
||||
{ n: "joint_L_middle4", l: evalFingerBoneLook( 0, 3, 4) },
|
||||
|
||||
{ n: "joint_L_ring1", l: evalFingerBoneLook( 0, 4, 1) },
|
||||
{ n: "joint_L_ring2", l: evalFingerBoneLook( 0, 4, 2) },
|
||||
{ n: "joint_L_ring3", l: evalFingerBoneLook( 0, 4, 3) },
|
||||
{ n: "joint_L_ring4", l: evalFingerBoneLook( 0, 4, 4) },
|
||||
|
||||
{ n: "joint_L_pinky1", l: evalFingerBoneLook( 0, 5, 1) },
|
||||
{ n: "joint_L_pinky2", l: evalFingerBoneLook( 0, 5, 2) },
|
||||
{ n: "joint_L_pinky3", l: evalFingerBoneLook( 0, 5, 3) },
|
||||
{ n: "joint_L_pinky4", l: evalFingerBoneLook( 0, 5, 4) },
|
||||
|
||||
{ n: "joint_R_elbow", l: evalArmBoneLook( 1, 2) },
|
||||
{ n: "joint_R_hand", l: evalArmBoneLook( 1, 1) },
|
||||
{ n: "joint_R_wrist", l: evalArmBoneLook( 1, 0) },
|
||||
|
||||
{ n: "joint_R_thumb2", l: evalFingerBoneLook( 1, 1, 2) },
|
||||
{ n: "joint_R_thumb3", l: evalFingerBoneLook( 1, 1, 3) },
|
||||
{ n: "joint_R_thumb4", l: evalFingerBoneLook( 1, 1, 4) },
|
||||
|
||||
{ n: "joint_R_index1", l: evalFingerBoneLook( 1, 2, 1) },
|
||||
{ n: "joint_R_index2", l: evalFingerBoneLook( 1, 2, 2) },
|
||||
{ n: "joint_R_index3", l: evalFingerBoneLook( 1, 2, 3) },
|
||||
{ n: "joint_R_index4", l: evalFingerBoneLook( 1, 2, 4) },
|
||||
|
||||
{ n: "joint_R_middle1", l: evalFingerBoneLook( 1, 3, 1) },
|
||||
{ n: "joint_R_middle2", l: evalFingerBoneLook( 1, 3, 2) },
|
||||
{ n: "joint_R_middle3", l: evalFingerBoneLook( 1, 3, 3) },
|
||||
{ n: "joint_R_middle4", l: evalFingerBoneLook( 1, 3, 4) },
|
||||
|
||||
{ n: "joint_R_ring1", l: evalFingerBoneLook( 1, 4, 1) },
|
||||
{ n: "joint_R_ring2", l: evalFingerBoneLook( 1, 4, 2) },
|
||||
{ n: "joint_R_ring3", l: evalFingerBoneLook( 1, 4, 3) },
|
||||
{ n: "joint_R_ring4", l: evalFingerBoneLook( 1, 4, 4) },
|
||||
|
||||
{ n: "joint_R_pinky1", l: evalFingerBoneLook( 1, 5, 1) },
|
||||
{ n: "joint_R_pinky2", l: evalFingerBoneLook( 1, 5, 2) },
|
||||
{ n: "joint_R_pinky3", l: evalFingerBoneLook( 1, 5, 3) },
|
||||
{ n: "joint_R_pinky4", l: evalFingerBoneLook( 1, 5, 4) },
|
||||
|
||||
];
|
||||
|
||||
function onSpatialEventHandler( jointName, look ) {
|
||||
var _jointName = jointName;
|
||||
var _look = look;
|
||||
var _side = look.side;
|
||||
return (function( spatialEvent ) {
|
||||
|
||||
// THis should be the call to update the skeleton joint from
|
||||
// the absolute joint transform coming from the Leap controller
|
||||
// WE need a new method on MyAvatar which will ultimately call
|
||||
// state.setRotationFromBindFrame(rotation, priority) in
|
||||
// Avatar::simulate=>void Model::setJointState(int index, bool valid, const glm::quat& rotation, float priority)
|
||||
|
||||
// MyAvatar.setJointRotationFromBindSpace(_jointName, controlerToSkeletonOri( _jointName, _side, spatialEvent ));
|
||||
|
||||
|
||||
updateJointParticle(_jointName,
|
||||
avatarToWorldPos( spatialEvent.absTranslation ),
|
||||
avatarToWorldQuat( spatialEvent.absRotation ),
|
||||
_look );
|
||||
printSpatialEvent(_jointName, spatialEvent );
|
||||
});
|
||||
}
|
||||
|
||||
var isPullingSpatialData = true;
|
||||
|
||||
var jointControllers = [];
|
||||
for ( i in leapJoints ) {
|
||||
|
||||
print( leapJoints[i].n );
|
||||
// In the current implementation, the LEapmotion is the only "Spatial" device
|
||||
// Each jointTracker is retreived from the joint name following the rigging convention
|
||||
var controller = Controller.createInputController( "Spatial", leapJoints[i].n );
|
||||
var handler = onSpatialEventHandler( leapJoints[i].n, leapJoints[i].l );
|
||||
jointControllers.push( { c: controller, h: handler } );
|
||||
|
||||
if ( ! isPullingSpatialData ) {
|
||||
controller.spatialEvent.connect( handler );
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Script.update.connect(function(deltaTime) {
|
||||
|
||||
if ( isPullingSpatialData )
|
||||
{
|
||||
for ( i in jointControllers ) {
|
||||
if ( jointControllers[i].c.isActive() ) {
|
||||
var spatialEvent = { absTranslation: jointControllers[i].c.getAbsTranslation(),
|
||||
absRotation: jointControllers[i].c.getAbsRotation(),
|
||||
locTranslation: jointControllers[i].c.getLocTranslation(),
|
||||
locRotation: jointControllers[i].c.getLocRotation() };
|
||||
jointControllers[i].h( spatialEvent );
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
Script.scriptEnding.connect(function() {
|
||||
});
|
BIN
images/bg_hr.png
Normal file
After Width: | Height: | Size: 943 B |
BIN
images/blacktocat.png
Normal file
After Width: | Height: | Size: 1.4 KiB |
BIN
images/body-bg.png
Normal file
After Width: | Height: | Size: 8.7 KiB |
BIN
images/highlight-bg.jpg
Normal file
After Width: | Height: | Size: 33 KiB |
BIN
images/hr.png
Normal file
After Width: | Height: | Size: 1 KiB |
BIN
images/icon_download.png
Normal file
After Width: | Height: | Size: 1.1 KiB |
BIN
images/octocat-icon.png
Normal file
After Width: | Height: | Size: 1.6 KiB |
BIN
images/sprite_download.png
Normal file
After Width: | Height: | Size: 16 KiB |
BIN
images/tar-gz-icon.png
Normal file
After Width: | Height: | Size: 1.6 KiB |
BIN
images/zip-icon.png
Normal file
After Width: | Height: | Size: 1.6 KiB |
52
index.html
Normal file
|
@ -0,0 +1,52 @@
|
|||
<!doctype html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta http-equiv="X-UA-Compatible" content="chrome=1">
|
||||
<title>Hifi by highfidelity</title>
|
||||
|
||||
<link rel="stylesheet" href="stylesheets/styles.css">
|
||||
<link rel="stylesheet" href="stylesheets/pygment_trac.css">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1, user-scalable=no">
|
||||
<!--[if lt IE 9]>
|
||||
<script src="//html5shiv.googlecode.com/svn/trunk/html5.js"></script>
|
||||
<![endif]-->
|
||||
</head>
|
||||
<body>
|
||||
<div class="wrapper">
|
||||
<header>
|
||||
<h1>Hifi</h1>
|
||||
<p>Open, decentralized virtual worlds using sensors to control avatars and dynamically assigned devices as servers. San Francisco based startup, we are hiring: http://highfidelity.io/jobs You can also contribute by doing jobs listed at http://worklist.net -</p>
|
||||
|
||||
<p class="view"><a href="https://github.com/highfidelity/hifi">View the Project on GitHub <small>highfidelity/hifi</small></a></p>
|
||||
|
||||
|
||||
<ul>
|
||||
<li><a href="https://github.com/highfidelity/hifi/zipball/master">Download <strong>ZIP File</strong></a></li>
|
||||
<li><a href="https://github.com/highfidelity/hifi/tarball/master">Download <strong>TAR Ball</strong></a></li>
|
||||
<li><a href="https://github.com/highfidelity/hifi">View On <strong>GitHub</strong></a></li>
|
||||
</ul>
|
||||
</header>
|
||||
<section>
|
||||
<h3>
|
||||
<a name="avatar-documentation" class="anchor" href="#avatar-documentation"><span class="octicon octicon-link"></span></a>Avatar Documentation</h3>
|
||||
|
||||
<ul>
|
||||
<li><a href="https://github.com/highfidelity/hifi/wiki/Exporting-Your-Rigged-Avatar-From-Faceshift">Exporting your Rigged Avatar from Faceshift</a></li>
|
||||
<li><a href="https://github.com/highfidelity/hifi/wiki/Creating-Blendshapes-for-your-Avatar">Creating Blendshapes for your Avatar</a></li>
|
||||
<li><a href="https://github.com/highfidelity/hifi/wiki/How-to-Rig-a-Character-for-Faceshift">How to Rig a Character for Faceshift</a></li>
|
||||
<li><a href="https://github.com/highfidelity/hifi/wiki/Naming-Your-Skeletal-Joints">Naming your Skeletal Joints</a></li>
|
||||
<li><a href="https://github.com/highfidelity/hifi/wiki/The-FST-file">The FST File</a></li>
|
||||
<li><a href="https://github.com/highfidelity/hifi/wiki/Training-in-Faceshift">Training in Faceshift</a></li>
|
||||
<li><a href="https://github.com/highfidelity/hifi/wiki/Uploading-Your-Models">Uploading your Models</a></li>
|
||||
</ul>
|
||||
</section>
|
||||
<footer>
|
||||
<p>This project is maintained by <a href="https://github.com/highfidelity">highfidelity</a></p>
|
||||
<p><small>Hosted on GitHub Pages — Theme by <a href="https://github.com/orderedlist">orderedlist</a></small></p>
|
||||
</footer>
|
||||
</div>
|
||||
<script src="javascripts/scale.fix.js"></script>
|
||||
|
||||
</body>
|
||||
</html>
|
|
@ -18,8 +18,10 @@ 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")
|
||||
set(LEAPMOTION_ROOT_DIR "${CMAKE_CURRENT_SOURCE_DIR}/external/leapmotion")
|
||||
set(RTMIDI_ROOT_DIR "${CMAKE_CURRENT_SOURCE_DIR}/external/rtmidi")
|
||||
|
||||
|
||||
find_package(Qt5LinguistTools REQUIRED)
|
||||
find_package(Qt5LinguistToolsMacros)
|
||||
|
||||
|
@ -139,6 +141,7 @@ find_package(PrioVR)
|
|||
find_package(SDL)
|
||||
find_package(Sixense)
|
||||
find_package(Visage)
|
||||
find_package(LeapMotion)
|
||||
find_package(ZLIB)
|
||||
find_package(Qxmpp)
|
||||
find_package(RtMidi)
|
||||
|
@ -200,6 +203,17 @@ if (PRIOVR_FOUND AND NOT DISABLE_PRIOVR)
|
|||
target_link_libraries(${TARGET_NAME} "${PRIOVR_LIBRARIES}")
|
||||
endif (PRIOVR_FOUND AND NOT DISABLE_PRIOVR)
|
||||
|
||||
# and with LeapMotion library
|
||||
if (LEAPMOTION_FOUND AND NOT DISABLE_LEAPMOTION)
|
||||
add_definitions(-DHAVE_LEAPMOTION)
|
||||
include_directories(SYSTEM "${LEAPMOTION_INCLUDE_DIRS}")
|
||||
|
||||
if (APPLE OR UNIX)
|
||||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -isystem ${LEAPMOTION_INCLUDE_DIRS}")
|
||||
endif ()
|
||||
target_link_libraries(${TARGET_NAME} "${LEAPMOTION_LIBRARIES}")
|
||||
endif (LEAPMOTION_FOUND AND NOT DISABLE_LEAPMOTION)
|
||||
|
||||
# and with SDL for joysticks
|
||||
if (SDL_FOUND AND NOT DISABLE_SDL)
|
||||
add_definitions(-DHAVE_SDL)
|
||||
|
|
33
interface/external/leapmotion/readme.txt
vendored
Normal file
|
@ -0,0 +1,33 @@
|
|||
|
||||
Instructions for adding the Leap Motion library (LeapSDK) to Interface
|
||||
Sam Cake, June 10, 2014
|
||||
|
||||
You can download the Leap Developer Kit from https://developer.leapmotion.com/ (account creation required).
|
||||
Interface has been tested with SDK versions:
|
||||
- LeapDeveloperKit_2.0.3+17004_win
|
||||
- LeapDeveloperKit_2.0.3+17004_mac
|
||||
|
||||
1. Copy the LeapSDK folders from the LeapDeveloperKit installation directory (Lib, Include) into the interface/externals/leapmotion folder.
|
||||
This readme.txt should be there as well.
|
||||
|
||||
The files neeeded in the folders are:
|
||||
|
||||
include/
|
||||
- Leap.h
|
||||
- Leap.i
|
||||
- LeapMath.h
|
||||
|
||||
lib/
|
||||
x86/
|
||||
- Leap.dll
|
||||
- Leap.lib
|
||||
- mscvcp120.dll (optional if you already have the Msdev 2012 SDK redistriuable installed)
|
||||
- mscvcr120.dll (optional if you already have the Msdev 2012 SDK redistriuable installed)
|
||||
- lipLeap.dylib
|
||||
libc++/
|
||||
-libLeap.dylib
|
||||
|
||||
You may optionally choose to copy the SDK folders to a location outside the repository (so you can re-use with different checkouts and different projects).
|
||||
If so our CMake find module expects you to set the ENV variable 'HIFI_LIB_DIR' to a directory containing a subfolder 'leapmotion' that contains the 2 folders mentioned above (Include, Lib).
|
||||
|
||||
2. Clear your build directory, run cmake and build, and you should be all set.
|
|
@ -90,6 +90,8 @@
|
|||
#include "ui/Stats.h"
|
||||
#include "ui/TextRenderer.h"
|
||||
|
||||
#include "devices/Leapmotion.h"
|
||||
|
||||
using namespace std;
|
||||
|
||||
// Starfield information
|
||||
|
@ -1721,6 +1723,8 @@ void Application::init() {
|
|||
_faceplus.init();
|
||||
_visage.init();
|
||||
|
||||
Leapmotion::init();
|
||||
|
||||
// fire off an immediate domain-server check in now that settings are loaded
|
||||
NodeList::getInstance()->sendDomainServerCheckIn();
|
||||
|
||||
|
@ -2054,11 +2058,13 @@ void Application::update(float deltaTime) {
|
|||
updateMouseRay(); // check what's under the mouse and update the mouse voxel
|
||||
{
|
||||
PerformanceTimer perfTimer("devices");
|
||||
DeviceTracker::updateAll();
|
||||
updateFaceshift();
|
||||
updateVisage();
|
||||
_sixenseManager.update(deltaTime);
|
||||
_joystickManager.update();
|
||||
_prioVR.update(deltaTime);
|
||||
|
||||
}
|
||||
{
|
||||
PerformanceTimer perfTimer("myAvatar");
|
||||
|
@ -2066,6 +2072,10 @@ void Application::update(float deltaTime) {
|
|||
updateMyAvatar(deltaTime); // Sample hardware, update view frustum if needed, and send avatar data to mixer/nodes
|
||||
}
|
||||
|
||||
|
||||
// Dispatch input events
|
||||
_controllerScriptingInterface.updateInputControllers();
|
||||
|
||||
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...
|
||||
|
@ -3178,6 +3188,7 @@ void Application::resetSensors() {
|
|||
OculusManager::reset();
|
||||
|
||||
_prioVR.reset();
|
||||
//_leapmotion.reset();
|
||||
|
||||
QCursor::setPos(_mouseX, _mouseY);
|
||||
_myAvatar->reset();
|
||||
|
|
|
@ -32,6 +32,7 @@
|
|||
#include <AccountManager.h>
|
||||
#include <XmppClient.h>
|
||||
#include <UUID.h>
|
||||
#include <UserActivityLogger.h>
|
||||
|
||||
#include "Application.h"
|
||||
#include "AccountManager.h"
|
||||
|
@ -433,8 +434,14 @@ Menu::Menu() :
|
|||
addCheckableActionToQMenuAndActionHash(handOptionsMenu, MenuOption::ShowIKConstraints, 0, false);
|
||||
addCheckableActionToQMenuAndActionHash(handOptionsMenu, MenuOption::AlignForearmsWithWrists, 0, true);
|
||||
addCheckableActionToQMenuAndActionHash(handOptionsMenu, MenuOption::AlternateIK, 0, false);
|
||||
|
||||
|
||||
addCheckableActionToQMenuAndActionHash(developerMenu, MenuOption::DisableNackPackets, 0, false);
|
||||
addCheckableActionToQMenuAndActionHash(developerMenu,
|
||||
MenuOption::DisableActivityLogger,
|
||||
0,
|
||||
false,
|
||||
&UserActivityLogger::getInstance(),
|
||||
SLOT(disable(bool)));
|
||||
|
||||
addDisabledActionAndSeparator(developerMenu, "Testing");
|
||||
|
||||
|
|
|
@ -341,6 +341,7 @@ namespace MenuOption {
|
|||
const QString Console = "Console...";
|
||||
const QString DecreaseAvatarSize = "Decrease Avatar Size";
|
||||
const QString DecreaseVoxelSize = "Decrease Voxel Size";
|
||||
const QString DisableActivityLogger = "Disable Activity Logger";
|
||||
const QString DisableAutoAdjustLOD = "Disable Automatically Adjusting LOD";
|
||||
const QString DisableNackPackets = "Disable NACK Packets";
|
||||
const QString DisableQAudioOutputOverflowCheck = "Disable QAudioOutput Overflow Check";
|
||||
|
|
|
@ -928,27 +928,34 @@ void Avatar::setShowDisplayName(bool showDisplayName) {
|
|||
|
||||
void Avatar::setLocalLightDirection(const glm::vec3& direction, int lightIndex) {
|
||||
_localLightDirections[lightIndex] = direction;
|
||||
qDebug( "set light %d direction ( %f, %f, %f )\n", lightIndex, direction.x, direction.y, direction.z );
|
||||
}
|
||||
|
||||
void Avatar::setLocalLightColor(const glm::vec3& color, int lightIndex) {
|
||||
_localLightColors[lightIndex] = color;
|
||||
qDebug( "set light %d color ( %f, %f, %f )\n", lightIndex, color.x, color.y, color.z );
|
||||
}
|
||||
|
||||
void Avatar::addLocalLight() {
|
||||
if (_numLocalLights + 1 <= MAX_LOCAL_LIGHTS) {
|
||||
++_numLocalLights;
|
||||
}
|
||||
|
||||
qDebug("ADD LOCAL LIGHT (numLocalLights = %d)\n", _numLocalLights);
|
||||
}
|
||||
|
||||
void Avatar::removeLocalLight() {
|
||||
if (_numLocalLights - 1 >= 0) {
|
||||
--_numLocalLights;
|
||||
}
|
||||
|
||||
qDebug("REMOVE LOCAL LIGHT (numLocalLights = %d)\n", _numLocalLights);
|
||||
}
|
||||
|
||||
int Avatar::getNumLocalLights() {
|
||||
return _numLocalLights;
|
||||
}
|
||||
|
||||
glm::vec3 Avatar::getLocalLightDirection(int lightIndex) {
|
||||
return _localLightDirections[lightIndex];
|
||||
}
|
||||
|
||||
glm::vec3 Avatar::getLocalLightColor(int lightIndex) {
|
||||
return _localLightColors[lightIndex];
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -162,7 +162,10 @@ public slots:
|
|||
void setLocalLightDirection(const glm::vec3& direction, int lightIndex);
|
||||
void setLocalLightColor(const glm::vec3& color, int lightIndex);
|
||||
void addLocalLight();
|
||||
void removeLocalLight();
|
||||
void removeLocalLight();
|
||||
int getNumLocalLights();
|
||||
glm::vec3 getLocalLightDirection(int lightIndex);
|
||||
glm::vec3 getLocalLightColor(int lightIndex);
|
||||
|
||||
signals:
|
||||
void collisionWithAvatar(const QUuid& myUUID, const QUuid& theirUUID, const CollisionInfo& collision);
|
||||
|
|
|
@ -200,14 +200,17 @@ void AvatarManager::setAvatarLightColor(const glm::vec3& color, int lightIndex,
|
|||
}
|
||||
}
|
||||
|
||||
int AvatarManager::getNumLightsInAvatar(int avatarIndex) {
|
||||
int numLights = 0;
|
||||
|
||||
Avatar* avatar = getAvatarFromIndex(avatarIndex);
|
||||
if (avatar) {
|
||||
numLights = avatar->getNumLocalLights();
|
||||
}
|
||||
|
||||
return numLights;
|
||||
}
|
||||
|
||||
int AvatarManager::getNumAvatars() {
|
||||
return _avatarHash.count();
|
||||
}
|
||||
|
||||
QString AvatarManager::getAvatarHashKey(int index) {
|
||||
QString id = ((_avatarHash.keys())[index]).toString();
|
||||
std::string idString = id.toStdString();
|
||||
return id;
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -42,8 +42,8 @@ public slots:
|
|||
void setAvatarLightDirection(const glm::vec3& direction, int lightIndex, int avatarIndex);
|
||||
void removeAvatarLocalLight(int avatarIndex);
|
||||
void addAvatarLocalLight(int avatarIndex);
|
||||
int getNumLightsInAvatar(int avatarIndex);
|
||||
int getNumAvatars();
|
||||
QString getAvatarHashKey(int index);
|
||||
|
||||
private:
|
||||
AvatarManager(const AvatarManager& other);
|
||||
|
|
88
interface/src/devices/DeviceTracker.cpp
Normal file
|
@ -0,0 +1,88 @@
|
|||
//
|
||||
// DeviceTracker.cpp
|
||||
// interface/src/devices
|
||||
//
|
||||
// Created by Sam Cake on 6/20/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 "DeviceTracker.h"
|
||||
|
||||
DeviceTracker::SingletonData::~SingletonData() {
|
||||
// Destroy all the device registered
|
||||
//TODO C++11 for (auto device = _devicesVector.begin(); device != _devicesVector.end(); device++) {
|
||||
for (Vector::iterator device = _devicesVector.begin(); device != _devicesVector.end(); device++) {
|
||||
delete (*device);
|
||||
}
|
||||
}
|
||||
|
||||
int DeviceTracker::getNumDevices() {
|
||||
return Singleton::get()->_devicesMap.size();
|
||||
}
|
||||
|
||||
DeviceTracker::ID DeviceTracker::getDeviceID(const Name& name) {
|
||||
//TODO C++11 auto deviceIt = Singleton::get()->_devicesMap.find(name);
|
||||
Map::iterator deviceIt = Singleton::get()->_devicesMap.find(name);
|
||||
if (deviceIt != Singleton::get()->_devicesMap.end()) {
|
||||
return (*deviceIt).second;
|
||||
} else {
|
||||
return INVALID_DEVICE;
|
||||
}
|
||||
}
|
||||
|
||||
DeviceTracker* DeviceTracker::getDevice(const Name& name) {
|
||||
return getDevice(getDeviceID(name));
|
||||
}
|
||||
|
||||
DeviceTracker* DeviceTracker::getDevice(DeviceTracker::ID deviceID) {
|
||||
if ((deviceID >= 0) && (deviceID < Singleton::get()->_devicesVector.size())) {
|
||||
return Singleton::get()->_devicesVector[ deviceID ];
|
||||
} else {
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
|
||||
DeviceTracker::ID DeviceTracker::registerDevice(const Name& name, DeviceTracker* device) {
|
||||
// Check that the device exists, if not exit
|
||||
if (!device) {
|
||||
return INVALID_DEVICE;
|
||||
}
|
||||
|
||||
// Look if the name is not already used
|
||||
ID deviceID = getDeviceID(name);
|
||||
if (deviceID >= 0) {
|
||||
return INVALID_DEVICE_NAME;
|
||||
}
|
||||
|
||||
// Good to register the device
|
||||
deviceID = Singleton::get()->_devicesVector.size();
|
||||
Singleton::get()->_devicesMap.insert(Map::value_type(name, deviceID));
|
||||
Singleton::get()->_devicesVector.push_back(device);
|
||||
device->assignIDAndName(deviceID, name);
|
||||
|
||||
return deviceID;
|
||||
}
|
||||
|
||||
void DeviceTracker::updateAll() {
|
||||
//TODO C++11 for (auto deviceIt = Singleton::get()->_devicesVector.begin(); deviceIt != Singleton::get()->_devicesVector.end(); deviceIt++) {
|
||||
for (Vector::iterator deviceIt = Singleton::get()->_devicesVector.begin(); deviceIt != Singleton::get()->_devicesVector.end(); deviceIt++) {
|
||||
if ((*deviceIt))
|
||||
(*deviceIt)->update();
|
||||
}
|
||||
}
|
||||
|
||||
// Core features of the Device Tracker
|
||||
DeviceTracker::DeviceTracker() :
|
||||
_ID(INVALID_DEVICE),
|
||||
_name("Unkown")
|
||||
{
|
||||
}
|
||||
|
||||
DeviceTracker::~DeviceTracker() {
|
||||
}
|
||||
|
||||
void DeviceTracker::update() {
|
||||
}
|
114
interface/src/devices/DeviceTracker.h
Normal file
|
@ -0,0 +1,114 @@
|
|||
//
|
||||
// DeviceTracker.h
|
||||
// interface/src/devices
|
||||
//
|
||||
// Created by Sam Cake on 6/20/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_DeviceTracker_h
|
||||
#define hifi_DeviceTracker_h
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <map>
|
||||
|
||||
// Singleton template class
|
||||
template < typename T >
|
||||
class TemplateSingleton {
|
||||
public:
|
||||
|
||||
static T* get() {
|
||||
if ( !_singleton._one ) {
|
||||
_singleton._one = new T();
|
||||
}
|
||||
return _singleton._one;
|
||||
}
|
||||
|
||||
TemplateSingleton() :
|
||||
_one(0)
|
||||
{
|
||||
}
|
||||
~TemplateSingleton() {
|
||||
if ( _one ) {
|
||||
delete _one;
|
||||
_one = 0;
|
||||
}
|
||||
}
|
||||
private:
|
||||
static TemplateSingleton< T > _singleton;
|
||||
T* _one;
|
||||
};
|
||||
template <typename T>
|
||||
TemplateSingleton<T> TemplateSingleton<T>::_singleton;
|
||||
|
||||
/// Base class for device trackers.
|
||||
class DeviceTracker {
|
||||
public:
|
||||
|
||||
// THe ID and Name types used to manage the pool of devices
|
||||
typedef std::string Name;
|
||||
typedef int ID;
|
||||
static const ID INVALID_DEVICE = -1;
|
||||
static const ID INVALID_DEVICE_NAME = -2;
|
||||
|
||||
// Singleton interface to register and query the devices currently connected
|
||||
static int getNumDevices();
|
||||
static ID getDeviceID(const Name& name);
|
||||
static DeviceTracker* getDevice(ID deviceID);
|
||||
static DeviceTracker* getDevice(const Name& name);
|
||||
|
||||
/// Update all the devices calling for their update() function
|
||||
/// This should be called every frame by the main loop to update all the devices that pull their state
|
||||
static void updateAll();
|
||||
|
||||
/// Register a device tracker to the factory
|
||||
/// Right after creating a new DeviceTracker, it should be registered
|
||||
/// This is why, it's recommended to use a factory static call in the specialized class
|
||||
/// to create a new input device
|
||||
///
|
||||
/// \param name The Name under wich registering the device
|
||||
/// \param parent The DeviceTracker
|
||||
///
|
||||
/// \return The Index of the newly registered device.
|
||||
/// Valid if everything went well.
|
||||
/// INVALID_DEVICE if the device is not valid (NULL)
|
||||
/// INVALID_DEVICE_NAME if the name is already taken
|
||||
static ID registerDevice(const Name& name, DeviceTracker* tracker);
|
||||
|
||||
// DeviceTracker interface
|
||||
|
||||
virtual void update();
|
||||
|
||||
/// Get the ID assigned to the Device when registered after creation, or INVALID_DEVICE if it hasn't been registered which should not happen.
|
||||
ID getID() const { return _ID; }
|
||||
|
||||
/// Get the name assigned to the Device when registered after creation, or "Unknown" if it hasn't been registered which should not happen.
|
||||
const Name& getName() const { return _name; }
|
||||
|
||||
protected:
|
||||
DeviceTracker();
|
||||
virtual ~DeviceTracker();
|
||||
|
||||
private:
|
||||
ID _ID;
|
||||
Name _name;
|
||||
|
||||
// this call is used by the singleton when the device tracker is currently beeing registered and beeing assigned an ID
|
||||
void assignIDAndName( const ID id, const Name& name ) { _ID = id; _name = name; }
|
||||
|
||||
typedef std::vector< DeviceTracker* > Vector;
|
||||
typedef std::map< Name, ID > Map;
|
||||
struct SingletonData {
|
||||
Map _devicesMap;
|
||||
Vector _devicesVector;
|
||||
|
||||
~SingletonData();
|
||||
};
|
||||
typedef TemplateSingleton< SingletonData > Singleton;
|
||||
};
|
||||
|
||||
#endif // hifi_DeviceTracker_h
|
230
interface/src/devices/Leapmotion.cpp
Normal file
|
@ -0,0 +1,230 @@
|
|||
//
|
||||
// Leapmotion.cpp
|
||||
// interface/src/devices
|
||||
//
|
||||
// Created by Sam Cake on 6/2/2014
|
||||
// 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 "SharedUtil.h"
|
||||
#include "Leapmotion.h"
|
||||
|
||||
const int PALMROOT_NUM_JOINTS = 3;
|
||||
const int FINGER_NUM_JOINTS = 4;
|
||||
const int HAND_NUM_JOINTS = FINGER_NUM_JOINTS*5+PALMROOT_NUM_JOINTS;
|
||||
|
||||
const DeviceTracker::Name Leapmotion::NAME = "Leapmotion";
|
||||
|
||||
// find the index of a joint from
|
||||
// the side: true = right
|
||||
// the finger & the bone:
|
||||
// finger in [0..4] : bone in [0..3] a finger phalange
|
||||
// [-1] up the hand branch : bone in [0..2] <=> [ hand, forearm, arm]
|
||||
MotionTracker::Index evalJointIndex(bool isRightSide, int finger, int bone) {
|
||||
|
||||
MotionTracker::Index offset = 1 // start after root
|
||||
+ (int(isRightSide) * HAND_NUM_JOINTS) // then offset for side
|
||||
+ PALMROOT_NUM_JOINTS; // then add the arm/forearm/hand chain
|
||||
if (finger >= 0) {
|
||||
// from there go down in the correct finger and bone
|
||||
return offset + (finger * FINGER_NUM_JOINTS) + bone;
|
||||
} else {
|
||||
// or go back up for the correct root bone
|
||||
return offset - 1 - bone;
|
||||
}
|
||||
}
|
||||
|
||||
// static
|
||||
void Leapmotion::init() {
|
||||
DeviceTracker* device = DeviceTracker::getDevice(NAME);
|
||||
|
||||
if (!device) {
|
||||
// create a new Leapmotion and register it
|
||||
Leapmotion* leap = new Leapmotion();
|
||||
DeviceTracker::registerDevice(NAME, leap);
|
||||
}
|
||||
}
|
||||
|
||||
// static
|
||||
Leapmotion* Leapmotion::getInstance() {
|
||||
DeviceTracker* device = DeviceTracker::getDevice(NAME);
|
||||
if (!device) {
|
||||
// create a new Leapmotion and register it
|
||||
device = new Leapmotion();
|
||||
DeviceTracker::registerDevice(NAME, device);
|
||||
}
|
||||
return dynamic_cast< Leapmotion* > (device);
|
||||
}
|
||||
|
||||
Leapmotion::Leapmotion() :
|
||||
MotionTracker(),
|
||||
_active(false)
|
||||
{
|
||||
// Create the Leapmotion joint hierarchy
|
||||
std::vector< Semantic > sides;
|
||||
sides.push_back("joint_L_");
|
||||
sides.push_back("joint_R_");
|
||||
|
||||
std::vector< Semantic > rootBones;
|
||||
rootBones.push_back("elbow");
|
||||
rootBones.push_back("hand");
|
||||
rootBones.push_back("wrist");
|
||||
|
||||
std::vector< Semantic > fingers;
|
||||
fingers.push_back("thumb");
|
||||
fingers.push_back("index");
|
||||
fingers.push_back("middle");
|
||||
fingers.push_back("ring");
|
||||
fingers.push_back("pinky");
|
||||
|
||||
std::vector< Semantic > fingerBones;
|
||||
fingerBones.push_back("1");
|
||||
fingerBones.push_back("2");
|
||||
fingerBones.push_back("3");
|
||||
fingerBones.push_back("4");
|
||||
|
||||
std::vector< Index > palms;
|
||||
for (unsigned int s = 0; s < sides.size(); s++) {
|
||||
Index rootJoint = 0;
|
||||
for (unsigned int rb = 0; rb < rootBones.size(); rb++) {
|
||||
rootJoint = addJoint(sides[s] + rootBones[rb], rootJoint);
|
||||
}
|
||||
|
||||
// capture the hand index for debug
|
||||
palms.push_back(rootJoint);
|
||||
|
||||
for (unsigned int f = 0; f < fingers.size(); f++) {
|
||||
for (unsigned int b = 0; b < fingerBones.size(); b++) {
|
||||
rootJoint = addJoint(sides[s] + fingers[f] + fingerBones[b], rootJoint);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Leapmotion::~Leapmotion() {
|
||||
}
|
||||
|
||||
#ifdef HAVE_LEAPMOTION
|
||||
glm::quat quatFromLeapBase(float sideSign, const Leap::Matrix& basis) {
|
||||
|
||||
// fix the handness to right and always...
|
||||
glm::vec3 xAxis = glm::normalize(sideSign * glm::vec3(basis.xBasis.x, basis.xBasis.y, basis.xBasis.z));
|
||||
glm::vec3 yAxis = glm::normalize(glm::vec3(basis.yBasis.x, basis.yBasis.y, basis.yBasis.z));
|
||||
glm::vec3 zAxis = glm::normalize(glm::vec3(basis.zBasis.x, basis.zBasis.y, basis.zBasis.z));
|
||||
|
||||
xAxis = glm::normalize(glm::cross(yAxis, zAxis));
|
||||
|
||||
glm::quat orientation = (glm::quat_cast(glm::mat3(xAxis, yAxis, zAxis)));
|
||||
return orientation;
|
||||
}
|
||||
|
||||
glm::vec3 vec3FromLeapVector(const Leap::Vector& vec) {
|
||||
return glm::vec3(vec.x * METERS_PER_MILLIMETER, vec.y * METERS_PER_MILLIMETER, vec.z * METERS_PER_MILLIMETER);
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
void Leapmotion::update() {
|
||||
#ifdef HAVE_LEAPMOTION
|
||||
// Check that the controller is actually active
|
||||
_active = _controller.isConnected();
|
||||
if (!_active) {
|
||||
return;
|
||||
}
|
||||
|
||||
// go through all the joints and increment their counter since last update
|
||||
// TODO C++11 for (auto jointIt = _jointsArray.begin(); jointIt != _jointsArray.end(); jointIt++) {
|
||||
for (JointTracker::Vector::iterator jointIt = _jointsArray.begin(); jointIt != _jointsArray.end(); jointIt++) {
|
||||
(*jointIt).tickNewFrame();
|
||||
}
|
||||
|
||||
// Get the most recent frame and report some basic information
|
||||
const Leap::Frame frame = _controller.frame();
|
||||
static int64_t lastFrameID = -1;
|
||||
int64_t newFrameID = frame.id();
|
||||
|
||||
// If too soon then exit
|
||||
if (lastFrameID >= newFrameID)
|
||||
return;
|
||||
|
||||
glm::vec3 delta(0.f);
|
||||
glm::quat handOri;
|
||||
if (!frame.hands().isEmpty()) {
|
||||
for (int handNum = 0; handNum < frame.hands().count(); handNum++) {
|
||||
|
||||
const Leap::Hand hand = frame.hands()[handNum];
|
||||
int side = (hand.isRight() ? 1 : -1);
|
||||
|
||||
JointTracker* parentJointTracker = _jointsArray.data();
|
||||
|
||||
|
||||
int rootBranchIndex = -1;
|
||||
|
||||
Leap::Arm arm = hand.arm();
|
||||
if (arm.isValid()) {
|
||||
glm::quat ori = quatFromLeapBase(float(side), arm.basis());
|
||||
glm::vec3 pos = vec3FromLeapVector(arm.elbowPosition());
|
||||
JointTracker* elbow = editJointTracker(evalJointIndex((side > 0), rootBranchIndex, 2)); // 2 is the index of the elbow joint
|
||||
elbow->editAbsFrame().setTranslation(pos);
|
||||
elbow->editAbsFrame().setRotation(ori);
|
||||
elbow->updateLocFromAbsTransform(parentJointTracker);
|
||||
elbow->activeFrame();
|
||||
parentJointTracker = elbow;
|
||||
|
||||
pos = vec3FromLeapVector(arm.wristPosition());
|
||||
JointTracker* wrist = editJointTracker(evalJointIndex((side > 0), rootBranchIndex, 1)); // 1 is the index of the wrist joint
|
||||
wrist->editAbsFrame().setTranslation(pos);
|
||||
wrist->editAbsFrame().setRotation(ori);
|
||||
wrist->updateLocFromAbsTransform(parentJointTracker);
|
||||
wrist->activeFrame();
|
||||
parentJointTracker = wrist;
|
||||
}
|
||||
|
||||
JointTracker* palmJoint = NULL;
|
||||
{
|
||||
glm::vec3 pos = vec3FromLeapVector(hand.palmPosition());
|
||||
glm::quat ori = quatFromLeapBase(float(side), hand.basis());
|
||||
|
||||
palmJoint = editJointTracker(evalJointIndex((side > 0), rootBranchIndex, 0)); // 0 is the index of the palm joint
|
||||
palmJoint->editAbsFrame().setTranslation(pos);
|
||||
palmJoint->editAbsFrame().setRotation(ori);
|
||||
palmJoint->updateLocFromAbsTransform(parentJointTracker);
|
||||
palmJoint->activeFrame();
|
||||
}
|
||||
|
||||
// Check if the hand has any fingers
|
||||
const Leap::FingerList fingers = hand.fingers();
|
||||
if (!fingers.isEmpty()) {
|
||||
// For every fingers in the list
|
||||
for (int i = 0; i < fingers.count(); ++i) {
|
||||
// Reset the parent joint to the palmJoint for every finger traversal
|
||||
parentJointTracker = palmJoint;
|
||||
|
||||
// surprisingly, Leap::Finger::Type start at 0 for thumb a until 4 for the pinky
|
||||
Index fingerIndex = evalJointIndex((side > 0), int(fingers[i].type()), 0);
|
||||
|
||||
// let's update the finger's joints
|
||||
for (int b = 0; b < FINGER_NUM_JOINTS; b++) {
|
||||
Leap::Bone::Type type = Leap::Bone::Type(b + Leap::Bone::TYPE_METACARPAL);
|
||||
Leap::Bone bone = fingers[i].bone(type);
|
||||
JointTracker* ljointTracker = editJointTracker(fingerIndex + b);
|
||||
if (bone.isValid()) {
|
||||
Leap::Vector bp = bone.nextJoint();
|
||||
|
||||
ljointTracker->editAbsFrame().setTranslation(vec3FromLeapVector(bp));
|
||||
ljointTracker->editAbsFrame().setRotation(quatFromLeapBase(float(side), bone.basis()));
|
||||
ljointTracker->updateLocFromAbsTransform(parentJointTracker);
|
||||
ljointTracker->activeFrame();
|
||||
}
|
||||
parentJointTracker = ljointTracker;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
lastFrameID = newFrameID;
|
||||
#endif
|
||||
}
|
50
interface/src/devices/Leapmotion.h
Normal file
|
@ -0,0 +1,50 @@
|
|||
//
|
||||
// Leapmotion.h
|
||||
// interface/src/devices
|
||||
//
|
||||
// Created by Sam Cake on 6/2/2014
|
||||
// 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_Leapmotion_h
|
||||
#define hifi_Leapmotion_h
|
||||
|
||||
#include <QDateTime>
|
||||
|
||||
#include "MotionTracker.h"
|
||||
|
||||
#ifdef HAVE_LEAPMOTION
|
||||
#include <Leap.h>
|
||||
#endif
|
||||
|
||||
/// Handles interaction with the Leapmotion skeleton tracking suit.
|
||||
class Leapmotion : public MotionTracker {
|
||||
public:
|
||||
static const Name NAME;
|
||||
|
||||
static void init();
|
||||
|
||||
/// Leapmotion MotionTracker factory
|
||||
static Leapmotion* getInstance();
|
||||
|
||||
bool isActive() const { return _active; }
|
||||
|
||||
virtual void update();
|
||||
|
||||
protected:
|
||||
Leapmotion();
|
||||
virtual ~Leapmotion();
|
||||
|
||||
private:
|
||||
#ifdef HAVE_LEAPMOTION
|
||||
Leap::Listener _listener;
|
||||
Leap::Controller _controller;
|
||||
#endif
|
||||
|
||||
bool _active;
|
||||
};
|
||||
|
||||
#endif // hifi_Leapmotion_h
|
186
interface/src/devices/MotionTracker.cpp
Normal file
|
@ -0,0 +1,186 @@
|
|||
//
|
||||
// MotionTracker.cpp
|
||||
// interface/src/devices
|
||||
//
|
||||
// Created by Sam Cake on 6/20/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 "MotionTracker.h"
|
||||
|
||||
|
||||
// glm::mult(mat43, mat43) just the composition of the 2 matrices assuming they are in fact mat44 with the last raw = { 0, 0, 0, 1 }
|
||||
namespace glm {
|
||||
mat4x3 mult(const mat4& lhs, const mat4x3& rhs) {
|
||||
vec3 lrx(lhs[0].x, lhs[1].x, lhs[2].x);
|
||||
vec3 lry(lhs[0].y, lhs[1].y, lhs[2].y);
|
||||
vec3 lrz(lhs[0].z, lhs[1].z, lhs[2].z);
|
||||
return mat4x3(
|
||||
dot(lrx, rhs[0]),
|
||||
dot(lry, rhs[0]),
|
||||
dot(lrz, rhs[0]),
|
||||
|
||||
dot(lrx, rhs[1]),
|
||||
dot(lry, rhs[1]),
|
||||
dot(lrz, rhs[1]),
|
||||
|
||||
dot(lrx, rhs[2]),
|
||||
dot(lry, rhs[2]),
|
||||
dot(lrz, rhs[2]),
|
||||
|
||||
dot(lrx, rhs[3]) + lhs[3].x,
|
||||
dot(lry, rhs[3]) + lhs[3].y,
|
||||
dot(lrz, rhs[3]) + lhs[3].z
|
||||
);
|
||||
}
|
||||
mat4x3 mult(const mat4x3& lhs, const mat4x3& rhs) {
|
||||
vec3 lrx(lhs[0].x, lhs[1].x, lhs[2].x);
|
||||
vec3 lry(lhs[0].y, lhs[1].y, lhs[2].y);
|
||||
vec3 lrz(lhs[0].z, lhs[1].z, lhs[2].z);
|
||||
return mat4x3(
|
||||
dot(lrx, rhs[0]),
|
||||
dot(lry, rhs[0]),
|
||||
dot(lrz, rhs[0]),
|
||||
|
||||
dot(lrx, rhs[1]),
|
||||
dot(lry, rhs[1]),
|
||||
dot(lrz, rhs[1]),
|
||||
|
||||
dot(lrx, rhs[2]),
|
||||
dot(lry, rhs[2]),
|
||||
dot(lrz, rhs[2]),
|
||||
|
||||
dot(lrx, rhs[3]) + lhs[3].x,
|
||||
dot(lry, rhs[3]) + lhs[3].y,
|
||||
dot(lrz, rhs[3]) + lhs[3].z
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// MotionTracker
|
||||
MotionTracker::MotionTracker() :
|
||||
DeviceTracker()
|
||||
{
|
||||
_jointsArray.resize(1);
|
||||
_jointsMap.insert(JointTracker::Map::value_type(Semantic("Root"), 0));
|
||||
}
|
||||
|
||||
MotionTracker::~MotionTracker()
|
||||
{
|
||||
}
|
||||
|
||||
bool MotionTracker::isConnected() const {
|
||||
return false;
|
||||
}
|
||||
|
||||
MotionTracker::Index MotionTracker::addJoint(const Semantic& semantic, Index parent) {
|
||||
// Check the parent
|
||||
if (int(parent) < 0)
|
||||
return INVALID_PARENT;
|
||||
|
||||
// Check that the semantic is not already in use
|
||||
Index foundIndex = findJointIndex(semantic);
|
||||
if (foundIndex >= 0)
|
||||
return INVALID_SEMANTIC;
|
||||
|
||||
// All good then allocate the joint
|
||||
Index newIndex = _jointsArray.size();
|
||||
_jointsArray.push_back(JointTracker(semantic, parent));
|
||||
_jointsMap.insert(JointTracker::Map::value_type(semantic, newIndex));
|
||||
|
||||
return newIndex;
|
||||
}
|
||||
|
||||
MotionTracker::Index MotionTracker::findJointIndex(const Semantic& semantic) const {
|
||||
// TODO C++11 auto jointIt = _jointsMap.find(semantic);
|
||||
JointTracker::Map::const_iterator jointIt = _jointsMap.find(semantic);
|
||||
if (jointIt != _jointsMap.end())
|
||||
return (*jointIt).second;
|
||||
return INVALID_SEMANTIC;
|
||||
}
|
||||
|
||||
void MotionTracker::updateAllAbsTransform() {
|
||||
_jointsArray[0].updateAbsFromLocTransform(0);
|
||||
|
||||
// Because we know the hierarchy is stored from root down the branches let's just traverse and update
|
||||
for (Index i = 1; i < (Index)(_jointsArray.size()); i++) {
|
||||
JointTracker* joint = _jointsArray.data() + i;
|
||||
joint->updateAbsFromLocTransform(_jointsArray.data() + joint->getParent());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// MotionTracker::JointTracker
|
||||
MotionTracker::JointTracker::JointTracker() :
|
||||
_locFrame(),
|
||||
_absFrame(),
|
||||
_semantic(""),
|
||||
_parent(INVALID_PARENT),
|
||||
_lastUpdate(0)
|
||||
{
|
||||
}
|
||||
|
||||
MotionTracker::JointTracker::JointTracker(const Semantic& semantic, Index parent) :
|
||||
_semantic(semantic),
|
||||
_parent(parent),
|
||||
_lastUpdate(0)
|
||||
{
|
||||
}
|
||||
|
||||
MotionTracker::JointTracker::JointTracker(const JointTracker& tracker) :
|
||||
_locFrame(tracker._locFrame),
|
||||
_absFrame(tracker._absFrame),
|
||||
_semantic(tracker._semantic),
|
||||
_parent(tracker._parent),
|
||||
_lastUpdate(tracker._lastUpdate)
|
||||
{
|
||||
}
|
||||
|
||||
void MotionTracker::JointTracker::updateAbsFromLocTransform(const JointTracker* parentJoint) {
|
||||
if (parentJoint) {
|
||||
editAbsFrame()._transform = (parentJoint->getAbsFrame()._transform * getLocFrame()._transform);
|
||||
} else {
|
||||
editAbsFrame()._transform = getLocFrame()._transform;
|
||||
}
|
||||
}
|
||||
|
||||
void MotionTracker::JointTracker::updateLocFromAbsTransform(const JointTracker* parentJoint) {
|
||||
if (parentJoint) {
|
||||
glm::mat4 ip = glm::inverse(parentJoint->getAbsFrame()._transform);
|
||||
editLocFrame()._transform = (ip * getAbsFrame()._transform);
|
||||
} else {
|
||||
editLocFrame()._transform = getAbsFrame()._transform;
|
||||
}
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------------------------
|
||||
// MotionTracker::Frame
|
||||
//--------------------------------------------------------------------------------------
|
||||
|
||||
MotionTracker::Frame::Frame() :
|
||||
_transform()
|
||||
{
|
||||
}
|
||||
|
||||
void MotionTracker::Frame::setRotation(const glm::quat& rotation) {
|
||||
glm::mat3x3 rot = glm::mat3_cast(rotation);
|
||||
_transform[0] = glm::vec4(rot[0], 0.f);
|
||||
_transform[1] = glm::vec4(rot[1], 0.f);
|
||||
_transform[2] = glm::vec4(rot[2], 0.f);
|
||||
}
|
||||
|
||||
void MotionTracker::Frame::getRotation(glm::quat& rotation) const {
|
||||
rotation = glm::quat_cast( _transform);
|
||||
}
|
||||
|
||||
void MotionTracker::Frame::setTranslation(const glm::vec3& translation) {
|
||||
_transform[3] = glm::vec4(translation, 1.f);
|
||||
}
|
||||
|
||||
void MotionTracker::Frame::getTranslation(glm::vec3& translation) const {
|
||||
translation = glm::vec3(_transform[3]);
|
||||
}
|
||||
|
121
interface/src/devices/MotionTracker.h
Normal file
|
@ -0,0 +1,121 @@
|
|||
//
|
||||
// MotionTracker.h
|
||||
// interface/src/devices
|
||||
//
|
||||
// Created by Sam Cake on 6/20/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_MotionTracker_h
|
||||
#define hifi_MotionTracker_h
|
||||
|
||||
#include "DeviceTracker.h"
|
||||
|
||||
#include <glm/glm.hpp>
|
||||
#include <glm/gtc/quaternion.hpp>
|
||||
#include <glm/gtc/matrix_transform.hpp>
|
||||
|
||||
/// Base class for device trackers.
|
||||
class MotionTracker : public DeviceTracker {
|
||||
public:
|
||||
|
||||
class Frame {
|
||||
public:
|
||||
Frame();
|
||||
|
||||
glm::mat4 _transform;
|
||||
|
||||
void setRotation(const glm::quat& rotation);
|
||||
void getRotation(glm::quat& rotatio) const;
|
||||
|
||||
void setTranslation(const glm::vec3& translation);
|
||||
void getTranslation(glm::vec3& translation) const;
|
||||
};
|
||||
|
||||
// Semantic and Index types to retreive the JointTrackers of this MotionTracker
|
||||
typedef std::string Semantic;
|
||||
typedef int Index;
|
||||
static const Index INVALID_SEMANTIC = -1;
|
||||
static const Index INVALID_PARENT = -2;
|
||||
|
||||
class JointTracker {
|
||||
public:
|
||||
typedef std::vector< JointTracker > Vector;
|
||||
typedef std::map< Semantic, Index > Map;
|
||||
|
||||
JointTracker();
|
||||
JointTracker(const JointTracker& tracker);
|
||||
JointTracker(const Semantic& semantic, Index parent);
|
||||
|
||||
const Frame& getLocFrame() const { return _locFrame; }
|
||||
Frame& editLocFrame() { return _locFrame; }
|
||||
void setLocFrame(const Frame& frame) { editLocFrame() = frame; }
|
||||
|
||||
const Frame& getAbsFrame() const { return _absFrame; }
|
||||
Frame& editAbsFrame() { return _absFrame; }
|
||||
void setAbsFrame(const Frame& frame) { editAbsFrame() = frame; }
|
||||
|
||||
const Semantic& getSemantic() const { return _semantic; }
|
||||
const Index& getParent() const { return _parent; }
|
||||
|
||||
bool isActive() const { return (_lastUpdate <= 0); }
|
||||
void tickNewFrame() { _lastUpdate++; }
|
||||
void activeFrame() { _lastUpdate = 0; }
|
||||
|
||||
/// Update the loc/abs transform for this joint from the current abs/loc value and the specified parent joint abs frame
|
||||
void updateLocFromAbsTransform(const JointTracker* parentJoint);
|
||||
void updateAbsFromLocTransform(const JointTracker* parentJoint);
|
||||
|
||||
protected:
|
||||
Frame _locFrame;
|
||||
Frame _absFrame;
|
||||
Semantic _semantic;
|
||||
Index _parent;
|
||||
int _lastUpdate;
|
||||
};
|
||||
|
||||
virtual bool isConnected() const;
|
||||
|
||||
Index numJointTrackers() const { return _jointsArray.size(); }
|
||||
|
||||
/// Access a Joint from it's index.
|
||||
/// Index 0 is always the "Root".
|
||||
/// if the index is Invalid then returns NULL.
|
||||
const JointTracker* getJointTracker(Index index) const { return ((index > 0) && (index < (Index)(_jointsArray.size())) ? _jointsArray.data() + index : NULL); }
|
||||
JointTracker* editJointTracker(Index index) { return ((index > 0) && (index < (Index)(_jointsArray.size())) ? _jointsArray.data() + index : NULL); }
|
||||
|
||||
/// From a semantic, find the Index of the Joint.
|
||||
/// \return the index of the mapped Joint or INVALID_SEMANTIC if the semantic is not knowned.
|
||||
Index findJointIndex(const Semantic& semantic) const;
|
||||
|
||||
protected:
|
||||
MotionTracker();
|
||||
virtual ~MotionTracker();
|
||||
|
||||
JointTracker::Vector _jointsArray;
|
||||
JointTracker::Map _jointsMap;
|
||||
|
||||
/// Adding joint is only done from the specialized Motion Tracker, hence this function is protected.
|
||||
/// The hierarchy of joints must be created from the top down to the branches.
|
||||
/// The "Root" node is at index 0 and exists at creation of the Motion Tracker.
|
||||
///
|
||||
/// \param semantic A joint is defined by it's semantic, the unique name mapping to it
|
||||
/// \param parent The parent's index, the parent must be valid and correspond to a Joint that has been previously created
|
||||
///
|
||||
/// \return The Index of the newly created Joint.
|
||||
/// Valid if everything went well.
|
||||
/// INVALID_SEMANTIC if the semantic is already in use
|
||||
/// INVALID_PARENT if the parent is not valid
|
||||
Index addJoint(const Semantic& semantic, Index parent);
|
||||
|
||||
/// Update the absolute transform stack traversing the hierarchy from the root down the branches
|
||||
/// This is a generic way to update all the Joint's absFrame by combining the locFrame going down the hierarchy branch.
|
||||
void updateAllAbsTransform();
|
||||
};
|
||||
|
||||
|
||||
|
||||
#endif // hifi_MotionTracker_h
|
|
@ -13,6 +13,7 @@
|
|||
#include "Application.h"
|
||||
#include "devices/SixenseManager.h"
|
||||
#include "ControllerScriptingInterface.h"
|
||||
#include "devices/MotionTracker.h"
|
||||
|
||||
ControllerScriptingInterface::ControllerScriptingInterface() :
|
||||
_mouseCaptured(false),
|
||||
|
@ -258,3 +259,84 @@ glm::vec2 ControllerScriptingInterface::getViewportDimensions() const {
|
|||
QGLWidget* widget = Application::getInstance()->getGLWidget();
|
||||
return glm::vec2(widget->width(), widget->height());
|
||||
}
|
||||
|
||||
AbstractInputController* ControllerScriptingInterface::createInputController(const QString& deviceName, const QString& tracker) {
|
||||
// This is where we retreive the Device Tracker category and then the sub tracker within it
|
||||
//TODO C++11 auto icIt = _inputControllers.find(0);
|
||||
InputControllerMap::iterator icIt = _inputControllers.find(0);
|
||||
|
||||
if (icIt != _inputControllers.end()) {
|
||||
return (*icIt).second;
|
||||
} else {
|
||||
|
||||
// Look for device
|
||||
DeviceTracker::ID deviceID = DeviceTracker::getDeviceID(deviceName.toStdString());
|
||||
if (deviceID < 0) {
|
||||
deviceID = 0;
|
||||
}
|
||||
// TODO in this current implementation, we just pick the device assuming there is one (normally the Leapmotion)
|
||||
// in the near future we need to change that to a real mapping between the devices and the deviceName
|
||||
// ALso we need to expand the spec so we can fall back on the "default" controller per categories
|
||||
|
||||
if (deviceID >= 0) {
|
||||
// TODO here again the assumption it's the LeapMotion and so it's a MOtionTracker, this would need to be changed to support different types of devices
|
||||
MotionTracker* motionTracker = dynamic_cast< MotionTracker* > (DeviceTracker::getDevice(deviceID));
|
||||
if (motionTracker) {
|
||||
MotionTracker::Index trackerID = motionTracker->findJointIndex(tracker.toStdString());
|
||||
if (trackerID >= 0) {
|
||||
AbstractInputController* inputController = new InputController(deviceID, trackerID, this);
|
||||
|
||||
_inputControllers.insert(InputControllerMap::value_type(inputController->getKey(), inputController));
|
||||
|
||||
return inputController;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
void ControllerScriptingInterface::updateInputControllers() {
|
||||
//TODO C++11 for (auto it = _inputControllers.begin(); it != _inputControllers.end(); it++) {
|
||||
for (InputControllerMap::iterator it = _inputControllers.begin(); it != _inputControllers.end(); it++) {
|
||||
(*it).second->update();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
InputController::InputController(int deviceTrackerId, int subTrackerId, QObject* parent) :
|
||||
AbstractInputController(),
|
||||
_deviceTrackerId(deviceTrackerId),
|
||||
_subTrackerId(subTrackerId)
|
||||
{
|
||||
}
|
||||
|
||||
void InputController::update() {
|
||||
_isActive = false;
|
||||
|
||||
// TODO for now the InputController is only supporting a JointTracker from a MotionTracker
|
||||
MotionTracker* motionTracker = dynamic_cast< MotionTracker*> (DeviceTracker::getDevice(_deviceTrackerId));
|
||||
if (motionTracker) {
|
||||
if (_subTrackerId < motionTracker->numJointTrackers()) {
|
||||
const MotionTracker::JointTracker* joint = motionTracker->getJointTracker(_subTrackerId);
|
||||
|
||||
if (joint->isActive()) {
|
||||
joint->getAbsFrame().getTranslation(_eventCache.absTranslation);
|
||||
joint->getAbsFrame().getRotation(_eventCache.absRotation);
|
||||
joint->getLocFrame().getTranslation(_eventCache.locTranslation);
|
||||
joint->getLocFrame().getRotation(_eventCache.locRotation);
|
||||
|
||||
_isActive = true;
|
||||
emit spatialEvent(_eventCache);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const unsigned int INPUTCONTROLLER_KEY_DEVICE_OFFSET = 16;
|
||||
const unsigned int INPUTCONTROLLER_KEY_DEVICE_MASK = 16;
|
||||
|
||||
InputController::Key InputController::getKey() const {
|
||||
return (((_deviceTrackerId & INPUTCONTROLLER_KEY_DEVICE_MASK) << INPUTCONTROLLER_KEY_DEVICE_OFFSET) | _subTrackerId);
|
||||
}
|
||||
|
|
|
@ -17,6 +17,37 @@
|
|||
#include <AbstractControllerScriptingInterface.h>
|
||||
class PalmData;
|
||||
|
||||
|
||||
class InputController : public AbstractInputController {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
InputController(int deviceTrackerId, int subTrackerId, QObject* parent = NULL);
|
||||
|
||||
virtual void update();
|
||||
virtual Key getKey() const;
|
||||
|
||||
public slots:
|
||||
|
||||
virtual bool isActive() const { return _isActive; }
|
||||
virtual glm::vec3 getAbsTranslation() const { return _eventCache.absTranslation; }
|
||||
virtual glm::quat getAbsRotation() const { return _eventCache.absRotation; }
|
||||
virtual glm::vec3 getLocTranslation() const { return _eventCache.locTranslation; }
|
||||
virtual glm::quat getLocRotation() const { return _eventCache.locRotation; }
|
||||
|
||||
private:
|
||||
|
||||
int _deviceTrackerId;
|
||||
int _subTrackerId;
|
||||
|
||||
// cache for the spatial
|
||||
SpatialEvent _eventCache;
|
||||
bool _isActive;
|
||||
|
||||
signals:
|
||||
};
|
||||
|
||||
|
||||
/// handles scripting of input controller commands from JS
|
||||
class ControllerScriptingInterface : public AbstractControllerScriptingInterface {
|
||||
Q_OBJECT
|
||||
|
@ -43,6 +74,10 @@ public:
|
|||
bool isWheelCaptured() const { return _wheelCaptured; }
|
||||
bool isJoystickCaptured(int joystickIndex) const;
|
||||
|
||||
void updateInputControllers();
|
||||
|
||||
void releaseInputController(AbstractInputController* input);
|
||||
|
||||
public slots:
|
||||
virtual bool isPrimaryButtonPressed() const;
|
||||
virtual glm::vec2 getPrimaryJoystickPosition() const;
|
||||
|
@ -78,6 +113,9 @@ public slots:
|
|||
|
||||
virtual glm::vec2 getViewportDimensions() const;
|
||||
|
||||
/// Factory to create an InputController
|
||||
virtual AbstractInputController* createInputController(const QString& deviceName, const QString& tracker);
|
||||
|
||||
private:
|
||||
const PalmData* getPrimaryPalm() const;
|
||||
const PalmData* getPalm(int palmIndex) const;
|
||||
|
@ -89,6 +127,9 @@ private:
|
|||
bool _wheelCaptured;
|
||||
QMultiMap<int,KeyEvent> _capturedKeys;
|
||||
QSet<int> _capturedJoysticks;
|
||||
|
||||
typedef std::map< AbstractInputController::Key, AbstractInputController* > InputControllerMap;
|
||||
InputControllerMap _inputControllers;
|
||||
};
|
||||
|
||||
const int NUMBER_OF_SPATIALCONTROLS_PER_PALM = 2; // the hand and the tip
|
||||
|
@ -98,4 +139,5 @@ const int NUMBER_OF_BUTTONS_PER_PALM = 6;
|
|||
const int PALM_SPATIALCONTROL = 0;
|
||||
const int TIP_SPATIALCONTROL = 1;
|
||||
|
||||
|
||||
#endif // hifi_ControllerScriptingInterface_h
|
||||
|
|
|
@ -10,9 +10,11 @@
|
|||
//
|
||||
|
||||
#include <QDir>
|
||||
#include <QDialogButtonBox>
|
||||
#include <QFileDialog>
|
||||
#include <QInputDialog>
|
||||
#include <QMessageBox>
|
||||
#include <QScriptValue>
|
||||
|
||||
#include "Application.h"
|
||||
#include "Menu.h"
|
||||
|
@ -24,6 +26,9 @@ WindowScriptingInterface* WindowScriptingInterface::getInstance() {
|
|||
return &sharedInstance;
|
||||
}
|
||||
|
||||
WindowScriptingInterface::WindowScriptingInterface() {
|
||||
}
|
||||
|
||||
QScriptValue WindowScriptingInterface::alert(const QString& message) {
|
||||
QScriptValue retVal;
|
||||
QMetaObject::invokeMethod(this, "showAlert", Qt::BlockingQueuedConnection, Q_RETURN_ARG(QScriptValue, retVal), Q_ARG(const QString&, message));
|
||||
|
@ -37,6 +42,14 @@ QScriptValue WindowScriptingInterface::confirm(const QString& message) {
|
|||
return retVal;
|
||||
}
|
||||
|
||||
QScriptValue WindowScriptingInterface::form(const QString& title, QScriptValue form) {
|
||||
QScriptValue retVal;
|
||||
QMetaObject::invokeMethod(this, "showForm", Qt::BlockingQueuedConnection,
|
||||
Q_RETURN_ARG(QScriptValue, retVal),
|
||||
Q_ARG(const QString&, title), Q_ARG(QScriptValue, form));
|
||||
return retVal;
|
||||
}
|
||||
|
||||
QScriptValue WindowScriptingInterface::prompt(const QString& message, const QString& defaultText) {
|
||||
QScriptValue retVal;
|
||||
QMetaObject::invokeMethod(this, "showPrompt", Qt::BlockingQueuedConnection,
|
||||
|
@ -69,6 +82,73 @@ QScriptValue WindowScriptingInterface::showConfirm(const QString& message) {
|
|||
return QScriptValue(response == QMessageBox::Yes);
|
||||
}
|
||||
|
||||
/// Display a form layout with an edit box
|
||||
/// \param const QString& title title to display
|
||||
/// \param const QScriptValue form to display (array containing labels and values)
|
||||
/// \return QScriptValue result form (unchanged is dialog canceled)
|
||||
QScriptValue WindowScriptingInterface::showForm(const QString& title, QScriptValue form) {
|
||||
if (form.isArray() && form.property("length").toInt32() > 0) {
|
||||
QDialog* editDialog = new QDialog(Application::getInstance()->getWindow());
|
||||
editDialog->setWindowTitle(title);
|
||||
|
||||
QVBoxLayout* layout = new QVBoxLayout();
|
||||
editDialog->setLayout(layout);
|
||||
|
||||
QScrollArea* area = new QScrollArea();
|
||||
layout->addWidget(area);
|
||||
area->setWidgetResizable(true);
|
||||
QWidget* container = new QWidget();
|
||||
QFormLayout* formLayout = new QFormLayout();
|
||||
container->setLayout(formLayout);
|
||||
container->sizePolicy().setHorizontalStretch(1);
|
||||
formLayout->setRowWrapPolicy(QFormLayout::DontWrapRows);
|
||||
formLayout->setFieldGrowthPolicy(QFormLayout::ExpandingFieldsGrow);
|
||||
formLayout->setFormAlignment(Qt::AlignHCenter | Qt::AlignTop);
|
||||
formLayout->setLabelAlignment(Qt::AlignLeft);
|
||||
|
||||
area->setWidget(container);
|
||||
|
||||
QVector<QLineEdit*> edits;
|
||||
for (int i = 0; i < form.property("length").toInt32(); ++i) {
|
||||
QScriptValue item = form.property(i);
|
||||
edits.push_back(new QLineEdit(item.property("value").toString()));
|
||||
formLayout->addRow(item.property("label").toString(), edits.back());
|
||||
}
|
||||
QDialogButtonBox* buttons = new QDialogButtonBox(QDialogButtonBox::Ok);
|
||||
connect(buttons, SIGNAL(accepted()), editDialog, SLOT(accept()));
|
||||
layout->addWidget(buttons);
|
||||
|
||||
if (editDialog->exec() == QDialog::Accepted) {
|
||||
for (int i = 0; i < form.property("length").toInt32(); ++i) {
|
||||
QScriptValue item = form.property(i);
|
||||
QScriptValue value = item.property("value");
|
||||
bool ok = true;
|
||||
if (value.isNumber()) {
|
||||
value = edits.at(i)->text().toDouble(&ok);
|
||||
} else if (value.isString()) {
|
||||
value = edits.at(i)->text();
|
||||
} else if (value.isBool()) {
|
||||
if (edits.at(i)->text() == "true") {
|
||||
value = true;
|
||||
} else if (edits.at(i)->text() == "false") {
|
||||
value = false;
|
||||
} else {
|
||||
ok = false;
|
||||
}
|
||||
}
|
||||
if (ok) {
|
||||
item.setProperty("value", value);
|
||||
form.setProperty(i, item);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
delete editDialog;
|
||||
}
|
||||
|
||||
return form;
|
||||
}
|
||||
|
||||
/// Display a prompt with a text box
|
||||
/// \param const QString& message message to display
|
||||
/// \param const QString& defaultText default text in the text box
|
||||
|
@ -79,11 +159,11 @@ QScriptValue WindowScriptingInterface::showPrompt(const QString& message, const
|
|||
promptDialog.setLabelText(message);
|
||||
promptDialog.setTextValue(defaultText);
|
||||
promptDialog.setFixedSize(600, 200);
|
||||
|
||||
|
||||
if (promptDialog.exec() == QDialog::Accepted) {
|
||||
return QScriptValue(promptDialog.textValue());
|
||||
}
|
||||
|
||||
|
||||
return QScriptValue::NullValue;
|
||||
}
|
||||
|
||||
|
|
|
@ -20,7 +20,6 @@ class WindowScriptingInterface : public QObject {
|
|||
Q_OBJECT
|
||||
Q_PROPERTY(int innerWidth READ getInnerWidth)
|
||||
Q_PROPERTY(int innerHeight READ getInnerHeight)
|
||||
WindowScriptingInterface() { };
|
||||
public:
|
||||
static WindowScriptingInterface* getInstance();
|
||||
int getInnerWidth();
|
||||
|
@ -29,14 +28,19 @@ public:
|
|||
public slots:
|
||||
QScriptValue alert(const QString& message = "");
|
||||
QScriptValue confirm(const QString& message = "");
|
||||
QScriptValue form(const QString& title, QScriptValue array);
|
||||
QScriptValue prompt(const QString& message = "", const QString& defaultText = "");
|
||||
QScriptValue browse(const QString& title = "", const QString& directory = "", const QString& nameFilter = "");
|
||||
|
||||
private slots:
|
||||
QScriptValue showAlert(const QString& message);
|
||||
QScriptValue showConfirm(const QString& message);
|
||||
QScriptValue showForm(const QString& title, QScriptValue form);
|
||||
QScriptValue showPrompt(const QString& message, const QString& defaultText);
|
||||
QScriptValue showBrowse(const QString& title, const QString& directory, const QString& nameFilter);
|
||||
|
||||
private:
|
||||
WindowScriptingInterface();
|
||||
};
|
||||
|
||||
#endif // hifi_WindowScriptingInterface_h
|
||||
|
|
|
@ -136,6 +136,8 @@ void PreferencesDialog::loadPreferences() {
|
|||
|
||||
_skeletonURLString = myAvatar->getSkeletonModel().getURL().toString();
|
||||
ui.skeletonURLEdit->setText(_skeletonURLString);
|
||||
|
||||
ui.sendDataCheckBox->setChecked(!menuInstance->isOptionChecked(MenuOption::DisableActivityLogger));
|
||||
|
||||
ui.snapshotLocationEdit->setText(menuInstance->getSnapshotsLocation());
|
||||
|
||||
|
@ -201,6 +203,11 @@ void PreferencesDialog::savePreferences() {
|
|||
myAvatar->sendIdentityPacket();
|
||||
Application::getInstance()->bumpSettings();
|
||||
}
|
||||
|
||||
if (!Menu::getInstance()->isOptionChecked(MenuOption::DisableActivityLogger)
|
||||
!= ui.sendDataCheckBox->isChecked()) {
|
||||
Menu::getInstance()->triggerOption(MenuOption::DisableActivityLogger);
|
||||
}
|
||||
|
||||
if (!ui.snapshotLocationEdit->text().isEmpty() && QDir(ui.snapshotLocationEdit->text()).exists()) {
|
||||
Menu::getInstance()->setSnapshotsLocation(ui.snapshotLocationEdit->text());
|
||||
|
|
|
@ -812,5 +812,26 @@ void Stats::display(
|
|||
drawText(horizontalOffset, verticalOffset, 0.10f, 0.f, 2.f, reflectionsStatus, color);
|
||||
|
||||
}
|
||||
|
||||
// draw local light stats
|
||||
int numLocalLights = myAvatar->getNumLocalLights();
|
||||
verticalOffset = 400;
|
||||
horizontalOffset = 20;
|
||||
|
||||
char buffer[128];
|
||||
for (int i = 0; i < numLocalLights; i++) {
|
||||
glm::vec3 lightDirection = myAvatar->getLocalLightDirection(i);
|
||||
snprintf(buffer, sizeof(buffer), "Light %d direction (%.2f, %.2f, %.2f)", i, lightDirection.x, lightDirection.y, lightDirection.z);
|
||||
drawText(horizontalOffset, verticalOffset, scale, rotation, font, buffer, color);
|
||||
|
||||
verticalOffset += STATS_PELS_PER_LINE;
|
||||
|
||||
glm::vec3 lightColor = myAvatar->getLocalLightColor(i);
|
||||
snprintf(buffer, sizeof(buffer), "Light %d color (%.2f, %.2f, %.2f)", i, lightColor.x, lightColor.y, lightColor.z);
|
||||
drawText(horizontalOffset, verticalOffset, scale, rotation, font, buffer, color);
|
||||
|
||||
verticalOffset += STATS_PELS_PER_LINE;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
|
|
@ -135,7 +135,7 @@ color: #0e7077</string>
|
|||
<x>0</x>
|
||||
<y>30</y>
|
||||
<width>494</width>
|
||||
<height>384</height>
|
||||
<height>361</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="frameShape">
|
||||
|
@ -155,8 +155,8 @@ color: #0e7077</string>
|
|||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>600</width>
|
||||
<height>1091</height>
|
||||
<width>494</width>
|
||||
<height>1456</height>
|
||||
</rect>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="verticalLayout_2">
|
||||
|
@ -790,6 +790,132 @@ padding: 10px;margin-top:10px</string>
|
|||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QLabel" name="privacyLabel">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Fixed" vsizetype="Fixed">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="minimumSize">
|
||||
<size>
|
||||
<width>0</width>
|
||||
<height>30</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="font">
|
||||
<font>
|
||||
<family>Arial</family>
|
||||
<pointsize>16</pointsize>
|
||||
</font>
|
||||
</property>
|
||||
<property name="styleSheet">
|
||||
<string notr="true">color: #0e7077</string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Privacy</string>
|
||||
</property>
|
||||
<property name="alignment">
|
||||
<set>Qt::AlignBottom|Qt::AlignLeading|Qt::AlignLeft</set>
|
||||
</property>
|
||||
<property name="margin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="buddy">
|
||||
<cstring>snapshotLocationEdit</cstring>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<layout class="QHBoxLayout" name="horizontalLayout_18">
|
||||
<property name="spacing">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="topMargin">
|
||||
<number>10</number>
|
||||
</property>
|
||||
<property name="rightMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="bottomMargin">
|
||||
<number>10</number>
|
||||
</property>
|
||||
<item>
|
||||
<widget class="QLabel" name="label_15">
|
||||
<property name="font">
|
||||
<font>
|
||||
<family>Arial</family>
|
||||
</font>
|
||||
</property>
|
||||
<property name="styleSheet">
|
||||
<string notr="true">color: rgb(51, 51, 51)</string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Send data</string>
|
||||
</property>
|
||||
<property name="indent">
|
||||
<number>15</number>
|
||||
</property>
|
||||
<property name="buddy">
|
||||
<cstring>maxVoxelsSpin</cstring>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<spacer name="horizontalSpacer_18">
|
||||
<property name="font">
|
||||
<font>
|
||||
<family>Arial</family>
|
||||
</font>
|
||||
</property>
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>40</width>
|
||||
<height>20</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QCheckBox" name="sendDataCheckBox">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Minimum" vsizetype="Fixed">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="minimumSize">
|
||||
<size>
|
||||
<width>32</width>
|
||||
<height>0</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="baseSize">
|
||||
<size>
|
||||
<width>0</width>
|
||||
<height>0</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string/>
|
||||
</property>
|
||||
<property name="iconSize">
|
||||
<size>
|
||||
<width>32</width>
|
||||
<height>32</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="checked">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item>
|
||||
<spacer name="verticalSpacer">
|
||||
<property name="orientation">
|
||||
|
@ -2073,8 +2199,8 @@ padding: 10px;margin-top:10px</string>
|
|||
<slot>close()</slot>
|
||||
<hints>
|
||||
<hint type="sourcelabel">
|
||||
<x>495</x>
|
||||
<y>749</y>
|
||||
<x>966</x>
|
||||
<y>557</y>
|
||||
</hint>
|
||||
<hint type="destinationlabel">
|
||||
<x>528</x>
|
||||
|
@ -2089,8 +2215,8 @@ padding: 10px;margin-top:10px</string>
|
|||
<slot>accept()</slot>
|
||||
<hints>
|
||||
<hint type="sourcelabel">
|
||||
<x>504</x>
|
||||
<y>749</y>
|
||||
<x>1070</x>
|
||||
<y>557</y>
|
||||
</hint>
|
||||
<hint type="destinationlabel">
|
||||
<x>20</x>
|
||||
|
|
1
javascripts/main.js
Normal file
|
@ -0,0 +1 @@
|
|||
console.log('This would be the main JS file.');
|
17
javascripts/scale.fix.js
Normal file
|
@ -0,0 +1,17 @@
|
|||
var metas = document.getElementsByTagName('meta');
|
||||
var i;
|
||||
if (navigator.userAgent.match(/iPhone/i)) {
|
||||
for (i=0; i<metas.length; i++) {
|
||||
if (metas[i].name == "viewport") {
|
||||
metas[i].content = "width=device-width, minimum-scale=1.0, maximum-scale=1.0";
|
||||
}
|
||||
}
|
||||
document.addEventListener("gesturestart", gestureStart, false);
|
||||
}
|
||||
function gestureStart() {
|
||||
for (i=0; i<metas.length; i++) {
|
||||
if (metas[i].name == "viewport") {
|
||||
metas[i].content = "width=device-width, minimum-scale=0.25, maximum-scale=1.6";
|
||||
}
|
||||
}
|
||||
}
|
|
@ -9,13 +9,13 @@
|
|||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
//
|
||||
|
||||
#include "UserActivityLogger.h"
|
||||
|
||||
#include <QEventLoop>
|
||||
#include <QJsonDocument>
|
||||
#include <QHttpMultiPart>
|
||||
#include <QTimer>
|
||||
|
||||
#include "UserActivityLogger.h"
|
||||
|
||||
static const QString USER_ACTIVITY_URL = "/api/v1/user_activities";
|
||||
|
||||
UserActivityLogger& UserActivityLogger::getInstance() {
|
||||
|
@ -23,10 +23,18 @@ UserActivityLogger& UserActivityLogger::getInstance() {
|
|||
return sharedInstance;
|
||||
}
|
||||
|
||||
UserActivityLogger::UserActivityLogger() {
|
||||
UserActivityLogger::UserActivityLogger() : _disabled(false) {
|
||||
}
|
||||
|
||||
void UserActivityLogger::disable(bool disable) {
|
||||
_disabled = disable;
|
||||
}
|
||||
|
||||
void UserActivityLogger::logAction(QString action, QJsonObject details, JSONCallbackParameters params) {
|
||||
if (_disabled) {
|
||||
return;
|
||||
}
|
||||
|
||||
AccountManager& accountManager = AccountManager::getInstance();
|
||||
QHttpMultiPart* multipart = new QHttpMultiPart(QHttpMultiPart::FormDataType);
|
||||
|
||||
|
|
|
@ -26,6 +26,7 @@ public:
|
|||
static UserActivityLogger& getInstance();
|
||||
|
||||
public slots:
|
||||
void disable(bool disable);
|
||||
void logAction(QString action, QJsonObject details = QJsonObject(), JSONCallbackParameters params = JSONCallbackParameters());
|
||||
|
||||
void launch(QString applicationVersion);
|
||||
|
@ -43,6 +44,7 @@ private slots:
|
|||
|
||||
private:
|
||||
UserActivityLogger();
|
||||
bool _disabled;
|
||||
};
|
||||
|
||||
#endif // hifi_UserActivityLogger_h
|
|
@ -19,6 +19,28 @@
|
|||
|
||||
#include "EventTypes.h"
|
||||
|
||||
class AbstractInputController : public QObject {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
typedef unsigned int Key;
|
||||
|
||||
virtual void update() = 0;
|
||||
|
||||
virtual Key getKey() const = 0;
|
||||
|
||||
public slots:
|
||||
|
||||
virtual bool isActive() const = 0;
|
||||
virtual glm::vec3 getAbsTranslation() const = 0;
|
||||
virtual glm::quat getAbsRotation() const = 0;
|
||||
virtual glm::vec3 getLocTranslation() const = 0;
|
||||
virtual glm::quat getLocRotation() const = 0;
|
||||
|
||||
signals:
|
||||
void spatialEvent(const SpatialEvent& event);
|
||||
|
||||
};
|
||||
|
||||
/// handles scripting of input controller commands from JS
|
||||
class AbstractControllerScriptingInterface : public QObject {
|
||||
|
@ -60,6 +82,9 @@ public slots:
|
|||
|
||||
virtual glm::vec2 getViewportDimensions() const = 0;
|
||||
|
||||
|
||||
virtual AbstractInputController* createInputController( const QString& category, const QString& tracker ) = 0;
|
||||
|
||||
signals:
|
||||
void keyPressEvent(const KeyEvent& event);
|
||||
void keyReleaseEvent(const KeyEvent& event);
|
||||
|
|
|
@ -19,6 +19,7 @@ void registerEventTypes(QScriptEngine* engine) {
|
|||
qScriptRegisterMetaType(engine, mouseEventToScriptValue, mouseEventFromScriptValue);
|
||||
qScriptRegisterMetaType(engine, touchEventToScriptValue, touchEventFromScriptValue);
|
||||
qScriptRegisterMetaType(engine, wheelEventToScriptValue, wheelEventFromScriptValue);
|
||||
qScriptRegisterMetaType(engine, spatialEventToScriptValue, spatialEventFromScriptValue);
|
||||
}
|
||||
|
||||
KeyEvent::KeyEvent() :
|
||||
|
@ -600,3 +601,34 @@ void wheelEventFromScriptValue(const QScriptValue& object, WheelEvent& event) {
|
|||
}
|
||||
|
||||
|
||||
|
||||
SpatialEvent::SpatialEvent() :
|
||||
locTranslation(0.0f),
|
||||
locRotation(),
|
||||
absTranslation(0.0f),
|
||||
absRotation()
|
||||
{
|
||||
};
|
||||
|
||||
SpatialEvent::SpatialEvent(const SpatialEvent& event) {
|
||||
locTranslation = event.locTranslation;
|
||||
locRotation = event.locRotation;
|
||||
absTranslation = event.absTranslation;
|
||||
absRotation = event.absRotation;
|
||||
}
|
||||
|
||||
|
||||
QScriptValue spatialEventToScriptValue(QScriptEngine* engine, const SpatialEvent& event) {
|
||||
QScriptValue obj = engine->newObject();
|
||||
|
||||
obj.setProperty("locTranslation", vec3toScriptValue(engine, event.locTranslation) );
|
||||
obj.setProperty("locRotation", quatToScriptValue(engine, event.locRotation) );
|
||||
obj.setProperty("absTranslation", vec3toScriptValue(engine, event.absTranslation) );
|
||||
obj.setProperty("absRotation", quatToScriptValue(engine, event.absRotation) );
|
||||
|
||||
return obj;
|
||||
}
|
||||
|
||||
void spatialEventFromScriptValue(const QScriptValue& object,SpatialEvent& event) {
|
||||
// nothing for now...
|
||||
}
|
||||
|
|
|
@ -13,6 +13,7 @@
|
|||
#define hifi_EventTypes_h
|
||||
|
||||
#include <glm/glm.hpp>
|
||||
#include <glm/gtc/quaternion.hpp>
|
||||
|
||||
#include <QtScript/QScriptEngine>
|
||||
|
||||
|
@ -107,10 +108,25 @@ public:
|
|||
bool isAlt;
|
||||
};
|
||||
|
||||
class SpatialEvent {
|
||||
public:
|
||||
SpatialEvent();
|
||||
SpatialEvent(const SpatialEvent& other);
|
||||
|
||||
glm::vec3 locTranslation;
|
||||
glm::quat locRotation;
|
||||
glm::vec3 absTranslation;
|
||||
glm::quat absRotation;
|
||||
|
||||
private:
|
||||
};
|
||||
|
||||
|
||||
Q_DECLARE_METATYPE(KeyEvent)
|
||||
Q_DECLARE_METATYPE(MouseEvent)
|
||||
Q_DECLARE_METATYPE(TouchEvent)
|
||||
Q_DECLARE_METATYPE(WheelEvent)
|
||||
Q_DECLARE_METATYPE(SpatialEvent)
|
||||
|
||||
void registerEventTypes(QScriptEngine* engine);
|
||||
|
||||
|
@ -126,4 +142,7 @@ void touchEventFromScriptValue(const QScriptValue& object, TouchEvent& event);
|
|||
QScriptValue wheelEventToScriptValue(QScriptEngine* engine, const WheelEvent& event);
|
||||
void wheelEventFromScriptValue(const QScriptValue& object, WheelEvent& event);
|
||||
|
||||
QScriptValue spatialEventToScriptValue(QScriptEngine* engine, const SpatialEvent& event);
|
||||
void spatialEventFromScriptValue(const QScriptValue& object, SpatialEvent& event);
|
||||
|
||||
#endif // hifi_EventTypes_h
|
||||
|
|
|
@ -72,6 +72,14 @@ void injectorFromScriptValue(const QScriptValue &object, AudioInjector* &out) {
|
|||
out = qobject_cast<AudioInjector*>(object.toQObject());
|
||||
}
|
||||
|
||||
QScriptValue injectorToScriptValueInputController(QScriptEngine *engine, AbstractInputController* const &in) {
|
||||
return engine->newQObject(in);
|
||||
}
|
||||
|
||||
void injectorFromScriptValueInputController(const QScriptValue &object, AbstractInputController* &out) {
|
||||
out = qobject_cast<AbstractInputController*>(object.toQObject());
|
||||
}
|
||||
|
||||
ScriptEngine::ScriptEngine(const QString& scriptContents, const QString& fileNameString,
|
||||
AbstractControllerScriptingInterface* controllerScriptingInterface) :
|
||||
|
||||
|
@ -262,6 +270,7 @@ void ScriptEngine::init() {
|
|||
_engine.globalObject().setProperty("LocalVoxels", localVoxelsValue);
|
||||
|
||||
qScriptRegisterMetaType(&_engine, injectorToScriptValue, injectorFromScriptValue);
|
||||
qScriptRegisterMetaType( &_engine, injectorToScriptValueInputController, injectorFromScriptValueInputController);
|
||||
|
||||
qScriptRegisterMetaType(&_engine, animationDetailsToScriptValue, animationDetailsFromScriptValue);
|
||||
|
||||
|
|
|
@ -187,4 +187,21 @@ VoxelDetail VoxelsScriptingInterface::getVoxelEnclosingPoint(const glm::vec3& po
|
|||
return result;
|
||||
}
|
||||
|
||||
VoxelDetail VoxelsScriptingInterface::getVoxelEnclosingPointBlocking(const glm::vec3& point) {
|
||||
VoxelDetail result = { 0.0f, 0.0f, 0.0f, 0.0f, 0, 0, 0 };
|
||||
if (_tree) {
|
||||
OctreeElement* element = _tree->getElementEnclosingPoint(point / (float)TREE_SCALE, Octree::Lock);
|
||||
if (element) {
|
||||
VoxelTreeElement* voxel = static_cast<VoxelTreeElement*>(element);
|
||||
result.x = voxel->getCorner().x;
|
||||
result.y = voxel->getCorner().y;
|
||||
result.z = voxel->getCorner().z;
|
||||
result.s = voxel->getScale();
|
||||
result.red = voxel->getColor()[0];
|
||||
result.green = voxel->getColor()[1];
|
||||
result.blue = voxel->getColor()[2];
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
|
|
|
@ -90,6 +90,11 @@ public:
|
|||
/// \return VoxelDetail - if no voxel encloses the point then VoxelDetail items will be 0
|
||||
Q_INVOKABLE VoxelDetail getVoxelEnclosingPoint(const glm::vec3& point);
|
||||
|
||||
/// checks the local voxel tree for the smallest voxel enclosing the point and uses a blocking lock
|
||||
/// \param point the x,y,z coordinates of the point (in meter units)
|
||||
/// \return VoxelDetail - if no voxel encloses the point then VoxelDetail items will be 0
|
||||
Q_INVOKABLE VoxelDetail getVoxelEnclosingPointBlocking(const glm::vec3& point);
|
||||
|
||||
private:
|
||||
/// actually does the work of finding the ray intersection, can be called in locking mode or tryLock mode
|
||||
RayToVoxelIntersectionResult findRayIntersectionWorker(const PickRay& ray, Octree::lockType lockType);
|
||||
|
|
1
params.json
Normal file
|
@ -0,0 +1 @@
|
|||
{"name":"Hifi","tagline":"Open, decentralized virtual worlds using sensors to control avatars and dynamically assigned devices as servers. San Francisco based startup, we are hiring: http://highfidelity.io/jobs You can also contribute by doing jobs listed at http://worklist.net -","body":"### Avatar Documentation\r\n\r\n* [Exporting your Rigged Avatar from Faceshift](https://github.com/highfidelity/hifi/wiki/Exporting-Your-Rigged-Avatar-From-Faceshift)\r\n* [Creating Blendshapes for your Avatar](https://github.com/highfidelity/hifi/wiki/Creating-Blendshapes-for-your-Avatar)\r\n* [How to Rig a Character for Faceshift](https://github.com/highfidelity/hifi/wiki/How-to-Rig-a-Character-for-Faceshift)\r\n* [Naming your Skeletal Joints](https://github.com/highfidelity/hifi/wiki/Naming-Your-Skeletal-Joints)\r\n* [The FST File](https://github.com/highfidelity/hifi/wiki/The-FST-file)\r\n* [Training in Faceshift](https://github.com/highfidelity/hifi/wiki/Training-in-Faceshift)\r\n* [Uploading your Models](https://github.com/highfidelity/hifi/wiki/Uploading-Your-Models)\r\n\r\n\r\n\r\n\r\n","google":"","note":"Don't delete this file! It's used internally to help with page regeneration."}
|
226
stylesheets/print.css
Normal file
|
@ -0,0 +1,226 @@
|
|||
html, body, div, span, applet, object, iframe,
|
||||
h1, h2, h3, h4, h5, h6, p, blockquote, pre,
|
||||
a, abbr, acronym, address, big, cite, code,
|
||||
del, dfn, em, img, ins, kbd, q, s, samp,
|
||||
small, strike, strong, sub, sup, tt, var,
|
||||
b, u, i, center,
|
||||
dl, dt, dd, ol, ul, li,
|
||||
fieldset, form, label, legend,
|
||||
table, caption, tbody, tfoot, thead, tr, th, td,
|
||||
article, aside, canvas, details, embed,
|
||||
figure, figcaption, footer, header, hgroup,
|
||||
menu, nav, output, ruby, section, summary,
|
||||
time, mark, audio, video {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
border: 0;
|
||||
font-size: 100%;
|
||||
font: inherit;
|
||||
vertical-align: baseline;
|
||||
}
|
||||
/* HTML5 display-role reset for older browsers */
|
||||
article, aside, details, figcaption, figure,
|
||||
footer, header, hgroup, menu, nav, section {
|
||||
display: block;
|
||||
}
|
||||
body {
|
||||
line-height: 1;
|
||||
}
|
||||
ol, ul {
|
||||
list-style: none;
|
||||
}
|
||||
blockquote, q {
|
||||
quotes: none;
|
||||
}
|
||||
blockquote:before, blockquote:after,
|
||||
q:before, q:after {
|
||||
content: '';
|
||||
content: none;
|
||||
}
|
||||
table {
|
||||
border-collapse: collapse;
|
||||
border-spacing: 0;
|
||||
}
|
||||
body {
|
||||
font-size: 13px;
|
||||
line-height: 1.5;
|
||||
font-family: 'Helvetica Neue', Helvetica, Arial, serif;
|
||||
color: #000;
|
||||
}
|
||||
|
||||
a {
|
||||
color: #d5000d;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
header {
|
||||
padding-top: 35px;
|
||||
padding-bottom: 10px;
|
||||
}
|
||||
|
||||
header h1 {
|
||||
font-weight: bold;
|
||||
letter-spacing: -1px;
|
||||
font-size: 48px;
|
||||
color: #303030;
|
||||
line-height: 1.2;
|
||||
}
|
||||
|
||||
header h2 {
|
||||
letter-spacing: -1px;
|
||||
font-size: 24px;
|
||||
color: #aaa;
|
||||
font-weight: normal;
|
||||
line-height: 1.3;
|
||||
}
|
||||
#downloads {
|
||||
display: none;
|
||||
}
|
||||
#main_content {
|
||||
padding-top: 20px;
|
||||
}
|
||||
|
||||
code, pre {
|
||||
font-family: Monaco, "Bitstream Vera Sans Mono", "Lucida Console", Terminal;
|
||||
color: #222;
|
||||
margin-bottom: 30px;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
code {
|
||||
padding: 0 3px;
|
||||
}
|
||||
|
||||
pre {
|
||||
border: solid 1px #ddd;
|
||||
padding: 20px;
|
||||
overflow: auto;
|
||||
}
|
||||
pre code {
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
ul, ol, dl {
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
|
||||
/* COMMON STYLES */
|
||||
|
||||
table {
|
||||
width: 100%;
|
||||
border: 1px solid #ebebeb;
|
||||
}
|
||||
|
||||
th {
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
td {
|
||||
border: 1px solid #ebebeb;
|
||||
text-align: center;
|
||||
font-weight: 300;
|
||||
}
|
||||
|
||||
form {
|
||||
background: #f2f2f2;
|
||||
padding: 20px;
|
||||
|
||||
}
|
||||
|
||||
|
||||
/* GENERAL ELEMENT TYPE STYLES */
|
||||
|
||||
h1 {
|
||||
font-size: 2.8em;
|
||||
}
|
||||
|
||||
h2 {
|
||||
font-size: 22px;
|
||||
font-weight: bold;
|
||||
color: #303030;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
h3 {
|
||||
color: #d5000d;
|
||||
font-size: 18px;
|
||||
font-weight: bold;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
h4 {
|
||||
font-size: 16px;
|
||||
color: #303030;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
h5 {
|
||||
font-size: 1em;
|
||||
color: #303030;
|
||||
}
|
||||
|
||||
h6 {
|
||||
font-size: .8em;
|
||||
color: #303030;
|
||||
}
|
||||
|
||||
p {
|
||||
font-weight: 300;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
a {
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
p a {
|
||||
font-weight: 400;
|
||||
}
|
||||
|
||||
blockquote {
|
||||
font-size: 1.6em;
|
||||
border-left: 10px solid #e9e9e9;
|
||||
margin-bottom: 20px;
|
||||
padding: 0 0 0 30px;
|
||||
}
|
||||
|
||||
ul li {
|
||||
list-style: disc inside;
|
||||
padding-left: 20px;
|
||||
}
|
||||
|
||||
ol li {
|
||||
list-style: decimal inside;
|
||||
padding-left: 3px;
|
||||
}
|
||||
|
||||
dl dd {
|
||||
font-style: italic;
|
||||
font-weight: 100;
|
||||
}
|
||||
|
||||
footer {
|
||||
margin-top: 40px;
|
||||
padding-top: 20px;
|
||||
padding-bottom: 30px;
|
||||
font-size: 13px;
|
||||
color: #aaa;
|
||||
}
|
||||
|
||||
footer a {
|
||||
color: #666;
|
||||
}
|
||||
|
||||
/* MISC */
|
||||
.clearfix:after {
|
||||
clear: both;
|
||||
content: '.';
|
||||
display: block;
|
||||
visibility: hidden;
|
||||
height: 0;
|
||||
}
|
||||
|
||||
.clearfix {display: inline-block;}
|
||||
* html .clearfix {height: 1%;}
|
||||
.clearfix {display: block;}
|
69
stylesheets/pygment_trac.css
Normal file
|
@ -0,0 +1,69 @@
|
|||
.highlight { background: #ffffff; }
|
||||
.highlight .c { color: #999988; font-style: italic } /* Comment */
|
||||
.highlight .err { color: #a61717; background-color: #e3d2d2 } /* Error */
|
||||
.highlight .k { font-weight: bold } /* Keyword */
|
||||
.highlight .o { font-weight: bold } /* Operator */
|
||||
.highlight .cm { color: #999988; font-style: italic } /* Comment.Multiline */
|
||||
.highlight .cp { color: #999999; font-weight: bold } /* Comment.Preproc */
|
||||
.highlight .c1 { color: #999988; font-style: italic } /* Comment.Single */
|
||||
.highlight .cs { color: #999999; font-weight: bold; font-style: italic } /* Comment.Special */
|
||||
.highlight .gd { color: #000000; background-color: #ffdddd } /* Generic.Deleted */
|
||||
.highlight .gd .x { color: #000000; background-color: #ffaaaa } /* Generic.Deleted.Specific */
|
||||
.highlight .ge { font-style: italic } /* Generic.Emph */
|
||||
.highlight .gr { color: #aa0000 } /* Generic.Error */
|
||||
.highlight .gh { color: #999999 } /* Generic.Heading */
|
||||
.highlight .gi { color: #000000; background-color: #ddffdd } /* Generic.Inserted */
|
||||
.highlight .gi .x { color: #000000; background-color: #aaffaa } /* Generic.Inserted.Specific */
|
||||
.highlight .go { color: #888888 } /* Generic.Output */
|
||||
.highlight .gp { color: #555555 } /* Generic.Prompt */
|
||||
.highlight .gs { font-weight: bold } /* Generic.Strong */
|
||||
.highlight .gu { color: #800080; font-weight: bold; } /* Generic.Subheading */
|
||||
.highlight .gt { color: #aa0000 } /* Generic.Traceback */
|
||||
.highlight .kc { font-weight: bold } /* Keyword.Constant */
|
||||
.highlight .kd { font-weight: bold } /* Keyword.Declaration */
|
||||
.highlight .kn { font-weight: bold } /* Keyword.Namespace */
|
||||
.highlight .kp { font-weight: bold } /* Keyword.Pseudo */
|
||||
.highlight .kr { font-weight: bold } /* Keyword.Reserved */
|
||||
.highlight .kt { color: #445588; font-weight: bold } /* Keyword.Type */
|
||||
.highlight .m { color: #009999 } /* Literal.Number */
|
||||
.highlight .s { color: #d14 } /* Literal.String */
|
||||
.highlight .na { color: #008080 } /* Name.Attribute */
|
||||
.highlight .nb { color: #0086B3 } /* Name.Builtin */
|
||||
.highlight .nc { color: #445588; font-weight: bold } /* Name.Class */
|
||||
.highlight .no { color: #008080 } /* Name.Constant */
|
||||
.highlight .ni { color: #800080 } /* Name.Entity */
|
||||
.highlight .ne { color: #990000; font-weight: bold } /* Name.Exception */
|
||||
.highlight .nf { color: #990000; font-weight: bold } /* Name.Function */
|
||||
.highlight .nn { color: #555555 } /* Name.Namespace */
|
||||
.highlight .nt { color: #000080 } /* Name.Tag */
|
||||
.highlight .nv { color: #008080 } /* Name.Variable */
|
||||
.highlight .ow { font-weight: bold } /* Operator.Word */
|
||||
.highlight .w { color: #bbbbbb } /* Text.Whitespace */
|
||||
.highlight .mf { color: #009999 } /* Literal.Number.Float */
|
||||
.highlight .mh { color: #009999 } /* Literal.Number.Hex */
|
||||
.highlight .mi { color: #009999 } /* Literal.Number.Integer */
|
||||
.highlight .mo { color: #009999 } /* Literal.Number.Oct */
|
||||
.highlight .sb { color: #d14 } /* Literal.String.Backtick */
|
||||
.highlight .sc { color: #d14 } /* Literal.String.Char */
|
||||
.highlight .sd { color: #d14 } /* Literal.String.Doc */
|
||||
.highlight .s2 { color: #d14 } /* Literal.String.Double */
|
||||
.highlight .se { color: #d14 } /* Literal.String.Escape */
|
||||
.highlight .sh { color: #d14 } /* Literal.String.Heredoc */
|
||||
.highlight .si { color: #d14 } /* Literal.String.Interpol */
|
||||
.highlight .sx { color: #d14 } /* Literal.String.Other */
|
||||
.highlight .sr { color: #009926 } /* Literal.String.Regex */
|
||||
.highlight .s1 { color: #d14 } /* Literal.String.Single */
|
||||
.highlight .ss { color: #990073 } /* Literal.String.Symbol */
|
||||
.highlight .bp { color: #999999 } /* Name.Builtin.Pseudo */
|
||||
.highlight .vc { color: #008080 } /* Name.Variable.Class */
|
||||
.highlight .vg { color: #008080 } /* Name.Variable.Global */
|
||||
.highlight .vi { color: #008080 } /* Name.Variable.Instance */
|
||||
.highlight .il { color: #009999 } /* Literal.Number.Integer.Long */
|
||||
|
||||
.type-csharp .highlight .k { color: #0000FF }
|
||||
.type-csharp .highlight .kt { color: #0000FF }
|
||||
.type-csharp .highlight .nf { color: #000000; font-weight: normal }
|
||||
.type-csharp .highlight .nc { color: #2B91AF }
|
||||
.type-csharp .highlight .nn { color: #000000 }
|
||||
.type-csharp .highlight .s { color: #A31515 }
|
||||
.type-csharp .highlight .sc { color: #A31515 }
|
255
stylesheets/styles.css
Normal file
|
@ -0,0 +1,255 @@
|
|||
@import url(https://fonts.googleapis.com/css?family=Lato:300italic,700italic,300,700);
|
||||
|
||||
body {
|
||||
padding:50px;
|
||||
font:14px/1.5 Lato, "Helvetica Neue", Helvetica, Arial, sans-serif;
|
||||
color:#777;
|
||||
font-weight:300;
|
||||
}
|
||||
|
||||
h1, h2, h3, h4, h5, h6 {
|
||||
color:#222;
|
||||
margin:0 0 20px;
|
||||
}
|
||||
|
||||
p, ul, ol, table, pre, dl {
|
||||
margin:0 0 20px;
|
||||
}
|
||||
|
||||
h1, h2, h3 {
|
||||
line-height:1.1;
|
||||
}
|
||||
|
||||
h1 {
|
||||
font-size:28px;
|
||||
}
|
||||
|
||||
h2 {
|
||||
color:#393939;
|
||||
}
|
||||
|
||||
h3, h4, h5, h6 {
|
||||
color:#494949;
|
||||
}
|
||||
|
||||
a {
|
||||
color:#39c;
|
||||
font-weight:400;
|
||||
text-decoration:none;
|
||||
}
|
||||
|
||||
a small {
|
||||
font-size:11px;
|
||||
color:#777;
|
||||
margin-top:-0.6em;
|
||||
display:block;
|
||||
}
|
||||
|
||||
.wrapper {
|
||||
width:860px;
|
||||
margin:0 auto;
|
||||
}
|
||||
|
||||
blockquote {
|
||||
border-left:1px solid #e5e5e5;
|
||||
margin:0;
|
||||
padding:0 0 0 20px;
|
||||
font-style:italic;
|
||||
}
|
||||
|
||||
code, pre {
|
||||
font-family:Monaco, Bitstream Vera Sans Mono, Lucida Console, Terminal;
|
||||
color:#333;
|
||||
font-size:12px;
|
||||
}
|
||||
|
||||
pre {
|
||||
padding:8px 15px;
|
||||
background: #f8f8f8;
|
||||
border-radius:5px;
|
||||
border:1px solid #e5e5e5;
|
||||
overflow-x: auto;
|
||||
}
|
||||
|
||||
table {
|
||||
width:100%;
|
||||
border-collapse:collapse;
|
||||
}
|
||||
|
||||
th, td {
|
||||
text-align:left;
|
||||
padding:5px 10px;
|
||||
border-bottom:1px solid #e5e5e5;
|
||||
}
|
||||
|
||||
dt {
|
||||
color:#444;
|
||||
font-weight:700;
|
||||
}
|
||||
|
||||
th {
|
||||
color:#444;
|
||||
}
|
||||
|
||||
img {
|
||||
max-width:100%;
|
||||
}
|
||||
|
||||
header {
|
||||
width:270px;
|
||||
float:left;
|
||||
position:fixed;
|
||||
}
|
||||
|
||||
header ul {
|
||||
list-style:none;
|
||||
height:40px;
|
||||
|
||||
padding:0;
|
||||
|
||||
background: #eee;
|
||||
background: -moz-linear-gradient(top, #f8f8f8 0%, #dddddd 100%);
|
||||
background: -webkit-gradient(linear, left top, left bottom, color-stop(0%,#f8f8f8), color-stop(100%,#dddddd));
|
||||
background: -webkit-linear-gradient(top, #f8f8f8 0%,#dddddd 100%);
|
||||
background: -o-linear-gradient(top, #f8f8f8 0%,#dddddd 100%);
|
||||
background: -ms-linear-gradient(top, #f8f8f8 0%,#dddddd 100%);
|
||||
background: linear-gradient(top, #f8f8f8 0%,#dddddd 100%);
|
||||
|
||||
border-radius:5px;
|
||||
border:1px solid #d2d2d2;
|
||||
box-shadow:inset #fff 0 1px 0, inset rgba(0,0,0,0.03) 0 -1px 0;
|
||||
width:270px;
|
||||
}
|
||||
|
||||
header li {
|
||||
width:89px;
|
||||
float:left;
|
||||
border-right:1px solid #d2d2d2;
|
||||
height:40px;
|
||||
}
|
||||
|
||||
header ul a {
|
||||
line-height:1;
|
||||
font-size:11px;
|
||||
color:#999;
|
||||
display:block;
|
||||
text-align:center;
|
||||
padding-top:6px;
|
||||
height:40px;
|
||||
}
|
||||
|
||||
strong {
|
||||
color:#222;
|
||||
font-weight:700;
|
||||
}
|
||||
|
||||
header ul li + li {
|
||||
width:88px;
|
||||
border-left:1px solid #fff;
|
||||
}
|
||||
|
||||
header ul li + li + li {
|
||||
border-right:none;
|
||||
width:89px;
|
||||
}
|
||||
|
||||
header ul a strong {
|
||||
font-size:14px;
|
||||
display:block;
|
||||
color:#222;
|
||||
}
|
||||
|
||||
section {
|
||||
width:500px;
|
||||
float:right;
|
||||
padding-bottom:50px;
|
||||
}
|
||||
|
||||
small {
|
||||
font-size:11px;
|
||||
}
|
||||
|
||||
hr {
|
||||
border:0;
|
||||
background:#e5e5e5;
|
||||
height:1px;
|
||||
margin:0 0 20px;
|
||||
}
|
||||
|
||||
footer {
|
||||
width:270px;
|
||||
float:left;
|
||||
position:fixed;
|
||||
bottom:50px;
|
||||
}
|
||||
|
||||
@media print, screen and (max-width: 960px) {
|
||||
|
||||
div.wrapper {
|
||||
width:auto;
|
||||
margin:0;
|
||||
}
|
||||
|
||||
header, section, footer {
|
||||
float:none;
|
||||
position:static;
|
||||
width:auto;
|
||||
}
|
||||
|
||||
header {
|
||||
padding-right:320px;
|
||||
}
|
||||
|
||||
section {
|
||||
border:1px solid #e5e5e5;
|
||||
border-width:1px 0;
|
||||
padding:20px 0;
|
||||
margin:0 0 20px;
|
||||
}
|
||||
|
||||
header a small {
|
||||
display:inline;
|
||||
}
|
||||
|
||||
header ul {
|
||||
position:absolute;
|
||||
right:50px;
|
||||
top:52px;
|
||||
}
|
||||
}
|
||||
|
||||
@media print, screen and (max-width: 720px) {
|
||||
body {
|
||||
word-wrap:break-word;
|
||||
}
|
||||
|
||||
header {
|
||||
padding:0;
|
||||
}
|
||||
|
||||
header ul, header p.view {
|
||||
position:static;
|
||||
}
|
||||
|
||||
pre, code {
|
||||
word-wrap:normal;
|
||||
}
|
||||
}
|
||||
|
||||
@media print, screen and (max-width: 480px) {
|
||||
body {
|
||||
padding:15px;
|
||||
}
|
||||
|
||||
header ul {
|
||||
display:none;
|
||||
}
|
||||
}
|
||||
|
||||
@media print {
|
||||
body {
|
||||
padding:0.4in;
|
||||
font-size:12pt;
|
||||
color:#444;
|
||||
}
|
||||
}
|
423
stylesheets/stylesheet.css
Normal file
|
@ -0,0 +1,423 @@
|
|||
/*******************************************************************************
|
||||
Slate Theme for GitHub Pages
|
||||
by Jason Costello, @jsncostello
|
||||
*******************************************************************************/
|
||||
|
||||
@import url(pygment_trac.css);
|
||||
|
||||
/*******************************************************************************
|
||||
MeyerWeb Reset
|
||||
*******************************************************************************/
|
||||
|
||||
html, body, div, span, applet, object, iframe,
|
||||
h1, h2, h3, h4, h5, h6, p, blockquote, pre,
|
||||
a, abbr, acronym, address, big, cite, code,
|
||||
del, dfn, em, img, ins, kbd, q, s, samp,
|
||||
small, strike, strong, sub, sup, tt, var,
|
||||
b, u, i, center,
|
||||
dl, dt, dd, ol, ul, li,
|
||||
fieldset, form, label, legend,
|
||||
table, caption, tbody, tfoot, thead, tr, th, td,
|
||||
article, aside, canvas, details, embed,
|
||||
figure, figcaption, footer, header, hgroup,
|
||||
menu, nav, output, ruby, section, summary,
|
||||
time, mark, audio, video {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
border: 0;
|
||||
font: inherit;
|
||||
vertical-align: baseline;
|
||||
}
|
||||
|
||||
/* HTML5 display-role reset for older browsers */
|
||||
article, aside, details, figcaption, figure,
|
||||
footer, header, hgroup, menu, nav, section {
|
||||
display: block;
|
||||
}
|
||||
|
||||
ol, ul {
|
||||
list-style: none;
|
||||
}
|
||||
|
||||
table {
|
||||
border-collapse: collapse;
|
||||
border-spacing: 0;
|
||||
}
|
||||
|
||||
/*******************************************************************************
|
||||
Theme Styles
|
||||
*******************************************************************************/
|
||||
|
||||
body {
|
||||
box-sizing: border-box;
|
||||
color:#373737;
|
||||
background: #212121;
|
||||
font-size: 16px;
|
||||
font-family: 'Myriad Pro', Calibri, Helvetica, Arial, sans-serif;
|
||||
line-height: 1.5;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
}
|
||||
|
||||
h1, h2, h3, h4, h5, h6 {
|
||||
margin: 10px 0;
|
||||
font-weight: 700;
|
||||
color:#222222;
|
||||
font-family: 'Lucida Grande', 'Calibri', Helvetica, Arial, sans-serif;
|
||||
letter-spacing: -1px;
|
||||
}
|
||||
|
||||
h1 {
|
||||
font-size: 36px;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
h2 {
|
||||
padding-bottom: 10px;
|
||||
font-size: 32px;
|
||||
background: url('../images/bg_hr.png') repeat-x bottom;
|
||||
}
|
||||
|
||||
h3 {
|
||||
font-size: 24px;
|
||||
}
|
||||
|
||||
h4 {
|
||||
font-size: 21px;
|
||||
}
|
||||
|
||||
h5 {
|
||||
font-size: 18px;
|
||||
}
|
||||
|
||||
h6 {
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
p {
|
||||
margin: 10px 0 15px 0;
|
||||
}
|
||||
|
||||
footer p {
|
||||
color: #f2f2f2;
|
||||
}
|
||||
|
||||
a {
|
||||
text-decoration: none;
|
||||
color: #007edf;
|
||||
text-shadow: none;
|
||||
|
||||
transition: color 0.5s ease;
|
||||
transition: text-shadow 0.5s ease;
|
||||
-webkit-transition: color 0.5s ease;
|
||||
-webkit-transition: text-shadow 0.5s ease;
|
||||
-moz-transition: color 0.5s ease;
|
||||
-moz-transition: text-shadow 0.5s ease;
|
||||
-o-transition: color 0.5s ease;
|
||||
-o-transition: text-shadow 0.5s ease;
|
||||
-ms-transition: color 0.5s ease;
|
||||
-ms-transition: text-shadow 0.5s ease;
|
||||
}
|
||||
|
||||
a:hover, a:focus {text-decoration: underline;}
|
||||
|
||||
footer a {
|
||||
color: #F2F2F2;
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
em {
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
strong {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
img {
|
||||
position: relative;
|
||||
margin: 0 auto;
|
||||
max-width: 739px;
|
||||
padding: 5px;
|
||||
margin: 10px 0 10px 0;
|
||||
border: 1px solid #ebebeb;
|
||||
|
||||
box-shadow: 0 0 5px #ebebeb;
|
||||
-webkit-box-shadow: 0 0 5px #ebebeb;
|
||||
-moz-box-shadow: 0 0 5px #ebebeb;
|
||||
-o-box-shadow: 0 0 5px #ebebeb;
|
||||
-ms-box-shadow: 0 0 5px #ebebeb;
|
||||
}
|
||||
|
||||
p img {
|
||||
display: inline;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
vertical-align: middle;
|
||||
text-align: center;
|
||||
border: none;
|
||||
}
|
||||
|
||||
pre, code {
|
||||
width: 100%;
|
||||
color: #222;
|
||||
background-color: #fff;
|
||||
|
||||
font-family: Monaco, "Bitstream Vera Sans Mono", "Lucida Console", Terminal, monospace;
|
||||
font-size: 14px;
|
||||
|
||||
border-radius: 2px;
|
||||
-moz-border-radius: 2px;
|
||||
-webkit-border-radius: 2px;
|
||||
}
|
||||
|
||||
pre {
|
||||
width: 100%;
|
||||
padding: 10px;
|
||||
box-shadow: 0 0 10px rgba(0,0,0,.1);
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
code {
|
||||
padding: 3px;
|
||||
margin: 0 3px;
|
||||
box-shadow: 0 0 10px rgba(0,0,0,.1);
|
||||
}
|
||||
|
||||
pre code {
|
||||
display: block;
|
||||
box-shadow: none;
|
||||
}
|
||||
|
||||
blockquote {
|
||||
color: #666;
|
||||
margin-bottom: 20px;
|
||||
padding: 0 0 0 20px;
|
||||
border-left: 3px solid #bbb;
|
||||
}
|
||||
|
||||
|
||||
ul, ol, dl {
|
||||
margin-bottom: 15px
|
||||
}
|
||||
|
||||
ul {
|
||||
list-style: inside;
|
||||
padding-left: 20px;
|
||||
}
|
||||
|
||||
ol {
|
||||
list-style: decimal inside;
|
||||
padding-left: 20px;
|
||||
}
|
||||
|
||||
dl dt {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
dl dd {
|
||||
padding-left: 20px;
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
dl p {
|
||||
padding-left: 20px;
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
hr {
|
||||
height: 1px;
|
||||
margin-bottom: 5px;
|
||||
border: none;
|
||||
background: url('../images/bg_hr.png') repeat-x center;
|
||||
}
|
||||
|
||||
table {
|
||||
border: 1px solid #373737;
|
||||
margin-bottom: 20px;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
th {
|
||||
font-family: 'Lucida Grande', 'Helvetica Neue', Helvetica, Arial, sans-serif;
|
||||
padding: 10px;
|
||||
background: #373737;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
td {
|
||||
padding: 10px;
|
||||
border: 1px solid #373737;
|
||||
}
|
||||
|
||||
form {
|
||||
background: #f2f2f2;
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
/*******************************************************************************
|
||||
Full-Width Styles
|
||||
*******************************************************************************/
|
||||
|
||||
.outer {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.inner {
|
||||
position: relative;
|
||||
max-width: 640px;
|
||||
padding: 20px 10px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
#forkme_banner {
|
||||
display: block;
|
||||
position: absolute;
|
||||
top:0;
|
||||
right: 10px;
|
||||
z-index: 10;
|
||||
padding: 10px 50px 10px 10px;
|
||||
color: #fff;
|
||||
background: url('../images/blacktocat.png') #0090ff no-repeat 95% 50%;
|
||||
font-weight: 700;
|
||||
box-shadow: 0 0 10px rgba(0,0,0,.5);
|
||||
border-bottom-left-radius: 2px;
|
||||
border-bottom-right-radius: 2px;
|
||||
}
|
||||
|
||||
#header_wrap {
|
||||
background: #212121;
|
||||
background: -moz-linear-gradient(top, #373737, #212121);
|
||||
background: -webkit-linear-gradient(top, #373737, #212121);
|
||||
background: -ms-linear-gradient(top, #373737, #212121);
|
||||
background: -o-linear-gradient(top, #373737, #212121);
|
||||
background: linear-gradient(top, #373737, #212121);
|
||||
}
|
||||
|
||||
#header_wrap .inner {
|
||||
padding: 50px 10px 30px 10px;
|
||||
}
|
||||
|
||||
#project_title {
|
||||
margin: 0;
|
||||
color: #fff;
|
||||
font-size: 42px;
|
||||
font-weight: 700;
|
||||
text-shadow: #111 0px 0px 10px;
|
||||
}
|
||||
|
||||
#project_tagline {
|
||||
color: #fff;
|
||||
font-size: 24px;
|
||||
font-weight: 300;
|
||||
background: none;
|
||||
text-shadow: #111 0px 0px 10px;
|
||||
}
|
||||
|
||||
#downloads {
|
||||
position: absolute;
|
||||
width: 210px;
|
||||
z-index: 10;
|
||||
bottom: -40px;
|
||||
right: 0;
|
||||
height: 70px;
|
||||
background: url('../images/icon_download.png') no-repeat 0% 90%;
|
||||
}
|
||||
|
||||
.zip_download_link {
|
||||
display: block;
|
||||
float: right;
|
||||
width: 90px;
|
||||
height:70px;
|
||||
text-indent: -5000px;
|
||||
overflow: hidden;
|
||||
background: url(../images/sprite_download.png) no-repeat bottom left;
|
||||
}
|
||||
|
||||
.tar_download_link {
|
||||
display: block;
|
||||
float: right;
|
||||
width: 90px;
|
||||
height:70px;
|
||||
text-indent: -5000px;
|
||||
overflow: hidden;
|
||||
background: url(../images/sprite_download.png) no-repeat bottom right;
|
||||
margin-left: 10px;
|
||||
}
|
||||
|
||||
.zip_download_link:hover {
|
||||
background: url(../images/sprite_download.png) no-repeat top left;
|
||||
}
|
||||
|
||||
.tar_download_link:hover {
|
||||
background: url(../images/sprite_download.png) no-repeat top right;
|
||||
}
|
||||
|
||||
#main_content_wrap {
|
||||
background: #f2f2f2;
|
||||
border-top: 1px solid #111;
|
||||
border-bottom: 1px solid #111;
|
||||
}
|
||||
|
||||
#main_content {
|
||||
padding-top: 40px;
|
||||
}
|
||||
|
||||
#footer_wrap {
|
||||
background: #212121;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
Small Device Styles
|
||||
*******************************************************************************/
|
||||
|
||||
@media screen and (max-width: 480px) {
|
||||
body {
|
||||
font-size:14px;
|
||||
}
|
||||
|
||||
#downloads {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.inner {
|
||||
min-width: 320px;
|
||||
max-width: 480px;
|
||||
}
|
||||
|
||||
#project_title {
|
||||
font-size: 32px;
|
||||
}
|
||||
|
||||
h1 {
|
||||
font-size: 28px;
|
||||
}
|
||||
|
||||
h2 {
|
||||
font-size: 24px;
|
||||
}
|
||||
|
||||
h3 {
|
||||
font-size: 21px;
|
||||
}
|
||||
|
||||
h4 {
|
||||
font-size: 18px;
|
||||
}
|
||||
|
||||
h5 {
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
h6 {
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
code, pre {
|
||||
min-width: 320px;
|
||||
max-width: 480px;
|
||||
font-size: 11px;
|
||||
}
|
||||
|
||||
}
|