mirror of
https://github.com/overte-org/overte.git
synced 2025-04-21 06:44:06 +02:00
Merge branch 'master' of https://github.com/highfidelity/hifi into decouple-avatar-updates
This commit is contained in:
commit
32eb51d0fa
49 changed files with 1767 additions and 1096 deletions
|
@ -11,6 +11,8 @@
|
|||
|
||||
#include "NodeConnectionData.h"
|
||||
|
||||
#include <QtCore/QDataStream>
|
||||
|
||||
NodeConnectionData NodeConnectionData::fromDataStream(QDataStream& dataStream, const HifiSockAddr& senderSockAddr,
|
||||
bool isConnectRequest) {
|
||||
NodeConnectionData newHeader;
|
||||
|
|
|
@ -1,307 +1,319 @@
|
|||
//
|
||||
// hydraGrab.js
|
||||
// examples
|
||||
//
|
||||
// Created by Clément Brisset on 4/24/14.
|
||||
// Updated by Eric Levin on 5/14/15.
|
||||
// Copyright 2014 High Fidelity, Inc.
|
||||
// Created by Eric Levin on 9/2/15
|
||||
// Copyright 2015 High Fidelity, Inc.
|
||||
//
|
||||
// This script allows you to grab and move/rotate physical objects with the hydra
|
||||
//
|
||||
// Using the hydras :
|
||||
// grab physical entities with the right trigger
|
||||
// Grab's physically moveable entities with the hydra- works for either near or far objects. User can also grab a far away object and drag it towards them by pressing the "4" button on either the left or ride controller.
|
||||
//
|
||||
// Distributed under the Apache License, Version 2.0.
|
||||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
//
|
||||
|
||||
|
||||
var entityProps, currentPosition, currentVelocity, currentRotation, distanceToTarget, velocityTowardTarget, desiredVelocity;
|
||||
var addedVelocity, newVelocity, angularVelocity, dT, cameraEntityDistance;
|
||||
var LEFT = 0;
|
||||
var RIGHT = 1;
|
||||
var LASER_WIDTH = 3;
|
||||
var LASER_COLOR = {
|
||||
red: 50,
|
||||
green: 150,
|
||||
blue: 200
|
||||
};
|
||||
var LASER_HOVER_COLOR = {
|
||||
red: 200,
|
||||
green: 50,
|
||||
blue: 50
|
||||
};
|
||||
var RIGHT_HAND_CLICK = Controller.findAction("RIGHT_HAND_CLICK");
|
||||
var rightTriggerAction = RIGHT_HAND_CLICK;
|
||||
|
||||
var DROP_DISTANCE = 5.0;
|
||||
var DROP_COLOR = {
|
||||
red: 200,
|
||||
green: 200,
|
||||
blue: 200
|
||||
};
|
||||
var LEFT_HAND_CLICK = Controller.findAction("LEFT_HAND_CLICK");
|
||||
var leftTriggerAction = LEFT_HAND_CLICK;
|
||||
|
||||
var FULL_STRENGTH = 0.05;
|
||||
var LASER_LENGTH_FACTOR = 500;
|
||||
var CLOSE_ENOUGH = 0.001;
|
||||
var SPRING_RATE = 1.5;
|
||||
var DAMPING_RATE = 0.8;
|
||||
var SCREEN_TO_METERS = 0.001;
|
||||
var DISTANCE_SCALE_FACTOR = 1000
|
||||
|
||||
var grabSound = SoundCache.getSound("https://hifi-public.s3.amazonaws.com/eric/sounds/CloseClamp.wav");
|
||||
var releaseSound = SoundCache.getSound("https://hifi-public.s3.amazonaws.com/eric/sounds/ReleaseClamp.wav");
|
||||
|
||||
function getRayIntersection(pickRay) {
|
||||
var intersection = Entities.findRayIntersection(pickRay, true);
|
||||
return intersection;
|
||||
}
|
||||
|
||||
|
||||
function controller(side) {
|
||||
this.triggerHeld = false;
|
||||
this.triggerThreshold = 0.9;
|
||||
this.side = side;
|
||||
this.palm = 2 * side;
|
||||
this.tip = 2 * side + 1;
|
||||
this.trigger = side;
|
||||
this.originalGravity = {
|
||||
var ZERO_VEC = {
|
||||
x: 0,
|
||||
y: 0,
|
||||
z: 0
|
||||
};
|
||||
}
|
||||
var LINE_LENGTH = 500;
|
||||
var THICK_LINE_WIDTH = 7;
|
||||
var THIN_LINE_WIDTH = 2;
|
||||
|
||||
this.laser = Overlays.addOverlay("line3d", {
|
||||
start: {
|
||||
x: 0,
|
||||
y: 0,
|
||||
z: 0
|
||||
},
|
||||
end: {
|
||||
x: 0,
|
||||
y: 0,
|
||||
z: 0
|
||||
},
|
||||
color: LASER_COLOR,
|
||||
alpha: 1,
|
||||
lineWidth: LASER_WIDTH,
|
||||
anchor: "MyAvatar"
|
||||
});
|
||||
var NO_INTERSECT_COLOR = {
|
||||
red: 10,
|
||||
green: 10,
|
||||
blue: 255
|
||||
};
|
||||
var INTERSECT_COLOR = {
|
||||
red: 250,
|
||||
green: 10,
|
||||
blue: 10
|
||||
};
|
||||
|
||||
this.dropLine = Overlays.addOverlay("line3d", {
|
||||
color: DROP_COLOR,
|
||||
alpha: 1,
|
||||
visible: false,
|
||||
lineWidth: 2
|
||||
});
|
||||
var GRAB_RADIUS = 2;
|
||||
|
||||
var GRAB_COLOR = {
|
||||
red: 250,
|
||||
green: 10,
|
||||
blue: 250
|
||||
};
|
||||
var SHOW_LINE_THRESHOLD = 0.2;
|
||||
var DISTANCE_HOLD_THRESHOLD = 0.8;
|
||||
|
||||
this.update = function(deltaTime) {
|
||||
this.updateControllerState();
|
||||
this.moveLaser();
|
||||
this.checkTrigger();
|
||||
this.checkEntityIntersection();
|
||||
if (this.grabbing) {
|
||||
this.updateEntity(deltaTime);
|
||||
}
|
||||
var right4Action = 18;
|
||||
var left4Action = 17;
|
||||
|
||||
this.oldPalmPosition = this.palmPosition;
|
||||
this.oldTipPosition = this.tipPosition;
|
||||
}
|
||||
var TRACTOR_BEAM_VELOCITY_THRESHOLD = 0.5;
|
||||
|
||||
this.updateEntity = function(deltaTime) {
|
||||
this.dControllerPosition = Vec3.subtract(this.palmPosition, this.oldPalmPosition);
|
||||
this.cameraEntityDistance = Vec3.distance(Camera.getPosition(), this.currentPosition);
|
||||
this.targetPosition = Vec3.sum(this.targetPosition, Vec3.multiply(this.dControllerPosition, this.cameraEntityDistance * SCREEN_TO_METERS * DISTANCE_SCALE_FACTOR));
|
||||
var RIGHT = 1;
|
||||
var LEFT = 0;
|
||||
var rightController = new controller(RIGHT, rightTriggerAction, right4Action, "right")
|
||||
var leftController = new controller(LEFT, leftTriggerAction, left4Action, "left")
|
||||
|
||||
this.entityProps = Entities.getEntityProperties(this.grabbedEntity);
|
||||
this.currentPosition = this.entityProps.position;
|
||||
this.currentVelocity = this.entityProps.velocity;
|
||||
|
||||
var dPosition = Vec3.subtract(this.targetPosition, this.currentPosition);
|
||||
this.distanceToTarget = Vec3.length(dPosition);
|
||||
if (this.distanceToTarget > CLOSE_ENOUGH) {
|
||||
// compute current velocity in the direction we want to move
|
||||
this.velocityTowardTarget = Vec3.dot(this.currentVelocity, Vec3.normalize(dPosition));
|
||||
this.velocityTowardTarget = Vec3.multiply(Vec3.normalize(dPosition), this.velocityTowardTarget);
|
||||
// compute the speed we would like to be going toward the target position
|
||||
|
||||
this.desiredVelocity = Vec3.multiply(dPosition, (1.0 / deltaTime) * SPRING_RATE);
|
||||
// compute how much we want to add to the existing velocity
|
||||
this.addedVelocity = Vec3.subtract(this.desiredVelocity, this.velocityTowardTarget);
|
||||
//If target is to far, roll off force as inverse square of distance
|
||||
if(this.distanceToTarget/ this.cameraEntityDistance > FULL_STRENGTH) {
|
||||
this.addedVelocity = Vec3.multiply(this.addedVelocity, Math.pow(FULL_STRENGTH/ this.distanceToTarget, 2.0));
|
||||
}
|
||||
this.newVelocity = Vec3.sum(this.currentVelocity, this.addedVelocity);
|
||||
this.newVelocity = Vec3.subtract(this.newVelocity, Vec3.multiply(this.newVelocity, DAMPING_RATE));
|
||||
function controller(side, triggerAction, pullAction, hand) {
|
||||
this.hand = hand;
|
||||
if (hand === "right") {
|
||||
this.getHandPosition = MyAvatar.getRightPalmPosition;
|
||||
this.getHandRotation = MyAvatar.getRightPalmRotation;
|
||||
} else {
|
||||
this.newVelocity = {
|
||||
x: 0,
|
||||
y: 0,
|
||||
z: 0
|
||||
};
|
||||
}
|
||||
this.transformedAngularVelocity = Controller.getSpatialControlRawAngularVelocity(this.tip);
|
||||
this.transformedAngularVelocity = Vec3.multiplyQbyV(Camera.getOrientation(), this.transformedAngularVelocity);
|
||||
|
||||
Entities.editEntity(this.grabbedEntity, {
|
||||
velocity: this.newVelocity,
|
||||
angularVelocity: this.transformedAngularVelocity
|
||||
this.getHandPosition = MyAvatar.getLeftPalmPosition;
|
||||
this.getHandRotation = MyAvatar.getLeftPalmRotation;
|
||||
}
|
||||
this.triggerAction = triggerAction;
|
||||
this.pullAction = pullAction;
|
||||
this.actionID = null;
|
||||
this.tractorBeamActive = false;
|
||||
this.distanceHolding = false;
|
||||
this.closeGrabbing = false;
|
||||
this.triggerValue = 0;
|
||||
this.prevTriggerValue = 0;
|
||||
this.palm = 2 * side;
|
||||
this.tip = 2 * side + 1;
|
||||
this.pointer = Entities.addEntity({
|
||||
type: "Line",
|
||||
name: "pointer",
|
||||
color: NO_INTERSECT_COLOR,
|
||||
dimensions: {
|
||||
x: 1000,
|
||||
y: 1000,
|
||||
z: 1000
|
||||
},
|
||||
visible: false,
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
controller.prototype.updateLine = function() {
|
||||
var handPosition = Controller.getSpatialControlPosition(this.palm);
|
||||
var direction = Controller.getSpatialControlNormal(this.tip);
|
||||
|
||||
Entities.editEntity(this.pointer, {
|
||||
position: handPosition,
|
||||
linePoints: [
|
||||
ZERO_VEC,
|
||||
Vec3.multiply(direction, LINE_LENGTH)
|
||||
]
|
||||
});
|
||||
|
||||
this.updateDropLine(this.targetPosition);
|
||||
|
||||
}
|
||||
|
||||
|
||||
this.updateControllerState = function() {
|
||||
this.palmPosition = Controller.getSpatialControlPosition(this.palm);
|
||||
this.tipPosition = Controller.getSpatialControlPosition(this.tip);
|
||||
this.triggerValue = Controller.getTriggerValue(this.trigger);
|
||||
}
|
||||
|
||||
this.checkTrigger = function() {
|
||||
if (this.triggerValue > this.triggerThreshold && !this.triggerHeld) {
|
||||
this.triggerHeld = true;
|
||||
} else if (this.triggerValue < this.triggerThreshold && this.triggerHeld) {
|
||||
this.triggerHeld = false;
|
||||
if (this.grabbing) {
|
||||
this.release();
|
||||
}
|
||||
//only check if we havent already grabbed an object
|
||||
if (this.distanceHolding) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
//move origin a bit away from hand so nothing gets in way
|
||||
var origin = Vec3.sum(handPosition, direction);
|
||||
if (this.checkForIntersections(origin, direction)) {
|
||||
Entities.editEntity(this.pointer, {
|
||||
color: INTERSECT_COLOR,
|
||||
});
|
||||
} else {
|
||||
Entities.editEntity(this.pointer, {
|
||||
color: NO_INTERSECT_COLOR,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
this.updateDropLine = function(position) {
|
||||
|
||||
Overlays.editOverlay(this.dropLine, {
|
||||
visible: true,
|
||||
start: {
|
||||
x: position.x,
|
||||
y: position.y + DROP_DISTANCE,
|
||||
z: position.z
|
||||
},
|
||||
end: {
|
||||
x: position.x,
|
||||
y: position.y - DROP_DISTANCE,
|
||||
z: position.z
|
||||
}
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
this.checkEntityIntersection = function() {
|
||||
|
||||
controller.prototype.checkForIntersections = function(origin, direction) {
|
||||
var pickRay = {
|
||||
origin: this.palmPosition,
|
||||
direction: Vec3.normalize(Vec3.subtract(this.tipPosition, this.palmPosition))
|
||||
origin: origin,
|
||||
direction: direction
|
||||
};
|
||||
var intersection = getRayIntersection(pickRay, true);
|
||||
if (intersection.intersects && intersection.properties.collisionsWillMove) {
|
||||
this.laserWasHovered = true;
|
||||
if (this.triggerHeld && !this.grabbing) {
|
||||
this.grab(intersection.entityID);
|
||||
}
|
||||
Overlays.editOverlay(this.laser, {
|
||||
color: LASER_HOVER_COLOR
|
||||
});
|
||||
} else if (this.laserWasHovered) {
|
||||
this.laserWasHovered = false;
|
||||
Overlays.editOverlay(this.laser, {
|
||||
color: LASER_COLOR
|
||||
});
|
||||
|
||||
var intersection = Entities.findRayIntersection(pickRay, true);
|
||||
if (intersection.intersects && intersection.properties.collisionsWillMove === 1) {
|
||||
this.distanceToEntity = Vec3.distance(origin, intersection.properties.position);
|
||||
Entities.editEntity(this.pointer, {
|
||||
linePoints: [
|
||||
ZERO_VEC,
|
||||
Vec3.multiply(direction, this.distanceToEntity)
|
||||
]
|
||||
});
|
||||
this.grabbedEntity = intersection.entityID;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
this.grab = function(entityId) {
|
||||
this.grabbing = true;
|
||||
this.grabbedEntity = entityId;
|
||||
this.entityProps = Entities.getEntityProperties(this.grabbedEntity);
|
||||
this.targetPosition = this.entityProps.position;
|
||||
this.currentPosition = this.targetPosition;
|
||||
this.oldPalmPosition = this.palmPosition;
|
||||
this.originalGravity = this.entityProps.gravity;
|
||||
Entities.editEntity(this.grabbedEntity, {
|
||||
gravity: {
|
||||
x: 0,
|
||||
y: 0,
|
||||
z: 0
|
||||
}
|
||||
});
|
||||
Overlays.editOverlay(this.laser, {
|
||||
visible: false
|
||||
});
|
||||
Audio.playSound(grabSound, {
|
||||
position: this.entityProps.position,
|
||||
volume: 0.25
|
||||
});
|
||||
}
|
||||
controller.prototype.attemptMove = function() {
|
||||
if (this.tractorBeamActive) {
|
||||
return;
|
||||
}
|
||||
if (this.grabbedEntity || this.distanceHolding) {
|
||||
var handPosition = Controller.getSpatialControlPosition(this.palm);
|
||||
var direction = Controller.getSpatialControlNormal(this.tip);
|
||||
|
||||
this.release = function() {
|
||||
this.grabbing = false;
|
||||
var newPosition = Vec3.sum(handPosition, Vec3.multiply(direction, this.distanceToEntity))
|
||||
this.distanceHolding = true;
|
||||
//TO DO : USE SPRING ACTION UPDATE FOR MOVING
|
||||
if (this.actionID === null) {
|
||||
this.actionID = Entities.addAction("spring", this.grabbedEntity, {
|
||||
targetPosition: newPosition,
|
||||
linearTimeScale: .1
|
||||
});
|
||||
} else {
|
||||
Entities.updateAction(this.grabbedEntity, this.actionID, {
|
||||
targetPosition: newPosition
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
controller.prototype.showPointer = function() {
|
||||
Entities.editEntity(this.pointer, {
|
||||
visible: true
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
controller.prototype.hidePointer = function() {
|
||||
Entities.editEntity(this.pointer, {
|
||||
visible: false
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
controller.prototype.letGo = function() {
|
||||
Entities.deleteAction(this.grabbedEntity, this.actionID);
|
||||
this.grabbedEntity = null;
|
||||
Overlays.editOverlay(this.laser, {
|
||||
visible: true
|
||||
});
|
||||
Overlays.editOverlay(this.dropLine, {
|
||||
visible: false
|
||||
});
|
||||
this.actionID = null;
|
||||
this.distanceHolding = false;
|
||||
this.tractorBeamActive = false;
|
||||
this.checkForEntityArrival = false;
|
||||
this.closeGrabbing = false;
|
||||
}
|
||||
|
||||
Audio.playSound(releaseSound, {
|
||||
position: this.entityProps.position,
|
||||
volume: 0.25
|
||||
});
|
||||
|
||||
// only restore the original gravity if it's not zero. This is to avoid...
|
||||
// 1. interface A grabs an entity and locally saves off its gravity
|
||||
// 2. interface A sets the entity's gravity to zero
|
||||
// 3. interface B grabs the entity and saves off its gravity (which is zero)
|
||||
// 4. interface A releases the entity and puts the original gravity back
|
||||
// 5. interface B releases the entity and puts the original gravity back (to zero)
|
||||
if(vectorIsZero(this.originalGravity)) {
|
||||
Entities.editEntity(this.grabbedEntity, {
|
||||
gravity: this.originalGravity
|
||||
});
|
||||
controller.prototype.update = function() {
|
||||
if (this.tractorBeamActive && this.checkForEntityArrival) {
|
||||
var entityVelocity = Entities.getEntityProperties(this.grabbedEntity).velocity
|
||||
if (Vec3.length(entityVelocity) < TRACTOR_BEAM_VELOCITY_THRESHOLD) {
|
||||
this.letGo();
|
||||
}
|
||||
return;
|
||||
}
|
||||
this.triggerValue = Controller.getActionValue(this.triggerAction);
|
||||
if (this.triggerValue > SHOW_LINE_THRESHOLD && this.prevTriggerValue < SHOW_LINE_THRESHOLD) {
|
||||
//First check if an object is within close range and then run the close grabbing logic
|
||||
if (this.checkForInRangeObject()) {
|
||||
this.grabEntity();
|
||||
} else {
|
||||
this.showPointer();
|
||||
this.shouldDisplayLine = true;
|
||||
}
|
||||
} else if (this.triggerValue < SHOW_LINE_THRESHOLD && this.prevTriggerValue > SHOW_LINE_THRESHOLD) {
|
||||
this.hidePointer();
|
||||
this.letGo();
|
||||
this.shouldDisplayLine = false;
|
||||
}
|
||||
}
|
||||
|
||||
this.moveLaser = function() {
|
||||
var inverseRotation = Quat.inverse(MyAvatar.orientation);
|
||||
var startPosition = Vec3.multiplyQbyV(inverseRotation, Vec3.subtract(this.palmPosition, MyAvatar.position));
|
||||
// startPosition = Vec3.multiply(startPosition, 1 / MyAvatar.scale);
|
||||
var direction = Vec3.multiplyQbyV(inverseRotation, Vec3.subtract(this.tipPosition, this.palmPosition));
|
||||
direction = Vec3.multiply(direction, LASER_LENGTH_FACTOR / (Vec3.length(direction) * MyAvatar.scale));
|
||||
var endPosition = Vec3.sum(startPosition, direction);
|
||||
if (this.shouldDisplayLine) {
|
||||
this.updateLine();
|
||||
}
|
||||
if (this.triggerValue > DISTANCE_HOLD_THRESHOLD && !this.closeGrabbing) {
|
||||
this.attemptMove();
|
||||
}
|
||||
|
||||
Overlays.editOverlay(this.laser, {
|
||||
start: startPosition,
|
||||
end: endPosition
|
||||
|
||||
this.prevTriggerValue = this.triggerValue;
|
||||
}
|
||||
|
||||
controller.prototype.grabEntity = function() {
|
||||
var handRotation = this.getHandRotation();
|
||||
var handPosition = this.getHandPosition();
|
||||
|
||||
var objectRotation = Entities.getEntityProperties(this.grabbedEntity).rotation;
|
||||
var offsetRotation = Quat.multiply(Quat.inverse(handRotation), objectRotation);
|
||||
|
||||
var objectPosition = Entities.getEntityProperties(this.grabbedEntity).position;
|
||||
var offset = Vec3.subtract(objectPosition, handPosition);
|
||||
var offsetPosition = Vec3.multiplyQbyV(Quat.inverse(Quat.multiply(handRotation, offsetRotation)), offset);
|
||||
this.closeGrabbing = true;
|
||||
this.actionID = Entities.addAction("hold", this.grabbedEntity, {
|
||||
relativePosition: offsetPosition,
|
||||
relativeRotation: offsetRotation,
|
||||
hand: this.hand,
|
||||
timeScale: 0.05
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
this.cleanup = function() {
|
||||
Overlays.deleteOverlay(this.laser);
|
||||
Overlays.deleteOverlay(this.dropLine);
|
||||
}
|
||||
}
|
||||
|
||||
function update(deltaTime) {
|
||||
rightController.update(deltaTime);
|
||||
leftController.update(deltaTime);
|
||||
|
||||
controller.prototype.checkForInRangeObject = function() {
|
||||
var handPosition = Controller.getSpatialControlPosition(this.palm);
|
||||
var entities = Entities.findEntities(handPosition, GRAB_RADIUS);
|
||||
var minDistance = GRAB_RADIUS;
|
||||
var grabbedEntity = null;
|
||||
//Get nearby entities and assign nearest
|
||||
for (var i = 0; i < entities.length; i++) {
|
||||
var props = Entities.getEntityProperties(entities[i]);
|
||||
var distance = Vec3.distance(props.position, handPosition);
|
||||
if (distance < minDistance && props.name !== "pointer" && props.collisionsWillMove === 1) {
|
||||
grabbedEntity = entities[i];
|
||||
minDistance = distance;
|
||||
}
|
||||
}
|
||||
if (grabbedEntity === null) {
|
||||
return false;
|
||||
} else {
|
||||
this.grabbedEntity = grabbedEntity;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
function scriptEnding() {
|
||||
rightController.cleanup();
|
||||
leftController.cleanup();
|
||||
|
||||
controller.prototype.onActionEvent = function(action, state) {
|
||||
if (this.pullAction === action && state === 1) {
|
||||
if (this.actionID !== null) {
|
||||
var self = this;
|
||||
this.tractorBeamActive = true;
|
||||
//We need to wait a bit before checking for entity arrival at target destination (meaning checking for velocity being close to some
|
||||
//low threshold) because otherwise we'll think the entity has arrived before its even really gotten moving!
|
||||
Script.setTimeout(function() {
|
||||
self.checkForEntityArrival = true;
|
||||
}, 500);
|
||||
var handPosition = Controller.getSpatialControlPosition(this.palm);
|
||||
var direction = Controller.getSpatialControlNormal(this.tip);
|
||||
//move final destination along line a bit, so it doesnt hit avatar hand
|
||||
Entities.updateAction(this.grabbedEntity, this.actionID, {
|
||||
targetPosition: Vec3.sum(handPosition, Vec3.multiply(2, direction))
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
function vectorIsZero(v) {
|
||||
return v.x === 0 && v.y === 0 && v.z === 0;
|
||||
controller.prototype.cleanup = function() {
|
||||
Entities.deleteEntity(this.pointer);
|
||||
Entities.deleteAction(this.grabbedEntity, this.actionID);
|
||||
}
|
||||
|
||||
var rightController = new controller(RIGHT);
|
||||
var leftController = new controller(LEFT);
|
||||
function update() {
|
||||
rightController.update();
|
||||
leftController.update();
|
||||
}
|
||||
|
||||
function onActionEvent(action, state) {
|
||||
rightController.onActionEvent(action, state);
|
||||
leftController.onActionEvent(action, state);
|
||||
|
||||
}
|
||||
|
||||
|
||||
Script.update.connect(update);
|
||||
Script.scriptEnding.connect(scriptEnding);
|
||||
function cleanup() {
|
||||
rightController.cleanup();
|
||||
leftController.cleanup();
|
||||
}
|
||||
|
||||
|
||||
Script.scriptEnding.connect(cleanup);
|
||||
Script.update.connect(update)
|
||||
Controller.actionEvent.connect(onActionEvent);
|
|
@ -13,32 +13,7 @@ Script.include("http://s3.amazonaws.com/hifi-public/scripts/libraries/toolBars.j
|
|||
|
||||
HIFI_PUBLIC_BUCKET = "http://s3.amazonaws.com/hifi-public/";
|
||||
|
||||
var nullActionID = "00000000-0000-0000-0000-000000000000";
|
||||
var controllerID;
|
||||
var controllerActive;
|
||||
var leftHandObjectID = null;
|
||||
var rightHandObjectID = null;
|
||||
var leftHandActionID = nullActionID;
|
||||
var rightHandActionID = nullActionID;
|
||||
|
||||
var TRIGGER_THRESHOLD = 0.2;
|
||||
var GRAB_RADIUS = 0.15;
|
||||
|
||||
var LEFT_HAND_CLICK = Controller.findAction("LEFT_HAND_CLICK");
|
||||
var RIGHT_HAND_CLICK = Controller.findAction("RIGHT_HAND_CLICK");
|
||||
var ACTION1 = Controller.findAction("ACTION1");
|
||||
var ACTION2 = Controller.findAction("ACTION2");
|
||||
|
||||
var rightHandGrabAction = RIGHT_HAND_CLICK;
|
||||
var leftHandGrabAction = LEFT_HAND_CLICK;
|
||||
|
||||
var rightHandGrabValue = 0;
|
||||
var leftHandGrabValue = 0;
|
||||
var prevRightHandGrabValue = 0
|
||||
var prevLeftHandGrabValue = 0;
|
||||
|
||||
var grabColor = { red: 0, green: 255, blue: 0};
|
||||
var releaseColor = { red: 0, green: 0, blue: 255};
|
||||
|
||||
var toolBar = new ToolBar(0, 0, ToolBar.vertical, "highfidelity.toybox.toolbar", function() {
|
||||
return {
|
||||
|
@ -63,25 +38,6 @@ var cleanupButton = toolBar.addOverlay("image", {
|
|||
alpha: 1
|
||||
});
|
||||
|
||||
var overlays = false;
|
||||
var leftHandOverlay;
|
||||
var rightHandOverlay;
|
||||
if (overlays) {
|
||||
leftHandOverlay = Overlays.addOverlay("sphere", {
|
||||
position: MyAvatar.getLeftPalmPosition(),
|
||||
size: GRAB_RADIUS,
|
||||
color: releaseColor,
|
||||
alpha: 0.5,
|
||||
solid: false
|
||||
});
|
||||
rightHandOverlay = Overlays.addOverlay("sphere", {
|
||||
position: MyAvatar.getRightPalmPosition(),
|
||||
size: GRAB_RADIUS,
|
||||
color: releaseColor,
|
||||
alpha: 0.5,
|
||||
solid: false
|
||||
});
|
||||
}
|
||||
|
||||
var OBJECT_HEIGHT_OFFSET = 0.5;
|
||||
var MIN_OBJECT_SIZE = 0.05;
|
||||
|
@ -98,8 +54,6 @@ var GRAVITY = {
|
|||
z: 0.0
|
||||
}
|
||||
|
||||
var LEFT = 0;
|
||||
var RIGHT = 1;
|
||||
|
||||
var tableCreated = false;
|
||||
|
||||
|
@ -108,7 +62,6 @@ var tableEntities = Array(NUM_OBJECTS + 1); // Also includes table
|
|||
|
||||
var VELOCITY_MAG = 0.3;
|
||||
|
||||
var entitiesToResize = [];
|
||||
|
||||
var MODELS = Array(
|
||||
{ modelURL: "https://hifi-public.s3.amazonaws.com/ozan/props/sword/sword.fbx" },
|
||||
|
@ -136,196 +89,15 @@ var COLLISION_SOUNDS = Array(
|
|||
var RESIZE_TIMER = 0.0;
|
||||
var RESIZE_WAIT = 0.05; // 50 milliseconds
|
||||
|
||||
var leftFist = Entities.addEntity( {
|
||||
type: "Sphere",
|
||||
shapeType: 'sphere',
|
||||
position: MyAvatar.getLeftPalmPosition(),
|
||||
dimensions: { x: GRAB_RADIUS, y: GRAB_RADIUS, z: GRAB_RADIUS },
|
||||
rotation: MyAvatar.getLeftPalmRotation(),
|
||||
visible: false,
|
||||
collisionsWillMove: false,
|
||||
ignoreForCollisions: true
|
||||
});
|
||||
var rightFist = Entities.addEntity( {
|
||||
type: "Sphere",
|
||||
shapeType: 'sphere',
|
||||
position: MyAvatar.getRightPalmPosition(),
|
||||
dimensions: { x: GRAB_RADIUS, y: GRAB_RADIUS, z: GRAB_RADIUS },
|
||||
rotation: MyAvatar.getRightPalmRotation(),
|
||||
visible: false,
|
||||
collisionsWillMove: false,
|
||||
ignoreForCollisions: true
|
||||
});
|
||||
|
||||
function letGo(hand) {
|
||||
var actionIDToRemove = (hand == LEFT) ? leftHandActionID : rightHandActionID;
|
||||
var entityIDToEdit = (hand == LEFT) ? leftHandObjectID : rightHandObjectID;
|
||||
var handVelocity = (hand == LEFT) ? MyAvatar.getLeftPalmVelocity() : MyAvatar.getRightPalmVelocity();
|
||||
var handAngularVelocity = (hand == LEFT) ? MyAvatar.getLeftPalmAngularVelocity() :
|
||||
MyAvatar.getRightPalmAngularVelocity();
|
||||
if (actionIDToRemove != nullActionID && entityIDToEdit != null) {
|
||||
Entities.deleteAction(entityIDToEdit, actionIDToRemove);
|
||||
// TODO: upon successful letGo, restore collision groups
|
||||
if (hand == LEFT) {
|
||||
leftHandObjectID = null;
|
||||
leftHandActionID = nullActionID;
|
||||
} else {
|
||||
rightHandObjectID = null;
|
||||
rightHandActionID = nullActionID;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function setGrabbedObject(hand) {
|
||||
var handPosition = (hand == LEFT) ? MyAvatar.getLeftPalmPosition() : MyAvatar.getRightPalmPosition();
|
||||
var entities = Entities.findEntities(handPosition, GRAB_RADIUS);
|
||||
var objectID = null;
|
||||
var minDistance = GRAB_RADIUS;
|
||||
for (var i = 0; i < entities.length; i++) {
|
||||
// Don't grab the object in your other hands, your fists, or the table
|
||||
if ((hand == LEFT && entities[i] == rightHandObjectID) ||
|
||||
(hand == RIGHT && entities[i] == leftHandObjectID) ||
|
||||
entities[i] == leftFist || entities[i] == rightFist ||
|
||||
(tableCreated && entities[i] == tableEntities[0])) {
|
||||
continue;
|
||||
} else {
|
||||
var distance = Vec3.distance(Entities.getEntityProperties(entities[i]).position, handPosition);
|
||||
if (distance <= minDistance) {
|
||||
objectID = entities[i];
|
||||
minDistance = distance;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (objectID == null) {
|
||||
return false;
|
||||
}
|
||||
if (hand == LEFT) {
|
||||
leftHandObjectID = objectID;
|
||||
} else {
|
||||
rightHandObjectID = objectID;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
function grab(hand) {
|
||||
if (!setGrabbedObject(hand)) {
|
||||
// If you don't grab an object, make a fist
|
||||
Entities.editEntity((hand == LEFT) ? leftFist : rightFist, { ignoreForCollisions: false } );
|
||||
return;
|
||||
}
|
||||
var objectID = (hand == LEFT) ? leftHandObjectID : rightHandObjectID;
|
||||
var handRotation = (hand == LEFT) ? MyAvatar.getLeftPalmRotation() : MyAvatar.getRightPalmRotation();
|
||||
var handPosition = (hand == LEFT) ? MyAvatar.getLeftPalmPosition() : MyAvatar.getRightPalmPosition();
|
||||
|
||||
var objectRotation = Entities.getEntityProperties(objectID).rotation;
|
||||
var offsetRotation = Quat.multiply(Quat.inverse(handRotation), objectRotation);
|
||||
|
||||
var objectPosition = Entities.getEntityProperties(objectID).position;
|
||||
var offset = Vec3.subtract(objectPosition, handPosition);
|
||||
var offsetPosition = Vec3.multiplyQbyV(Quat.inverse(Quat.multiply(handRotation, offsetRotation)), offset);
|
||||
// print(JSON.stringify(offsetPosition));
|
||||
var actionID = Entities.addAction("hold", objectID, {
|
||||
relativePosition: { x: 0, y: 0, z: 0 },
|
||||
relativeRotation: offsetRotation,
|
||||
hand: (hand == LEFT) ? "left" : "right",
|
||||
timeScale: 0.05
|
||||
});
|
||||
if (actionID == nullActionID) {
|
||||
if (hand == LEFT) {
|
||||
leftHandObjectID = null;
|
||||
} else {
|
||||
rightHandObjectID = null;
|
||||
}
|
||||
} else {
|
||||
// TODO: upon successful grab, add to collision group so object doesn't collide with immovable entities
|
||||
if (hand == LEFT) {
|
||||
leftHandActionID = actionID;
|
||||
} else {
|
||||
rightHandActionID = actionID;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function resizeModels() {
|
||||
var newEntitiesToResize = [];
|
||||
for (var i = 0; i < entitiesToResize.length; i++) {
|
||||
var naturalDimensions = Entities.getEntityProperties(entitiesToResize[i]).naturalDimensions;
|
||||
if (naturalDimensions.x != 1.0 || naturalDimensions.y != 1.0 || naturalDimensions.z != 1.0) {
|
||||
// bigger range of sizes for models
|
||||
var dimensions = Vec3.multiply(randFloat(MIN_OBJECT_SIZE, 3.0*MAX_OBJECT_SIZE), Vec3.normalize(naturalDimensions));
|
||||
Entities.editEntity(entitiesToResize[i], {
|
||||
dimensions: dimensions,
|
||||
shapeType: "box"
|
||||
});
|
||||
} else {
|
||||
newEntitiesToResize.push(entitiesToResize[i]);
|
||||
}
|
||||
|
||||
}
|
||||
entitiesToResize = newEntitiesToResize;
|
||||
}
|
||||
|
||||
function update(deltaTime) {
|
||||
if (overlays) {
|
||||
Overlays.editOverlay(leftHandOverlay, { position: MyAvatar.getLeftPalmPosition() });
|
||||
Overlays.editOverlay(rightHandOverlay, { position: MyAvatar.getRightPalmPosition() });
|
||||
}
|
||||
|
||||
// if (tableCreated && RESIZE_TIMER < RESIZE_WAIT) {
|
||||
// RESIZE_TIMER += deltaTime;
|
||||
// } else if (tableCreated) {
|
||||
// resizeModels();
|
||||
// }
|
||||
|
||||
rightHandGrabValue = Controller.getActionValue(rightHandGrabAction);
|
||||
leftHandGrabValue = Controller.getActionValue(leftHandGrabAction);
|
||||
|
||||
Entities.editEntity(leftFist, { position: MyAvatar.getLeftPalmPosition() });
|
||||
Entities.editEntity(rightFist, { position: MyAvatar.getRightPalmPosition() });
|
||||
|
||||
if (rightHandGrabValue > TRIGGER_THRESHOLD &&
|
||||
prevRightHandGrabValue < TRIGGER_THRESHOLD) {
|
||||
if (overlays) {
|
||||
Overlays.editOverlay(rightHandOverlay, { color: grabColor });
|
||||
}
|
||||
grab(RIGHT);
|
||||
} else if (rightHandGrabValue < TRIGGER_THRESHOLD &&
|
||||
prevRightHandGrabValue > TRIGGER_THRESHOLD) {
|
||||
Entities.editEntity(rightFist, { ignoreForCollisions: true } );
|
||||
if (overlays) {
|
||||
Overlays.editOverlay(rightHandOverlay, { color: releaseColor });
|
||||
}
|
||||
letGo(RIGHT);
|
||||
}
|
||||
|
||||
if (leftHandGrabValue > TRIGGER_THRESHOLD &&
|
||||
prevLeftHandGrabValue < TRIGGER_THRESHOLD) {
|
||||
if (overlays) {
|
||||
Overlays.editOverlay(leftHandOverlay, { color: grabColor });
|
||||
}
|
||||
grab(LEFT);
|
||||
} else if (leftHandGrabValue < TRIGGER_THRESHOLD &&
|
||||
prevLeftHandGrabValue > TRIGGER_THRESHOLD) {
|
||||
Entities.editEntity(leftFist, { ignoreForCollisions: true } );
|
||||
if (overlays) {
|
||||
Overlays.editOverlay(leftHandOverlay, { color: releaseColor });
|
||||
}
|
||||
letGo(LEFT);
|
||||
}
|
||||
|
||||
prevRightHandGrabValue = rightHandGrabValue;
|
||||
prevLeftHandGrabValue = leftHandGrabValue;
|
||||
}
|
||||
|
||||
function cleanUp() {
|
||||
letGo(RIGHT);
|
||||
letGo(LEFT);
|
||||
print("CLEANUP!!!")
|
||||
if (overlays) {
|
||||
Overlays.deleteOverlay(leftHandOverlay);
|
||||
Overlays.deleteOverlay(rightHandOverlay);
|
||||
}
|
||||
Entities.deleteEntity(leftFist);
|
||||
Entities.deleteEntity(rightFist);
|
||||
removeTable();
|
||||
toolBar.cleanup();
|
||||
}
|
||||
|
@ -405,7 +177,6 @@ function createTable() {
|
|||
density: 0.5,
|
||||
collisionsWillMove: true,
|
||||
color: { red: randInt(0, 255), green: randInt(0, 255), blue: randInt(0, 255) },
|
||||
// collisionSoundURL: COLLISION_SOUNDS[randInt(0, COLLISION_SOUNDS.length)]
|
||||
});
|
||||
if (type == "Model") {
|
||||
var randModel = randInt(0, MODELS.length);
|
||||
|
@ -413,7 +184,6 @@ function createTable() {
|
|||
shapeType: "box",
|
||||
modelURL: MODELS[randModel].modelURL
|
||||
});
|
||||
entitiesToResize.push(tableEntities[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -426,5 +196,4 @@ function removeTable() {
|
|||
}
|
||||
|
||||
Script.scriptEnding.connect(cleanUp);
|
||||
Script.update.connect(update);
|
||||
Controller.mousePressEvent.connect(onClick);
|
63
examples/libraries/constants.js
Normal file
63
examples/libraries/constants.js
Normal file
|
@ -0,0 +1,63 @@
|
|||
//
|
||||
// Created by Bradley Austin Davis on 2015/08/27
|
||||
// Copyright 2015 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
|
||||
//
|
||||
|
||||
HIFI_PUBLIC_BUCKET = "http://s3.amazonaws.com/hifi-public/";
|
||||
STICK_URL = HIFI_PUBLIC_BUCKET + "models/props/geo_stick.fbx";
|
||||
|
||||
ZERO_VECTOR = { x: 0, y: 0, z: 0 };
|
||||
|
||||
COLORS = {
|
||||
WHITE: {
|
||||
red: 255,
|
||||
green: 255,
|
||||
blue: 255,
|
||||
},
|
||||
BLACK: {
|
||||
red: 0,
|
||||
green: 0,
|
||||
blue: 0,
|
||||
},
|
||||
GREY: {
|
||||
red: 128,
|
||||
green: 128,
|
||||
blue: 128,
|
||||
},
|
||||
RED: {
|
||||
red: 255,
|
||||
green: 0,
|
||||
blue: 0
|
||||
},
|
||||
BLUE: {
|
||||
red: 0,
|
||||
green: 0,
|
||||
blue: 255
|
||||
},
|
||||
GREEN: {
|
||||
red: 0,
|
||||
green: 255,
|
||||
blue: 0
|
||||
},
|
||||
CYAN: {
|
||||
red: 0,
|
||||
green: 255,
|
||||
blue: 255
|
||||
},
|
||||
YELLOW: {
|
||||
red: 255,
|
||||
green: 255,
|
||||
blue: 0
|
||||
},
|
||||
MAGENTA: {
|
||||
red: 255,
|
||||
green: 0,
|
||||
blue: 255
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -53,6 +53,12 @@ Highlighter.prototype.setSize = function(newSize) {
|
|||
});
|
||||
}
|
||||
|
||||
Highlighter.prototype.setRotation = function(newRotation) {
|
||||
Overlays.editOverlay(this.highlightCube, {
|
||||
rotation: newRotation
|
||||
});
|
||||
}
|
||||
|
||||
Highlighter.prototype.updateHighlight = function() {
|
||||
if (this.hightlighted) {
|
||||
var properties = Entities.getEntityProperties(this.hightlighted);
|
145
examples/libraries/htmlColors.js
Normal file
145
examples/libraries/htmlColors.js
Normal file
|
@ -0,0 +1,145 @@
|
|||
|
||||
HTML_COLORS = {
|
||||
AliceBlue: "#F0F8FF",
|
||||
AntiqueWhite: "#FAEBD7",
|
||||
Aqua: "#00FFFF",
|
||||
Aquamarine: "#7FFFD4",
|
||||
Azure: "#F0FFFF",
|
||||
Beige: "#F5F5DC",
|
||||
Bisque: "#FFE4C4",
|
||||
Black: "#000000",
|
||||
BlanchedAlmond: "#FFEBCD",
|
||||
Blue: "#0000FF",
|
||||
BlueViolet: "#8A2BE2",
|
||||
Brown: "#A52A2A",
|
||||
BurlyWood: "#DEB887",
|
||||
CadetBlue: "#5F9EA0",
|
||||
Chartreuse: "#7FFF00",
|
||||
Chocolate: "#D2691E",
|
||||
Coral: "#FF7F50",
|
||||
CornflowerBlue: "#6495ED",
|
||||
Cornsilk: "#FFF8DC",
|
||||
Crimson: "#DC143C",
|
||||
Cyan: "#00FFFF",
|
||||
DarkBlue: "#00008B",
|
||||
DarkCyan: "#008B8B",
|
||||
DarkGoldenRod: "#B8860B",
|
||||
DarkGray: "#A9A9A9",
|
||||
DarkGreen: "#006400",
|
||||
DarkKhaki: "#BDB76B",
|
||||
DarkMagenta: "#8B008B",
|
||||
DarkOliveGreen: "#556B2F",
|
||||
DarkOrange: "#FF8C00",
|
||||
DarkOrchid: "#9932CC",
|
||||
DarkRed: "#8B0000",
|
||||
DarkSalmon: "#E9967A",
|
||||
DarkSeaGreen: "#8FBC8F",
|
||||
DarkSlateBlue: "#483D8B",
|
||||
DarkSlateGray: "#2F4F4F",
|
||||
DarkTurquoise: "#00CED1",
|
||||
DarkViolet: "#9400D3",
|
||||
DeepPink: "#FF1493",
|
||||
DeepSkyBlue: "#00BFFF",
|
||||
DimGray: "#696969",
|
||||
DodgerBlue: "#1E90FF",
|
||||
FireBrick: "#B22222",
|
||||
FloralWhite: "#FFFAF0",
|
||||
ForestGreen: "#228B22",
|
||||
Fuchsia: "#FF00FF",
|
||||
Gainsboro: "#DCDCDC",
|
||||
GhostWhite: "#F8F8FF",
|
||||
Gold: "#FFD700",
|
||||
GoldenRod: "#DAA520",
|
||||
Gray: "#808080",
|
||||
Green: "#008000",
|
||||
GreenYellow: "#ADFF2F",
|
||||
HoneyDew: "#F0FFF0",
|
||||
HotPink: "#FF69B4",
|
||||
IndianRed: "#CD5C5C",
|
||||
Indigo: "#4B0082",
|
||||
Ivory: "#FFFFF0",
|
||||
Khaki: "#F0E68C",
|
||||
Lavender: "#E6E6FA",
|
||||
LavenderBlush: "#FFF0F5",
|
||||
LawnGreen: "#7CFC00",
|
||||
LemonChiffon: "#FFFACD",
|
||||
LightBlue: "#ADD8E6",
|
||||
LightCoral: "#F08080",
|
||||
LightCyan: "#E0FFFF",
|
||||
LightGoldenRodYellow: "#FAFAD2",
|
||||
LightGray: "#D3D3D3",
|
||||
LightGreen: "#90EE90",
|
||||
LightPink: "#FFB6C1",
|
||||
LightSalmon: "#FFA07A",
|
||||
LightSeaGreen: "#20B2AA",
|
||||
LightSkyBlue: "#87CEFA",
|
||||
LightSlateGray: "#778899",
|
||||
LightSteelBlue: "#B0C4DE",
|
||||
LightYellow: "#FFFFE0",
|
||||
Lime: "#00FF00",
|
||||
LimeGreen: "#32CD32",
|
||||
Linen: "#FAF0E6",
|
||||
Magenta: "#FF00FF",
|
||||
Maroon: "#800000",
|
||||
MediumAquaMarine: "#66CDAA",
|
||||
MediumBlue: "#0000CD",
|
||||
MediumOrchid: "#BA55D3",
|
||||
MediumPurple: "#9370DB",
|
||||
MediumSeaGreen: "#3CB371",
|
||||
MediumSlateBlue: "#7B68EE",
|
||||
MediumSpringGreen: "#00FA9A",
|
||||
MediumTurquoise: "#48D1CC",
|
||||
MediumVioletRed: "#C71585",
|
||||
MidnightBlue: "#191970",
|
||||
MintCream: "#F5FFFA",
|
||||
MistyRose: "#FFE4E1",
|
||||
Moccasin: "#FFE4B5",
|
||||
NavajoWhite: "#FFDEAD",
|
||||
Navy: "#000080",
|
||||
OldLace: "#FDF5E6",
|
||||
Olive: "#808000",
|
||||
OliveDrab: "#6B8E23",
|
||||
Orange: "#FFA500",
|
||||
OrangeRed: "#FF4500",
|
||||
Orchid: "#DA70D6",
|
||||
PaleGoldenRod: "#EEE8AA",
|
||||
PaleGreen: "#98FB98",
|
||||
PaleTurquoise: "#AFEEEE",
|
||||
PaleVioletRed: "#DB7093",
|
||||
PapayaWhip: "#FFEFD5",
|
||||
PeachPuff: "#FFDAB9",
|
||||
Peru: "#CD853F",
|
||||
Pink: "#FFC0CB",
|
||||
Plum: "#DDA0DD",
|
||||
PowderBlue: "#B0E0E6",
|
||||
Purple: "#800080",
|
||||
RebeccaPurple: "#663399",
|
||||
Red: "#FF0000",
|
||||
RosyBrown: "#BC8F8F",
|
||||
RoyalBlue: "#4169E1",
|
||||
SaddleBrown: "#8B4513",
|
||||
Salmon: "#FA8072",
|
||||
SandyBrown: "#F4A460",
|
||||
SeaGreen: "#2E8B57",
|
||||
SeaShell: "#FFF5EE",
|
||||
Sienna: "#A0522D",
|
||||
Silver: "#C0C0C0",
|
||||
SkyBlue: "#87CEEB",
|
||||
SlateBlue: "#6A5ACD",
|
||||
SlateGray: "#708090",
|
||||
Snow: "#FFFAFA",
|
||||
SpringGreen: "#00FF7F",
|
||||
SteelBlue: "#4682B4",
|
||||
Tan: "#D2B48C",
|
||||
Teal: "#008080",
|
||||
Thistle: "#D8BFD8",
|
||||
Tomato: "#FF6347",
|
||||
Turquoise: "#40E0D0",
|
||||
Violet: "#EE82EE",
|
||||
Wheat: "#F5DEB3",
|
||||
White: "#FFFFFF",
|
||||
WhiteSmoke: "#F5F5F5",
|
||||
Yellow: "#FFFF00",
|
||||
YellowGreen: "#9ACD32",
|
||||
}
|
||||
|
304
examples/libraries/omniTool.js
Normal file
304
examples/libraries/omniTool.js
Normal file
|
@ -0,0 +1,304 @@
|
|||
//
|
||||
// Created by Bradley Austin Davis on 2015/09/01
|
||||
// Copyright 2015 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
|
||||
//
|
||||
|
||||
Script.include("constants.js");
|
||||
Script.include("utils.js");
|
||||
Script.include("highlighter.js");
|
||||
Script.include("omniTool/models/modelBase.js");
|
||||
Script.include("omniTool/models/wand.js");
|
||||
|
||||
OmniToolModules = {};
|
||||
OmniToolModuleType = null;
|
||||
|
||||
OmniTool = function(side) {
|
||||
this.OMNI_KEY = "OmniTool";
|
||||
this.MAX_FRAMERATE = 30;
|
||||
this.UPDATE_INTERVAL = 1.0 / this.MAX_FRAMERATE
|
||||
this.SIDE = side;
|
||||
this.PALM = 2 * side;
|
||||
this.ACTION = findAction(side ? "ACTION2" : "ACTION1");
|
||||
this.ALT_ACTION = findAction(side ? "ACTION1" : "ACTION2");
|
||||
|
||||
this.highlighter = new Highlighter();
|
||||
this.ignoreEntities = {};
|
||||
this.nearestOmniEntity = {
|
||||
id: null,
|
||||
inside: false,
|
||||
position: null,
|
||||
distance: Infinity,
|
||||
radius: 0,
|
||||
omniProperties: {},
|
||||
boundingBox: null,
|
||||
};
|
||||
|
||||
this.activeOmniEntityId = null;
|
||||
this.lastUpdateInterval = 0;
|
||||
this.tipLength = 0.4;
|
||||
this.active = false;
|
||||
this.module = null;
|
||||
this.moduleEntityId = null;
|
||||
this.lastScanPosition = ZERO_VECTOR;
|
||||
this.model = new Wand();
|
||||
this.model.setLength(this.tipLength);
|
||||
|
||||
// Connect to desired events
|
||||
var _this = this;
|
||||
Controller.actionEvent.connect(function(action, state) {
|
||||
_this.onActionEvent(action, state);
|
||||
});
|
||||
|
||||
Script.update.connect(function(deltaTime) {
|
||||
_this.lastUpdateInterval += deltaTime;
|
||||
if (_this.lastUpdateInterval >= _this.UPDATE_INTERVAL) {
|
||||
_this.onUpdate(_this.lastUpdateInterval);
|
||||
_this.lastUpdateInterval = 0;
|
||||
}
|
||||
});
|
||||
|
||||
Script.scriptEnding.connect(function() {
|
||||
_this.onCleanup();
|
||||
});
|
||||
}
|
||||
|
||||
OmniTool.prototype.onActionEvent = function(action, state) {
|
||||
// FIXME figure out the issues when only one spatial controller is active
|
||||
// logDebug("Action: " + action + " " + state);
|
||||
|
||||
if (this.module && this.module.onActionEvent) {
|
||||
this.module.onActionEvent(action, state);
|
||||
}
|
||||
|
||||
if (action == this.ACTION) {
|
||||
if (state) {
|
||||
this.onClick();
|
||||
} else {
|
||||
this.onRelease();
|
||||
}
|
||||
}
|
||||
|
||||
// FIXME Does not work
|
||||
//// with only one controller active (listed as 2 here because 'tip' + 'palm')
|
||||
//// then treat the alt action button as the action button
|
||||
}
|
||||
|
||||
OmniTool.prototype.getOmniToolData = function(entityId) {
|
||||
return getEntityCustomData(this.OMNI_KEY, entityId, null);
|
||||
}
|
||||
|
||||
OmniTool.prototype.setOmniToolData = function(entityId, data) {
|
||||
setEntityCustomData(this.OMNI_KEY, entityId, data);
|
||||
}
|
||||
|
||||
OmniTool.prototype.updateOmniToolData = function(entityId, data) {
|
||||
var currentData = this.getOmniToolData(entityId) || {};
|
||||
for (var key in data) {
|
||||
currentData[key] = data[key];
|
||||
}
|
||||
setEntityCustomData(this.OMNI_KEY, entityId, currentData);
|
||||
}
|
||||
|
||||
OmniTool.prototype.setActive = function(active) {
|
||||
if (active === this.active) {
|
||||
return;
|
||||
}
|
||||
logDebug("omnitool changing active state: " + active);
|
||||
this.active = active;
|
||||
this.model.setVisible(this.active);
|
||||
|
||||
if (this.module && this.module.onActiveChanged) {
|
||||
this.module.onActiveChanged(this.side);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
OmniTool.prototype.onUpdate = function(deltaTime) {
|
||||
// FIXME this returns data if either the left or right controller is not on the base
|
||||
this.position = Controller.getSpatialControlPosition(this.PALM);
|
||||
// When on the base, hydras report a position of 0
|
||||
this.setActive(Vec3.length(this.position) > 0.001);
|
||||
|
||||
var rawRotation = Controller.getSpatialControlRawRotation(this.PALM);
|
||||
this.rotation = Quat.multiply(MyAvatar.orientation, rawRotation);
|
||||
|
||||
this.model.setTransform({
|
||||
rotation: this.rotation,
|
||||
position: this.position,
|
||||
});
|
||||
|
||||
this.scan();
|
||||
|
||||
if (this.module && this.module.onUpdate) {
|
||||
this.module.onUpdate(deltaTime);
|
||||
}
|
||||
}
|
||||
|
||||
OmniTool.prototype.onClick = function() {
|
||||
// First check to see if the user is switching to a new omni module
|
||||
if (this.nearestOmniEntity.inside && this.nearestOmniEntity.omniProperties.script) {
|
||||
this.activateNewOmniModule();
|
||||
return;
|
||||
}
|
||||
|
||||
// Next check if there is an active module and if so propagate the click
|
||||
// FIXME how to I switch to a new module?
|
||||
if (this.module && this.module.onClick) {
|
||||
this.module.onClick();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
OmniTool.prototype.onRelease = function() {
|
||||
// FIXME how to I switch to a new module?
|
||||
if (this.module && this.module.onRelease) {
|
||||
this.module.onRelease();
|
||||
return;
|
||||
}
|
||||
logDebug("Base omnitool does nothing on release");
|
||||
}
|
||||
|
||||
// FIXME resturn a structure of all nearby entities to distances
|
||||
OmniTool.prototype.findNearestOmniEntity = function(maxDistance, selector) {
|
||||
if (!maxDistance) {
|
||||
maxDistance = 2.0;
|
||||
}
|
||||
var resultDistance = Infinity;
|
||||
var resultId = null;
|
||||
var resultProperties = null;
|
||||
var resultOmniData = null;
|
||||
var ids = Entities.findEntities(this.model.tipPosition, maxDistance);
|
||||
for (var i in ids) {
|
||||
var entityId = ids[i];
|
||||
if (this.ignoreEntities[entityId]) {
|
||||
continue;
|
||||
}
|
||||
var omniData = this.getOmniToolData(entityId);
|
||||
if (!omniData) {
|
||||
// FIXME find a place to flush this information
|
||||
this.ignoreEntities[entityId] = true;
|
||||
continue;
|
||||
}
|
||||
|
||||
// Let searchers query specifically
|
||||
if (selector && !selector(entityId, omniData)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
var properties = Entities.getEntityProperties(entityId);
|
||||
var distance = Vec3.distance(this.model.tipPosition, properties.position);
|
||||
if (distance < resultDistance) {
|
||||
resultDistance = distance;
|
||||
resultId = entityId;
|
||||
}
|
||||
}
|
||||
|
||||
return resultId;
|
||||
}
|
||||
|
||||
OmniTool.prototype.getPosition = function() {
|
||||
return this.model.tipPosition;
|
||||
}
|
||||
|
||||
OmniTool.prototype.onEnterNearestOmniEntity = function() {
|
||||
this.nearestOmniEntity.inside = true;
|
||||
this.highlighter.highlight(this.nearestOmniEntity.id);
|
||||
logDebug("On enter omniEntity " + this.nearestOmniEntity.id);
|
||||
}
|
||||
|
||||
OmniTool.prototype.onLeaveNearestOmniEntity = function() {
|
||||
this.nearestOmniEntity.inside = false;
|
||||
this.highlighter.highlight(null);
|
||||
logDebug("On leave omniEntity " + this.nearestOmniEntity.id);
|
||||
}
|
||||
|
||||
OmniTool.prototype.setNearestOmniEntity = function(entityId) {
|
||||
if (entityId && entityId !== this.nearestOmniEntity.id) {
|
||||
if (this.nearestOmniEntity.id && this.nearestOmniEntity.inside) {
|
||||
this.onLeaveNearestOmniEntity();
|
||||
}
|
||||
this.nearestOmniEntity.id = entityId;
|
||||
this.nearestOmniEntity.omniProperties = this.getOmniToolData(entityId);
|
||||
var properties = Entities.getEntityProperties(entityId);
|
||||
this.nearestOmniEntity.position = properties.position;
|
||||
// FIXME use a real bounding box, not a sphere
|
||||
var bbox = properties.boundingBox;
|
||||
this.nearestOmniEntity.radius = Vec3.length(Vec3.subtract(bbox.center, bbox.brn));
|
||||
this.highlighter.setRotation(properties.rotation);
|
||||
this.highlighter.setSize(Vec3.multiply(1.05, bbox.dimensions));
|
||||
}
|
||||
}
|
||||
|
||||
OmniTool.prototype.scan = function() {
|
||||
var scanDistance = Vec3.distance(this.model.tipPosition, this.lastScanPosition);
|
||||
|
||||
if (scanDistance < 0.005) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.lastScanPosition = this.model.tipPosition;
|
||||
|
||||
this.setNearestOmniEntity(this.findNearestOmniEntity());
|
||||
if (this.nearestOmniEntity.id) {
|
||||
var distance = Vec3.distance(this.model.tipPosition, this.nearestOmniEntity.position);
|
||||
// track distance on a half centimeter basis
|
||||
if (Math.abs(this.nearestOmniEntity.distance - distance) > 0.005) {
|
||||
this.nearestOmniEntity.distance = distance;
|
||||
if (!this.nearestOmniEntity.inside && distance < this.nearestOmniEntity.radius) {
|
||||
this.onEnterNearestOmniEntity();
|
||||
}
|
||||
|
||||
if (this.nearestOmniEntity.inside && distance > this.nearestOmniEntity.radius + 0.01) {
|
||||
this.onLeaveNearestOmniEntity();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
OmniTool.prototype.unloadModule = function() {
|
||||
if (this.module && this.module.onUnload) {
|
||||
this.module.onUnload();
|
||||
}
|
||||
this.module = null;
|
||||
this.moduleEntityId = null;
|
||||
}
|
||||
|
||||
OmniTool.prototype.activateNewOmniModule = function() {
|
||||
// Support the ability for scripts to just run without replacing the current module
|
||||
var script = this.nearestOmniEntity.omniProperties.script;
|
||||
if (script.indexOf("/") < 0) {
|
||||
script = "omniTool/modules/" + script;
|
||||
}
|
||||
|
||||
// Reset the tool type
|
||||
OmniToolModuleType = null;
|
||||
logDebug("Including script path: " + script);
|
||||
try {
|
||||
Script.include(script);
|
||||
} catch(err) {
|
||||
logWarn("Failed to include script: " + script + "\n" + err);
|
||||
return;
|
||||
}
|
||||
|
||||
// If we're building a new module, unload the old one
|
||||
if (OmniToolModuleType) {
|
||||
logDebug("New OmniToolModule: " + OmniToolModuleType);
|
||||
this.unloadModule();
|
||||
|
||||
try {
|
||||
this.module = new OmniToolModules[OmniToolModuleType](this, this.nearestOmniEntity.id);
|
||||
this.moduleEntityId = this.nearestOmniEntity.id;
|
||||
if (this.module.onLoad) {
|
||||
this.module.onLoad();
|
||||
}
|
||||
} catch(err) {
|
||||
logWarn("Failed to instantiate new module: " + err);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// FIXME find a good way to sync the two omni tools
|
||||
OMNI_TOOLS = [ new OmniTool(0), new OmniTool(1) ];
|
19
examples/libraries/omniTool/models/modelBase.js
Normal file
19
examples/libraries/omniTool/models/modelBase.js
Normal file
|
@ -0,0 +1,19 @@
|
|||
|
||||
ModelBase = function() {
|
||||
this.length = 0.2;
|
||||
}
|
||||
|
||||
ModelBase.prototype.setVisible = function(visible) {
|
||||
this.visible = visible;
|
||||
}
|
||||
|
||||
ModelBase.prototype.setLength = function(length) {
|
||||
this.length = length;
|
||||
}
|
||||
|
||||
ModelBase.prototype.setTransform = function(transform) {
|
||||
this.rotation = transform.rotation;
|
||||
this.position = transform.position;
|
||||
this.tipVector = Vec3.multiplyQbyV(this.rotation, { x: 0, y: this.length, z: 0 });
|
||||
this.tipPosition = Vec3.sum(this.position, this.tipVector);
|
||||
}
|
120
examples/libraries/omniTool/models/wand.js
Normal file
120
examples/libraries/omniTool/models/wand.js
Normal file
|
@ -0,0 +1,120 @@
|
|||
|
||||
Wand = function() {
|
||||
// Max updates fps
|
||||
this.MAX_FRAMERATE = 30
|
||||
this.UPDATE_INTERVAL = 1.0 / this.MAX_FRAMERATE
|
||||
this.DEFAULT_TIP_COLORS = [ {
|
||||
red: 128,
|
||||
green: 128,
|
||||
blue: 128,
|
||||
}, {
|
||||
red: 64,
|
||||
green: 64,
|
||||
blue: 64,
|
||||
}];
|
||||
this.POINTER_ROTATION = Quat.fromPitchYawRollDegrees(45, 0, 45);
|
||||
|
||||
// FIXME does this need to be a member of this?
|
||||
this.lastUpdateInterval = 0;
|
||||
|
||||
this.pointers = [
|
||||
Overlays.addOverlay("cube", {
|
||||
position: ZERO_VECTOR,
|
||||
color: this.DEFAULT_TIP_COLORS[0],
|
||||
alpha: 1.0,
|
||||
solid: true,
|
||||
visible: false,
|
||||
}),
|
||||
Overlays.addOverlay("cube", {
|
||||
position: ZERO_VECTOR,
|
||||
color: this.DEFAULT_TIP_COLORS[1],
|
||||
alpha: 1.0,
|
||||
solid: true,
|
||||
visible: false,
|
||||
})
|
||||
];
|
||||
|
||||
this.wand = Overlays.addOverlay("cube", {
|
||||
position: ZERO_VECTOR,
|
||||
color: COLORS.WHITE,
|
||||
dimensions: { x: 0.01, y: 0.01, z: 0.2 },
|
||||
alpha: 1.0,
|
||||
solid: true,
|
||||
visible: false
|
||||
});
|
||||
|
||||
var _this = this;
|
||||
Script.scriptEnding.connect(function() {
|
||||
Overlays.deleteOverlay(_this.pointers[0]);
|
||||
Overlays.deleteOverlay(_this.pointers[1]);
|
||||
Overlays.deleteOverlay(_this.wand);
|
||||
});
|
||||
|
||||
Script.update.connect(function(deltaTime) {
|
||||
_this.lastUpdateInterval += deltaTime;
|
||||
if (_this.lastUpdateInterval >= _this.UPDATE_INTERVAL) {
|
||||
_this.onUpdate(_this.lastUpdateInterval);
|
||||
_this.lastUpdateInterval = 0;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
Wand.prototype = Object.create( ModelBase.prototype );
|
||||
|
||||
Wand.prototype.setVisible = function(visible) {
|
||||
ModelBase.prototype.setVisible.call(this, visible);
|
||||
Overlays.editOverlay(this.pointers[0], {
|
||||
visible: this.visible
|
||||
});
|
||||
Overlays.editOverlay(this.pointers[1], {
|
||||
visible: this.visible
|
||||
});
|
||||
Overlays.editOverlay(this.wand, {
|
||||
visible: this.visible
|
||||
});
|
||||
}
|
||||
|
||||
Wand.prototype.setTransform = function(transform) {
|
||||
ModelBase.prototype.setTransform.call(this, transform);
|
||||
|
||||
var wandPosition = Vec3.sum(this.position, Vec3.multiply(0.5, this.tipVector));
|
||||
Overlays.editOverlay(this.pointers[0], {
|
||||
position: this.tipPosition,
|
||||
rotation: this.rotation,
|
||||
visible: true,
|
||||
});
|
||||
Overlays.editOverlay(this.pointers[1], {
|
||||
position: this.tipPosition,
|
||||
rotation: Quat.multiply(this.POINTER_ROTATION, this.rotation),
|
||||
visible: true,
|
||||
});
|
||||
Overlays.editOverlay(this.wand, {
|
||||
dimensions: { x: 0.01, y: this.length * 0.9, z: 0.01 },
|
||||
position: wandPosition,
|
||||
rotation: this.rotation,
|
||||
visible: true,
|
||||
});
|
||||
}
|
||||
|
||||
Wand.prototype.setTipColors = function(color1, color2) {
|
||||
Overlays.editOverlay(this.pointers[0], {
|
||||
color: color1 || this.DEFAULT_TIP_COLORS[0],
|
||||
});
|
||||
Overlays.editOverlay(this.pointers[1], {
|
||||
color: color2 || this.DEFAULT_TIP_COLORS[1],
|
||||
});
|
||||
}
|
||||
|
||||
Wand.prototype.onUpdate = function(deltaTime) {
|
||||
if (this.visible) {
|
||||
var time = new Date().getTime() / 250;
|
||||
var scale1 = Math.abs(Math.sin(time));
|
||||
var scale2 = Math.abs(Math.cos(time));
|
||||
Overlays.editOverlay(this.pointers[0], {
|
||||
scale: scale1 * 0.01,
|
||||
});
|
||||
Overlays.editOverlay(this.pointers[1], {
|
||||
scale: scale2 * 0.01,
|
||||
});
|
||||
}
|
||||
}
|
9
examples/libraries/omniTool/modules/test.js
Normal file
9
examples/libraries/omniTool/modules/test.js
Normal file
|
@ -0,0 +1,9 @@
|
|||
|
||||
OmniToolModules.Test = function() {
|
||||
}
|
||||
|
||||
OmniToolModules.Test.prototype.onClick = function() {
|
||||
logDebug("Test module onClick");
|
||||
}
|
||||
|
||||
OmniToolModuleType = "Test"
|
|
@ -53,11 +53,17 @@ getEntityUserData = function(id) {
|
|||
var results = null;
|
||||
var properties = Entities.getEntityProperties(id);
|
||||
if (properties.userData) {
|
||||
results = JSON.parse(properties.userData);
|
||||
try {
|
||||
results = JSON.parse(properties.userData);
|
||||
} catch(err) {
|
||||
logDebug(err);
|
||||
logDebug(properties.userData);
|
||||
}
|
||||
}
|
||||
return results ? results : {};
|
||||
}
|
||||
|
||||
|
||||
// Non-destructively modify the user data of an entity.
|
||||
setEntityCustomData = function(customKey, id, data) {
|
||||
var userData = getEntityUserData(id);
|
||||
|
@ -70,14 +76,6 @@ getEntityCustomData = function(customKey, id, defaultValue) {
|
|||
return userData[customKey] ? userData[customKey] : defaultValue;
|
||||
}
|
||||
|
||||
getMagBallsData = function(id) {
|
||||
return getEntityCustomData(CUSTOM_DATA_NAME, id, {});
|
||||
}
|
||||
|
||||
setMagBallsData = function(id, value) {
|
||||
setEntityCustomData(CUSTOM_DATA_NAME, id, value);
|
||||
}
|
||||
|
||||
mergeObjects = function(proto, custom) {
|
||||
var result = {};
|
||||
for (var attrname in proto) {
|
113
examples/toys/magBalls.js
Normal file
113
examples/toys/magBalls.js
Normal file
|
@ -0,0 +1,113 @@
|
|||
//
|
||||
// Created by Bradley Austin Davis on 2015/08/25
|
||||
// Copyright 2015 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
|
||||
//
|
||||
|
||||
// FIXME Script paths have to be relative to the caller, in this case libraries/OmniTool.js
|
||||
Script.include("../toys/magBalls/constants.js");
|
||||
Script.include("../toys/magBalls/graph.js");
|
||||
Script.include("../toys/magBalls/edgeSpring.js");
|
||||
Script.include("../toys/magBalls/magBalls.js");
|
||||
|
||||
OmniToolModuleType = "MagBallsController"
|
||||
|
||||
|
||||
OmniToolModules.MagBallsController = function(omniTool, entityId) {
|
||||
this.omniTool = omniTool;
|
||||
this.entityId = entityId;
|
||||
this.highlighter = new Highlighter();
|
||||
this.magBalls = new MagBalls();
|
||||
this.highlighter.setSize(BALL_SIZE);
|
||||
this.ghostEdges = {};
|
||||
}
|
||||
|
||||
var MAG_BALLS_DATA_NAME = "magBalls";
|
||||
|
||||
getMagBallsData = function(id) {
|
||||
return getEntityCustomData(MAG_BALLS_DATA_NAME, id, {});
|
||||
}
|
||||
|
||||
setMagBallsData = function(id, value) {
|
||||
setEntityCustomData(MAG_BALLS_DATA_NAME, id, value);
|
||||
}
|
||||
|
||||
//var magBalls = new MagBalls();
|
||||
// DEBUGGING ONLY - Clear any previous balls
|
||||
// magBalls.clear();
|
||||
|
||||
OmniToolModules.MagBallsController.prototype.onClick = function() {
|
||||
logDebug("MagBallsController onClick: " + vec3toStr(this.tipPosition));
|
||||
|
||||
this.selected = this.highlighter.hightlighted;
|
||||
logDebug("This selected: " + this.selected);
|
||||
if (!this.selected) {
|
||||
this.selected = this.magBalls.createBall(this.tipPosition);
|
||||
}
|
||||
this.magBalls.selectBall(this.selected);
|
||||
this.highlighter.highlight(null);
|
||||
logDebug("Selected " + this.selected);
|
||||
}
|
||||
|
||||
OmniToolModules.MagBallsController.prototype.onRelease = function() {
|
||||
logDebug("MagBallsController onRelease: " + vec3toStr(this.tipPosition));
|
||||
this.clearGhostEdges();
|
||||
if (this.selected) {
|
||||
this.magBalls.releaseBall(this.selected);
|
||||
this.selected = null;
|
||||
}
|
||||
}
|
||||
|
||||
OmniToolModules.MagBallsController.prototype.onUpdate = function(deltaTime) {
|
||||
this.tipPosition = this.omniTool.getPosition();
|
||||
if (!this.selected) {
|
||||
// Find the highlight target and set it.
|
||||
var target = this.magBalls.findNearestNode(this.tipPosition, BALL_SELECTION_RADIUS);
|
||||
this.highlighter.highlight(target);
|
||||
if (!target) {
|
||||
this.magBalls.onUpdate(deltaTime);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
this.highlighter.highlight(null);
|
||||
Entities.editEntity(this.selected, { position: this.tipPosition });
|
||||
var targetBalls = this.magBalls.findPotentialEdges(this.selected);
|
||||
for (var ballId in targetBalls) {
|
||||
if (!this.ghostEdges[ballId]) {
|
||||
// create the ovleray
|
||||
this.ghostEdges[ballId] = Overlays.addOverlay("line3d", {
|
||||
start: this.magBalls.getNodePosition(ballId),
|
||||
end: this.tipPosition,
|
||||
color: COLORS.RED,
|
||||
alpha: 1,
|
||||
lineWidth: 5,
|
||||
visible: true,
|
||||
});
|
||||
} else {
|
||||
Overlays.editOverlay(this.ghostEdges[ballId], {
|
||||
end: this.tipPosition,
|
||||
});
|
||||
}
|
||||
}
|
||||
for (var ballId in this.ghostEdges) {
|
||||
if (!targetBalls[ballId]) {
|
||||
Overlays.deleteOverlay(this.ghostEdges[ballId]);
|
||||
delete this.ghostEdges[ballId];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
OmniToolModules.MagBallsController.prototype.clearGhostEdges = function() {
|
||||
for(var ballId in this.ghostEdges) {
|
||||
Overlays.deleteOverlay(this.ghostEdges[ballId]);
|
||||
delete this.ghostEdges[ballId];
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
BallController.prototype.onUnload = function() {
|
||||
this.clearGhostEdges();
|
||||
}
|
|
@ -1,103 +0,0 @@
|
|||
Script.include("handController.js");
|
||||
Script.include("highlighter.js");
|
||||
|
||||
BallController = function(side, magBalls) {
|
||||
HandController.call(this, side);
|
||||
this.magBalls = magBalls;
|
||||
this.highlighter = new Highlighter();
|
||||
this.highlighter.setSize(BALL_SIZE);
|
||||
this.ghostEdges = {};
|
||||
}
|
||||
|
||||
BallController.prototype = Object.create( HandController.prototype );
|
||||
|
||||
BallController.prototype.onUpdate = function(deltaTime) {
|
||||
HandController.prototype.onUpdate.call(this, deltaTime);
|
||||
|
||||
if (!this.selected) {
|
||||
// Find the highlight target and set it.
|
||||
var target = this.magBalls.findNearestNode(this.tipPosition, BALL_SELECTION_RADIUS);
|
||||
this.highlighter.highlight(target);
|
||||
return;
|
||||
}
|
||||
this.highlighter.highlight(null);
|
||||
Entities.editEntity(this.selected, { position: this.tipPosition });
|
||||
var targetBalls = this.magBalls.findPotentialEdges(this.selected);
|
||||
for (var ballId in targetBalls) {
|
||||
if (!this.ghostEdges[ballId]) {
|
||||
// create the ovleray
|
||||
this.ghostEdges[ballId] = Overlays.addOverlay("line3d", {
|
||||
start: this.magBalls.getNodePosition(ballId),
|
||||
end: this.tipPosition,
|
||||
color: COLORS.RED,
|
||||
alpha: 1,
|
||||
lineWidth: 5,
|
||||
visible: true,
|
||||
});
|
||||
} else {
|
||||
Overlays.editOverlay(this.ghostEdges[ballId], {
|
||||
end: this.tipPosition,
|
||||
});
|
||||
}
|
||||
}
|
||||
for (var ballId in this.ghostEdges) {
|
||||
if (!targetBalls[ballId]) {
|
||||
Overlays.deleteOverlay(this.ghostEdges[ballId]);
|
||||
delete this.ghostEdges[ballId];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
BallController.prototype.onClick = function() {
|
||||
this.selected = this.magBalls.grabBall(this.tipPosition, BALL_SELECTION_RADIUS);
|
||||
this.highlighter.highlight(null);
|
||||
}
|
||||
|
||||
BallController.prototype.onRelease = function() {
|
||||
this.clearGhostEdges();
|
||||
this.magBalls.releaseBall(this.selected);
|
||||
this.selected = null;
|
||||
}
|
||||
|
||||
BallController.prototype.clearGhostEdges = function() {
|
||||
for(var ballId in this.ghostEdges) {
|
||||
Overlays.deleteOverlay(this.ghostEdges[ballId]);
|
||||
delete this.ghostEdges[ballId];
|
||||
}
|
||||
}
|
||||
|
||||
BallController.prototype.onCleanup = function() {
|
||||
HandController.prototype.onCleanup.call(this);
|
||||
this.clearGhostEdges();
|
||||
}
|
||||
|
||||
BallController.prototype.onAltClick = function() {
|
||||
return;
|
||||
var target = this.magBalls.findNearestNode(this.tipPosition, BALL_SELECTION_RADIUS);
|
||||
if (!target) {
|
||||
logDebug(target);
|
||||
return;
|
||||
}
|
||||
|
||||
// FIXME move to delete shape
|
||||
var toDelete = {};
|
||||
var deleteQueue = [ target ];
|
||||
while (deleteQueue.length) {
|
||||
var curNode = deleteQueue.shift();
|
||||
if (toDelete[curNode]) {
|
||||
continue;
|
||||
}
|
||||
toDelete[curNode] = true;
|
||||
for (var nodeId in this.magBalls.getConnectedNodes(curNode)) {
|
||||
deleteQueue.push(nodeId);
|
||||
}
|
||||
}
|
||||
for (var nodeId in toDelete) {
|
||||
this.magBalls.destroyNode(nodeId);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
BallController.prototype.onAltRelease = function() {
|
||||
}
|
|
@ -1,13 +1,3 @@
|
|||
//
|
||||
// Created by Bradley Austin Davis on 2015/08/27
|
||||
// Copyright 2015 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
|
||||
//
|
||||
|
||||
HIFI_PUBLIC_BUCKET = "http://s3.amazonaws.com/hifi-public/";
|
||||
STICK_URL = HIFI_PUBLIC_BUCKET + "models/props/geo_stick.fbx";
|
||||
|
||||
// FIXME make this editable through some script UI, so the user can customize the size of the structure built
|
||||
SCALE = 0.5;
|
||||
|
@ -16,60 +6,9 @@ STICK_LENGTH = 0.24 * SCALE;
|
|||
|
||||
DEBUG_MAGSTICKS = true;
|
||||
|
||||
CUSTOM_DATA_NAME = "magBalls";
|
||||
BALL_NAME = "MagBall";
|
||||
EDGE_NAME = "MagStick";
|
||||
|
||||
ZERO_VECTOR = { x: 0, y: 0, z: 0 };
|
||||
|
||||
COLORS = {
|
||||
WHITE: {
|
||||
red: 255,
|
||||
green: 255,
|
||||
blue: 255,
|
||||
},
|
||||
BLACK: {
|
||||
red: 0,
|
||||
green: 0,
|
||||
blue: 0,
|
||||
},
|
||||
GREY: {
|
||||
red: 128,
|
||||
green: 128,
|
||||
blue: 128,
|
||||
},
|
||||
RED: {
|
||||
red: 255,
|
||||
green: 0,
|
||||
blue: 0
|
||||
},
|
||||
BLUE: {
|
||||
red: 0,
|
||||
green: 0,
|
||||
blue: 255
|
||||
},
|
||||
GREEN: {
|
||||
red: 0,
|
||||
green: 255,
|
||||
blue: 0
|
||||
},
|
||||
CYAN: {
|
||||
red: 0,
|
||||
green: 255,
|
||||
blue: 255
|
||||
},
|
||||
YELLOW: {
|
||||
red: 255,
|
||||
green: 255,
|
||||
blue: 0
|
||||
},
|
||||
MAGENTA: {
|
||||
red: 255,
|
||||
green: 0,
|
||||
blue: 255
|
||||
}
|
||||
}
|
||||
|
||||
BALL_RADIUS = BALL_SIZE / 2.0;
|
||||
|
||||
BALL_SELECTION_RADIUS = BALL_RADIUS * 1.5;
|
||||
|
@ -136,5 +75,3 @@ EDGE_PROTOTYPE = LINE_PROTOTYPE;
|
|||
// ignoreCollisions: true,
|
||||
// collisionsWillMove: false
|
||||
// }
|
||||
|
||||
|
||||
|
|
|
@ -101,6 +101,10 @@ Graph.prototype.getConnectedNodes = function(nodeId) {
|
|||
return result;
|
||||
}
|
||||
|
||||
Graph.prototype.getNodesForEdge = function(edgeId) {
|
||||
return Object.keys(this.edges[edgeId]);
|
||||
}
|
||||
|
||||
Graph.prototype.getEdgeLength = function(edgeId) {
|
||||
var nodesInEdge = Object.keys(this.edges[edgeId]);
|
||||
return this.getNodeDistance(nodesInEdge[0], nodesInEdge[1]);
|
||||
|
|
|
@ -1,127 +0,0 @@
|
|||
//
|
||||
// Created by Bradley Austin Davis on 2015/08/29
|
||||
// Copyright 2015 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
|
||||
//
|
||||
LEFT_CONTROLLER = 0;
|
||||
RIGHT_CONTROLLER = 1;
|
||||
|
||||
// FIXME add a customizable wand model and a mechanism to switch between wands
|
||||
HandController = function(side) {
|
||||
this.side = side;
|
||||
this.palm = 2 * side;
|
||||
this.tip = 2 * side + 1;
|
||||
this.action = findAction(side ? "ACTION2" : "ACTION1");
|
||||
this.altAction = findAction(side ? "ACTION1" : "ACTION2");
|
||||
this.active = false;
|
||||
this.tipScale = 1.4;
|
||||
this.pointer = Overlays.addOverlay("sphere", {
|
||||
position: ZERO_VECTOR,
|
||||
size: 0.01,
|
||||
color: COLORS.YELLOW,
|
||||
alpha: 1.0,
|
||||
solid: true,
|
||||
visible: false,
|
||||
});
|
||||
|
||||
// Connect to desired events
|
||||
var _this = this;
|
||||
Controller.actionEvent.connect(function(action, state) {
|
||||
_this.onActionEvent(action, state);
|
||||
});
|
||||
|
||||
Script.update.connect(function(deltaTime) {
|
||||
_this.onUpdate(deltaTime);
|
||||
});
|
||||
|
||||
Script.scriptEnding.connect(function() {
|
||||
_this.onCleanup();
|
||||
});
|
||||
}
|
||||
|
||||
HandController.prototype.onActionEvent = function(action, state) {
|
||||
var spatialControlCount = Controller.getNumberOfSpatialControls();
|
||||
// If only 2 spacial controls, then we only have one controller active, so use either button
|
||||
// otherwise, only use the specified action
|
||||
|
||||
if (action == this.action) {
|
||||
if (state) {
|
||||
this.onClick();
|
||||
} else {
|
||||
this.onRelease();
|
||||
}
|
||||
}
|
||||
|
||||
if (action == this.altAction) {
|
||||
if (state) {
|
||||
this.onAltClick();
|
||||
} else {
|
||||
this.onAltRelease();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
HandController.prototype.setActive = function(active) {
|
||||
if (active == this.active) {
|
||||
return;
|
||||
}
|
||||
logDebug("Hand controller changing active state: " + active);
|
||||
this.active = active;
|
||||
Overlays.editOverlay(this.pointer, {
|
||||
visible: this.active
|
||||
});
|
||||
Entities.editEntity(this.wand, {
|
||||
visible: this.active
|
||||
});
|
||||
}
|
||||
|
||||
HandController.prototype.updateControllerState = function() {
|
||||
// FIXME this returns data if either the left or right controller is not on the base
|
||||
this.palmPos = Controller.getSpatialControlPosition(this.palm);
|
||||
var tipPos = Controller.getSpatialControlPosition(this.tip);
|
||||
this.tipPosition = scaleLine(this.palmPos, tipPos, this.tipScale);
|
||||
// When on the base, hydras report a position of 0
|
||||
this.setActive(Vec3.length(this.palmPos) > 0.001);
|
||||
|
||||
//logDebug(Controller.getTriggerValue(0) + " " + Controller.getTriggerValue(1));
|
||||
|
||||
//if (this.active) {
|
||||
// logDebug("#ctrls " + Controller.getNumberOfSpatialControls() + " Side: " + this.side + " Palm: " + this.palm + " " + vec3toStr(this.palmPos))
|
||||
//}
|
||||
}
|
||||
|
||||
HandController.prototype.onCleanup = function() {
|
||||
Overlays.deleteOverlay(this.pointer);
|
||||
}
|
||||
|
||||
HandController.prototype.onUpdate = function(deltaTime) {
|
||||
this.updateControllerState();
|
||||
if (this.active) {
|
||||
Overlays.editOverlay(this.pointer, {
|
||||
position: this.tipPosition
|
||||
});
|
||||
Entities.editEntity(this.wand, {
|
||||
position: this.tipPosition
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
HandController.prototype.onClick = function() {
|
||||
logDebug("Base hand controller does nothing on click");
|
||||
}
|
||||
|
||||
HandController.prototype.onRelease = function() {
|
||||
logDebug("Base hand controller does nothing on release");
|
||||
}
|
||||
|
||||
HandController.prototype.onAltClick = function() {
|
||||
logDebug("Base hand controller does nothing on alt click");
|
||||
}
|
||||
|
||||
HandController.prototype.onAltRelease = function() {
|
||||
logDebug("Base hand controller does nothing on alt click");
|
||||
}
|
||||
|
||||
|
|
@ -8,30 +8,34 @@
|
|||
|
||||
var UPDATE_INTERVAL = 0.1;
|
||||
|
||||
Script.include("graph.js");
|
||||
Script.include("edgeSpring.js");
|
||||
|
||||
// A collection of balls and edges connecting them.
|
||||
MagBalls = function() {
|
||||
Graph.call(this);
|
||||
|
||||
this.MAX_ADJUST_ITERATIONS = 100;
|
||||
this.REFRESH_WAIT_TICKS = 10;
|
||||
this.MAX_VARIANCE = 0.25;
|
||||
this.lastUpdateAge = 0;
|
||||
this.stable = false;
|
||||
this.stable = true;
|
||||
this.adjustIterations = 0;
|
||||
this.selectedNodes = {};
|
||||
this.edgeObjects = {};
|
||||
this.unstableEdges = {};
|
||||
|
||||
this.refresh();
|
||||
|
||||
var _this = this;
|
||||
Script.update.connect(function(deltaTime) {
|
||||
_this.onUpdate(deltaTime);
|
||||
});
|
||||
//Script.update.connect(function(deltaTime) {
|
||||
// _this.onUpdate(deltaTime);
|
||||
//});
|
||||
|
||||
Script.scriptEnding.connect(function() {
|
||||
_this.onCleanup();
|
||||
});
|
||||
|
||||
Entities.addingEntity.connect(function(entityId) {
|
||||
_this.onEntityAdded(entityId);
|
||||
});
|
||||
}
|
||||
|
||||
MagBalls.prototype = Object.create( Graph.prototype );
|
||||
|
@ -40,14 +44,23 @@ MagBalls.prototype.onUpdate = function(deltaTime) {
|
|||
this.lastUpdateAge += deltaTime;
|
||||
if (this.lastUpdateAge > UPDATE_INTERVAL) {
|
||||
this.lastUpdateAge = 0;
|
||||
if (!this.stable) {
|
||||
if (this.refreshNeeded) {
|
||||
if (++this.refreshNeeded > this.REFRESH_WAIT_TICKS) {
|
||||
logDebug("Refreshing");
|
||||
this.refresh();
|
||||
this.refreshNeeded = 0;
|
||||
}
|
||||
}
|
||||
if (!this.stable && !Object.keys(this.selectedNodes).length) {
|
||||
this.adjustIterations += 1;
|
||||
// logDebug("Update");
|
||||
var adjusted = false;
|
||||
var nodeAdjustResults = {};
|
||||
var fixupEdges = {};
|
||||
|
||||
for(var edgeId in this.edges) {
|
||||
if (!this.unstableEdges[edgeId]) {
|
||||
continue;
|
||||
}
|
||||
adjusted |= this.edgeObjects[edgeId].adjust(nodeAdjustResults);
|
||||
}
|
||||
for (var nodeId in nodeAdjustResults) {
|
||||
|
@ -72,8 +85,12 @@ MagBalls.prototype.onUpdate = function(deltaTime) {
|
|||
}, ((UPDATE_INTERVAL * 1000) / 2));
|
||||
|
||||
if (!adjusted || this.adjustIterations > this.MAX_ADJUST_ITERATIONS) {
|
||||
if (adjusted) {
|
||||
logDebug("Could not stabilized after " + this.MAX_ADJUST_ITERATIONS + " abandoning");
|
||||
}
|
||||
this.adjustIterations = 0;
|
||||
this.stable = true;
|
||||
this.unstableEdges = {};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -118,7 +135,7 @@ MagBalls.prototype.findPotentialEdges = function(nodeId) {
|
|||
// Check distance to attempt
|
||||
var distance = this.getNodeDistance(nodeId, otherNodeId);
|
||||
var variance = this.getVariance(distance);
|
||||
if (Math.abs(variance) > 0.25) {
|
||||
if (Math.abs(variance) > this.MAX_VARIANCE) {
|
||||
continue;
|
||||
}
|
||||
|
||||
|
@ -127,26 +144,38 @@ MagBalls.prototype.findPotentialEdges = function(nodeId) {
|
|||
return variances;
|
||||
}
|
||||
|
||||
MagBalls.prototype.grabBall = function(position, maxDist) {
|
||||
var selected = this.findNearestNode(position, maxDist);
|
||||
if (!selected) {
|
||||
selected = this.createNode({ position: position });
|
||||
}
|
||||
if (selected) {
|
||||
this.stable = true;
|
||||
this.breakEdges(selected);
|
||||
this.selectedNodes[selected] = true;
|
||||
}
|
||||
return selected;
|
||||
MagBalls.prototype.breakEdges = function(nodeId) {
|
||||
//var unstableNodes = this.findShape(Object.keys.target);
|
||||
//for (var node in unstableNodes) {
|
||||
// this.unstableNodes[node] = true;
|
||||
//}
|
||||
Graph.prototype.breakEdges.call(this, nodeId);
|
||||
}
|
||||
|
||||
MagBalls.prototype.createBall = function(position) {
|
||||
var created = this.createNode({ position: position });
|
||||
this.selectBall(created);
|
||||
return created;
|
||||
}
|
||||
|
||||
MagBalls.prototype.selectBall = function(selected) {
|
||||
if (!selected) {
|
||||
return;
|
||||
}
|
||||
// stop updating shapes while manipulating
|
||||
this.stable = true;
|
||||
this.selectedNodes[selected] = true;
|
||||
this.breakEdges(selected);
|
||||
}
|
||||
|
||||
|
||||
MagBalls.prototype.releaseBall = function(releasedBall) {
|
||||
delete this.selectedNodes[releasedBall];
|
||||
logDebug("Released ball: " + releasedBall);
|
||||
|
||||
var releasePosition = this.getNodePosition(releasedBall);
|
||||
this.stable = false;
|
||||
|
||||
var releasePosition = this.getNodePosition(releasedBall);
|
||||
|
||||
// iterate through the other balls and ensure we don't intersect with
|
||||
// any of them. If we do, just delete this ball and return.
|
||||
|
@ -169,10 +198,34 @@ MagBalls.prototype.releaseBall = function(releasedBall) {
|
|||
for (var otherBallId in targets) {
|
||||
this.createEdge(otherBallId, releasedBall);
|
||||
}
|
||||
|
||||
var unstableNodes = this.findShape(releasedBall);
|
||||
for (var nodeId in unstableNodes) {
|
||||
for (var edgeId in this.nodes[nodeId]) {
|
||||
this.unstableEdges[edgeId] = true;
|
||||
}
|
||||
}
|
||||
this.validate();
|
||||
}
|
||||
|
||||
|
||||
MagBalls.prototype.findShape = function(nodeId) {
|
||||
var result = {};
|
||||
var queue = [ nodeId ];
|
||||
while (queue.length) {
|
||||
var curNode = queue.shift();
|
||||
if (result[curNode]) {
|
||||
continue;
|
||||
}
|
||||
result[curNode] = true;
|
||||
for (var otherNodeId in this.getConnectedNodes(curNode)) {
|
||||
queue.push(otherNodeId);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
MagBalls.prototype.getVariance = function(distance) {
|
||||
// FIXME different balls or edges might have different ideas of variance...
|
||||
// let something else handle this
|
||||
|
@ -263,8 +316,11 @@ MagBalls.prototype.refresh = function() {
|
|||
Script.setTimeout(function() {
|
||||
for (var i in deleteEdges) {
|
||||
var edgeId = deleteEdges[i];
|
||||
logDebug("deleting invalid edge " + edgeId);
|
||||
Entities.deleteEntity(edgeId);
|
||||
//logDebug("deleting invalid edge " + edgeId);
|
||||
//Entities.deleteEntity(edgeId);
|
||||
Entities.editEntity(edgeId, {
|
||||
color: COLORS.RED
|
||||
})
|
||||
}
|
||||
}, 1000);
|
||||
}
|
||||
|
@ -291,3 +347,14 @@ MagBalls.prototype.fixupEdge = function(edgeId) {
|
|||
Entities.editEntity(edgeId, this.findEdgeParams(ballsInEdge[0], ballsInEdge[1]));
|
||||
}
|
||||
|
||||
MagBalls.prototype.onEntityAdded = function(entityId) {
|
||||
// We already have it
|
||||
if (this.nodes[entityId] || this.edges[entityId]) {
|
||||
return;
|
||||
}
|
||||
|
||||
var properties = Entities.getEntityProperties(entityId);
|
||||
if (properties.name == BALL_NAME || properties.name == EDGE_NAME) {
|
||||
this.refreshNeeded = 1;
|
||||
}
|
||||
}
|
|
@ -1,25 +0,0 @@
|
|||
//
|
||||
// Created by Bradley Austin Davis on 2015/08/25
|
||||
// Copyright 2015 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
|
||||
//
|
||||
|
||||
Script.include("constants.js");
|
||||
Script.include("utils.js");
|
||||
Script.include("magBalls.js");
|
||||
|
||||
Script.include("ballController.js");
|
||||
|
||||
var magBalls = new MagBalls();
|
||||
|
||||
// Clear any previous balls
|
||||
// magBalls.clear();
|
||||
|
||||
MenuController = function(side) {
|
||||
HandController.call(this, side);
|
||||
}
|
||||
|
||||
// FIXME resolve some of the issues with dual controllers before allowing both controllers active
|
||||
var handControllers = [new BallController(LEFT_CONTROLLER, magBalls)]; //, new HandController(RIGHT) ];
|
|
@ -1,66 +0,0 @@
|
|||
Script.include("handController.js");
|
||||
|
||||
MenuController = function(side, magBalls) {
|
||||
HandController.call(this, side);
|
||||
}
|
||||
|
||||
MenuController.prototype = Object.create( HandController.prototype );
|
||||
|
||||
MenuController.prototype.onUpdate = function(deltaTime) {
|
||||
HandController.prototype.onUpdate.call(this, deltaTime);
|
||||
if (!this.selected) {
|
||||
// Find the highlight target and set it.
|
||||
var target = this.magBalls.findNearestNode(this.tipPosition, BALL_SELECTION_RADIUS);
|
||||
this.highlighter.highlight(target);
|
||||
return;
|
||||
}
|
||||
this.highlighter.highlight(null);
|
||||
Entities.editEntity(this.selected, { position: this.tipPosition });
|
||||
var targetBalls = this.magBalls.findPotentialEdges(this.selected);
|
||||
for (var ballId in targetBalls) {
|
||||
if (!this.ghostEdges[ballId]) {
|
||||
// create the ovleray
|
||||
this.ghostEdges[ballId] = Overlays.addOverlay("line3d", {
|
||||
start: this.magBalls.getNodePosition(ballId),
|
||||
end: this.tipPosition,
|
||||
color: COLORS.RED,
|
||||
alpha: 1,
|
||||
lineWidth: 5,
|
||||
visible: true,
|
||||
});
|
||||
} else {
|
||||
Overlays.editOverlay(this.ghostEdges[ballId], {
|
||||
end: this.tipPosition,
|
||||
});
|
||||
}
|
||||
}
|
||||
for (var ballId in this.ghostEdges) {
|
||||
if (!targetBalls[ballId]) {
|
||||
Overlays.deleteOverlay(this.ghostEdges[ballId]);
|
||||
delete this.ghostEdges[ballId];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
MenuController.prototype.onClick = function() {
|
||||
this.selected = this.magBalls.grabBall(this.tipPosition, BALL_SELECTION_RADIUS);
|
||||
this.highlighter.highlight(null);
|
||||
}
|
||||
|
||||
MenuController.prototype.onRelease = function() {
|
||||
this.clearGhostEdges();
|
||||
this.magBalls.releaseBall(this.selected);
|
||||
this.selected = null;
|
||||
}
|
||||
|
||||
MenuController.prototype.clearGhostEdges = function() {
|
||||
for(var ballId in this.ghostEdges) {
|
||||
Overlays.deleteOverlay(this.ghostEdges[ballId]);
|
||||
delete this.ghostEdges[ballId];
|
||||
}
|
||||
}
|
||||
|
||||
MenuController.prototype.onCleanup = function() {
|
||||
HandController.prototype.onCleanup.call(this);
|
||||
this.clearGhostEdges();
|
||||
}
|
|
@ -64,6 +64,7 @@
|
|||
|
||||
#include <EntityScriptingInterface.h>
|
||||
#include <ErrorDialog.h>
|
||||
#include <Finally.h>
|
||||
#include <FramebufferCache.h>
|
||||
#include <gpu/Batch.h>
|
||||
#include <gpu/Context.h>
|
||||
|
@ -1010,6 +1011,16 @@ void Application::paintGL() {
|
|||
if (nullptr == _displayPlugin) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Some plugins process message events, potentially leading to
|
||||
// re-entering a paint event. don't allow further processing if this
|
||||
// happens
|
||||
if (_inPaint) {
|
||||
return;
|
||||
}
|
||||
_inPaint = true;
|
||||
Finally clearFlagLambda([this] { _inPaint = false; });
|
||||
|
||||
auto displayPlugin = getActiveDisplayPlugin();
|
||||
displayPlugin->preRender();
|
||||
_offscreenContext->makeCurrent();
|
||||
|
@ -1204,7 +1215,7 @@ void Application::paintGL() {
|
|||
// Ensure all operations from the previous context are complete before we try to read the fbo
|
||||
glWaitSync(sync, 0, GL_TIMEOUT_IGNORED);
|
||||
glDeleteSync(sync);
|
||||
|
||||
|
||||
{
|
||||
PROFILE_RANGE(__FUNCTION__ "/pluginDisplay");
|
||||
displayPlugin->display(finalTexture, toGlm(size));
|
||||
|
@ -1220,7 +1231,6 @@ void Application::paintGL() {
|
|||
_frameCount++;
|
||||
Stats::getInstance()->setRenderDetails(renderArgs._details);
|
||||
|
||||
|
||||
// Reset the gpu::Context Stages
|
||||
// Back to the default framebuffer;
|
||||
gpu::Batch batch;
|
||||
|
@ -4712,53 +4722,68 @@ void Application::updateDisplayMode() {
|
|||
|
||||
auto offscreenUi = DependencyManager::get<OffscreenUi>();
|
||||
DisplayPluginPointer oldDisplayPlugin = _displayPlugin;
|
||||
if (oldDisplayPlugin != newDisplayPlugin) {
|
||||
if (!_currentDisplayPluginActions.isEmpty()) {
|
||||
auto menu = Menu::getInstance();
|
||||
foreach(auto itemInfo, _currentDisplayPluginActions) {
|
||||
menu->removeMenuItem(itemInfo.first, itemInfo.second);
|
||||
}
|
||||
_currentDisplayPluginActions.clear();
|
||||
}
|
||||
|
||||
if (newDisplayPlugin) {
|
||||
_offscreenContext->makeCurrent();
|
||||
_activatingDisplayPlugin = true;
|
||||
newDisplayPlugin->activate();
|
||||
_activatingDisplayPlugin = false;
|
||||
_offscreenContext->makeCurrent();
|
||||
offscreenUi->resize(fromGlm(newDisplayPlugin->getRecommendedUiSize()));
|
||||
_offscreenContext->makeCurrent();
|
||||
}
|
||||
|
||||
oldDisplayPlugin = _displayPlugin;
|
||||
_displayPlugin = newDisplayPlugin;
|
||||
|
||||
// If the displayPlugin is a screen based HMD, then it will want the HMDTools displayed
|
||||
// Direct Mode HMDs (like windows Oculus) will be isHmd() but will have a screen of -1
|
||||
bool newPluginWantsHMDTools = newDisplayPlugin ?
|
||||
(newDisplayPlugin->isHmd() && (newDisplayPlugin->getHmdScreen() >= 0)) : false;
|
||||
bool oldPluginWantedHMDTools = oldDisplayPlugin ?
|
||||
(oldDisplayPlugin->isHmd() && (oldDisplayPlugin->getHmdScreen() >= 0)) : false;
|
||||
|
||||
// Only show the hmd tools after the correct plugin has
|
||||
// been activated so that it's UI is setup correctly
|
||||
if (newPluginWantsHMDTools) {
|
||||
_pluginContainer->showDisplayPluginsTools();
|
||||
}
|
||||
|
||||
if (oldDisplayPlugin) {
|
||||
oldDisplayPlugin->deactivate();
|
||||
_offscreenContext->makeCurrent();
|
||||
|
||||
// if the old plugin was HMD and the new plugin is not HMD, then hide our hmdtools
|
||||
if (oldPluginWantedHMDTools && !newPluginWantsHMDTools) {
|
||||
DependencyManager::get<DialogsManager>()->hmdTools(false);
|
||||
}
|
||||
}
|
||||
emit activeDisplayPluginChanged();
|
||||
resetSensors();
|
||||
if (newDisplayPlugin == oldDisplayPlugin) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Some plugins *cough* Oculus *cough* process message events from inside their
|
||||
// display function, and we don't want to change the display plugin underneath
|
||||
// the paintGL call, so we need to guard against that
|
||||
if (_inPaint) {
|
||||
qDebug() << "Deferring plugin switch until out of painting";
|
||||
// Have the old plugin stop requesting renders
|
||||
oldDisplayPlugin->stop();
|
||||
QCoreApplication::postEvent(this, new LambdaEvent([this] {
|
||||
updateDisplayMode();
|
||||
}));
|
||||
return;
|
||||
}
|
||||
|
||||
if (!_currentDisplayPluginActions.isEmpty()) {
|
||||
auto menu = Menu::getInstance();
|
||||
foreach(auto itemInfo, _currentDisplayPluginActions) {
|
||||
menu->removeMenuItem(itemInfo.first, itemInfo.second);
|
||||
}
|
||||
_currentDisplayPluginActions.clear();
|
||||
}
|
||||
|
||||
if (newDisplayPlugin) {
|
||||
_offscreenContext->makeCurrent();
|
||||
_activatingDisplayPlugin = true;
|
||||
newDisplayPlugin->activate();
|
||||
_activatingDisplayPlugin = false;
|
||||
_offscreenContext->makeCurrent();
|
||||
offscreenUi->resize(fromGlm(newDisplayPlugin->getRecommendedUiSize()));
|
||||
_offscreenContext->makeCurrent();
|
||||
}
|
||||
|
||||
oldDisplayPlugin = _displayPlugin;
|
||||
_displayPlugin = newDisplayPlugin;
|
||||
|
||||
// If the displayPlugin is a screen based HMD, then it will want the HMDTools displayed
|
||||
// Direct Mode HMDs (like windows Oculus) will be isHmd() but will have a screen of -1
|
||||
bool newPluginWantsHMDTools = newDisplayPlugin ?
|
||||
(newDisplayPlugin->isHmd() && (newDisplayPlugin->getHmdScreen() >= 0)) : false;
|
||||
bool oldPluginWantedHMDTools = oldDisplayPlugin ?
|
||||
(oldDisplayPlugin->isHmd() && (oldDisplayPlugin->getHmdScreen() >= 0)) : false;
|
||||
|
||||
// Only show the hmd tools after the correct plugin has
|
||||
// been activated so that it's UI is setup correctly
|
||||
if (newPluginWantsHMDTools) {
|
||||
_pluginContainer->showDisplayPluginsTools();
|
||||
}
|
||||
|
||||
if (oldDisplayPlugin) {
|
||||
oldDisplayPlugin->deactivate();
|
||||
_offscreenContext->makeCurrent();
|
||||
|
||||
// if the old plugin was HMD and the new plugin is not HMD, then hide our hmdtools
|
||||
if (oldPluginWantedHMDTools && !newPluginWantsHMDTools) {
|
||||
DependencyManager::get<DialogsManager>()->hmdTools(false);
|
||||
}
|
||||
}
|
||||
emit activeDisplayPluginChanged();
|
||||
resetSensors();
|
||||
Q_ASSERT_X(_displayPlugin, "Application::updateDisplayMode", "could not find an activated display plugin");
|
||||
}
|
||||
|
||||
|
@ -4928,7 +4953,7 @@ void Application::setPalmData(Hand* hand, UserInputMapper::PoseValue pose, float
|
|||
|
||||
// Store the one fingertip in the palm structure so we can track velocity
|
||||
const float FINGER_LENGTH = 0.3f; // meters
|
||||
const glm::vec3 FINGER_VECTOR(0.0f, 0.0f, FINGER_LENGTH);
|
||||
const glm::vec3 FINGER_VECTOR(0.0f, FINGER_LENGTH, 0.0f);
|
||||
const glm::vec3 newTipPosition = position + rotation * FINGER_VECTOR;
|
||||
glm::vec3 oldTipPosition = palm->getTipRawPosition();
|
||||
if (deltaTime > 0.0f) {
|
||||
|
@ -5061,3 +5086,15 @@ void Application::crashApplication() {
|
|||
|
||||
qCDebug(interfaceapp) << "Intentionally crashed Interface";
|
||||
}
|
||||
|
||||
void Application::setActiveDisplayPlugin(const QString& pluginName) {
|
||||
auto menu = Menu::getInstance();
|
||||
foreach(DisplayPluginPointer displayPlugin, PluginManager::getInstance()->getDisplayPlugins()) {
|
||||
QString name = displayPlugin->getName();
|
||||
QAction* action = menu->getActionForOption(name);
|
||||
if (pluginName == name) {
|
||||
action->setChecked(true);
|
||||
}
|
||||
}
|
||||
updateDisplayMode();
|
||||
}
|
||||
|
|
|
@ -683,6 +683,7 @@ private:
|
|||
int _simsPerSecondReport = 0;
|
||||
quint64 _lastSimsPerSecondUpdate = 0;
|
||||
bool _isForeground = true; // starts out assumed to be in foreground
|
||||
bool _inPaint = false;
|
||||
|
||||
friend class PluginContainerProxy;
|
||||
};
|
||||
|
|
|
@ -10,16 +10,39 @@
|
|||
//
|
||||
|
||||
#include "FileLogger.h"
|
||||
#include "HifiSockAddr.h"
|
||||
#include <FileUtils.h>
|
||||
#include <QDateTime>
|
||||
#include <QFile>
|
||||
#include <QDir>
|
||||
#include <QDesktopServices>
|
||||
|
||||
const QString FILENAME_FORMAT = "hifi-log_%1_%2.txt";
|
||||
const QString DATETIME_FORMAT = "yyyy-MM-dd_hh.mm.ss";
|
||||
const QString LOGS_DIRECTORY = "Logs";
|
||||
#include <QtCore/QDateTime>
|
||||
#include <QtCore/QFile>
|
||||
#include <QtCore/QDir>
|
||||
#include <QtGui/QDesktopServices>
|
||||
|
||||
#include <NumericalConstants.h>
|
||||
#include <FileUtils.h>
|
||||
#include <SharedUtil.h>
|
||||
|
||||
#include "HifiSockAddr.h"
|
||||
|
||||
static const QString FILENAME_FORMAT = "hifi-log_%1_%2.txt";
|
||||
static const QString DATETIME_FORMAT = "yyyy-MM-dd_hh.mm.ss";
|
||||
static const QString LOGS_DIRECTORY = "Logs";
|
||||
// Max log size is 1 MB
|
||||
static const uint64_t MAX_LOG_SIZE = 1024 * 1024;
|
||||
// Max log age is 1 hour
|
||||
static const uint64_t MAX_LOG_AGE_USECS = USECS_PER_SECOND * 3600;
|
||||
|
||||
QString getLogRollerFilename() {
|
||||
QString result = FileUtils::standardPath(LOGS_DIRECTORY);
|
||||
QHostAddress clientAddress = getLocalAddress();
|
||||
QDateTime now = QDateTime::currentDateTime();
|
||||
result.append(QString(FILENAME_FORMAT).arg(clientAddress.toString(), now.toString(DATETIME_FORMAT)));
|
||||
return result;
|
||||
}
|
||||
|
||||
const QString& getLogFilename() {
|
||||
static QString fileName = FileUtils::standardPath(LOGS_DIRECTORY) + "hifi-log.txt";
|
||||
return fileName;
|
||||
}
|
||||
|
||||
|
||||
class FilePersistThread : public GenericQueueThread < QString > {
|
||||
public:
|
||||
|
@ -28,8 +51,22 @@ public:
|
|||
}
|
||||
|
||||
protected:
|
||||
void rollFileIfNecessary(QFile& file) {
|
||||
uint64_t now = usecTimestampNow();
|
||||
if ((file.size() > MAX_LOG_SIZE) || (now - _lastRollTime) > MAX_LOG_AGE_USECS) {
|
||||
QString newFileName = getLogRollerFilename();
|
||||
if (file.copy(newFileName)) {
|
||||
_lastRollTime = now;
|
||||
file.open(QIODevice::WriteOnly | QIODevice::Truncate);
|
||||
file.close();
|
||||
qDebug() << "Rolled log file: " << newFileName;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
virtual bool processQueueItems(const Queue& messages) {
|
||||
QFile file(_logger._fileName);
|
||||
rollFileIfNecessary(file);
|
||||
if (file.open(QIODevice::WriteOnly | QIODevice::Append | QIODevice::Text)) {
|
||||
QTextStream out(&file);
|
||||
foreach(const QString& message, messages) {
|
||||
|
@ -40,20 +77,17 @@ protected:
|
|||
}
|
||||
private:
|
||||
const FileLogger& _logger;
|
||||
uint64_t _lastRollTime = 0;
|
||||
};
|
||||
|
||||
static FilePersistThread* _persistThreadInstance;
|
||||
|
||||
FileLogger::FileLogger(QObject* parent) :
|
||||
AbstractLoggerInterface(parent)
|
||||
AbstractLoggerInterface(parent), _fileName(getLogFilename())
|
||||
{
|
||||
_persistThreadInstance = new FilePersistThread(*this);
|
||||
_persistThreadInstance->initialize(true, QThread::LowestPriority);
|
||||
|
||||
_fileName = FileUtils::standardPath(LOGS_DIRECTORY);
|
||||
QHostAddress clientAddress = getLocalAddress();
|
||||
QDateTime now = QDateTime::currentDateTime();
|
||||
_fileName.append(QString(FILENAME_FORMAT).arg(clientAddress.toString(), now.toString(DATETIME_FORMAT)));
|
||||
}
|
||||
|
||||
FileLogger::~FileLogger() {
|
||||
|
|
|
@ -27,7 +27,7 @@ public:
|
|||
virtual void locateLog() override;
|
||||
|
||||
private:
|
||||
QString _fileName;
|
||||
const QString _fileName;
|
||||
friend class FilePersistThread;
|
||||
};
|
||||
|
||||
|
|
|
@ -147,15 +147,3 @@ void PluginContainerProxy::showDisplayPluginsTools() {
|
|||
QGLWidget* PluginContainerProxy::getPrimarySurface() {
|
||||
return qApp->_glWidget;
|
||||
}
|
||||
|
||||
void Application::setActiveDisplayPlugin(const QString& pluginName) {
|
||||
auto menu = Menu::getInstance();
|
||||
foreach(DisplayPluginPointer displayPlugin, PluginManager::getInstance()->getDisplayPlugins()) {
|
||||
QString name = displayPlugin->getName();
|
||||
QAction* action = menu->getActionForOption(name);
|
||||
if (pluginName == name) {
|
||||
action->setChecked(true);
|
||||
}
|
||||
}
|
||||
updateDisplayMode();
|
||||
}
|
||||
|
|
|
@ -785,8 +785,10 @@ Transform Avatar::calculateDisplayNameTransform(const ViewFrustum& frustum, floa
|
|||
void Avatar::renderDisplayName(gpu::Batch& batch, const ViewFrustum& frustum, const glm::ivec4& viewport) const {
|
||||
bool shouldShowReceiveStats = DependencyManager::get<AvatarManager>()->shouldShowReceiveStats() && !isMyAvatar();
|
||||
|
||||
// If we have nothing to draw, or it's tottaly transparent, return
|
||||
if ((_displayName.isEmpty() && !shouldShowReceiveStats) || _displayNameAlpha == 0.0f) {
|
||||
// If we have nothing to draw, or it's totally transparent, or it's too close or behind the camera, return
|
||||
const float CLIP_DISTANCE = 0.2f;
|
||||
if ((_displayName.isEmpty() && !shouldShowReceiveStats) || _displayNameAlpha == 0.0f
|
||||
|| (glm::dot(frustum.getDirection(), getDisplayNamePosition() - frustum.getPosition()) <= CLIP_DISTANCE)) {
|
||||
return;
|
||||
}
|
||||
auto renderer = textRenderer(DISPLAYNAME);
|
||||
|
|
|
@ -93,7 +93,7 @@ void SkeletonModel::initJointStates(QVector<JointState> states) {
|
|||
_rig->updateJointState(i, rootTransform);
|
||||
}
|
||||
|
||||
buildShapes();
|
||||
computeBoundingShape();
|
||||
|
||||
Extents meshExtents = getMeshExtents();
|
||||
_headClipDistance = -(meshExtents.minimum.z / _scale.z - _defaultEyeModelPosition.z);
|
||||
|
@ -255,6 +255,7 @@ void SkeletonModel::applyHandPosition(int jointIndex, const glm::vec3& position)
|
|||
rotationBetween(handRotation * glm::vec3(-sign, 0.0f, 0.0f), forearmVector),
|
||||
true, PALM_PRIORITY);
|
||||
}
|
||||
|
||||
void SkeletonModel::applyPalmData(int jointIndex, PalmData& palm) {
|
||||
if (jointIndex == -1 || jointIndex >= _rig->getJointStateCount()) {
|
||||
return;
|
||||
|
@ -268,9 +269,7 @@ void SkeletonModel::applyPalmData(int jointIndex, PalmData& palm) {
|
|||
// the palm's position must be transformed into the model-frame
|
||||
glm::quat inverseRotation = glm::inverse(_rotation);
|
||||
glm::vec3 palmPosition = inverseRotation * (palm.getPosition() - _translation);
|
||||
|
||||
// the palm's "raw" rotation is already in the model-frame
|
||||
glm::quat palmRotation = palm.getRawRotation();
|
||||
glm::quat palmRotation = inverseRotation * palm.getRotation();
|
||||
|
||||
inverseKinematics(jointIndex, palmPosition, palmRotation, PALM_PRIORITY);
|
||||
}
|
||||
|
@ -353,9 +352,9 @@ void SkeletonModel::renderOrientationDirections(gpu::Batch& batch, int jointInde
|
|||
}
|
||||
OrientationLineIDs& jointLineIDs = _jointOrientationLines[jointIndex];
|
||||
|
||||
glm::vec3 pRight = position + orientation * IDENTITY_RIGHT * size;
|
||||
glm::vec3 pUp = position + orientation * IDENTITY_UP * size;
|
||||
glm::vec3 pFront = position + orientation * IDENTITY_FRONT * size;
|
||||
glm::vec3 pRight = position + orientation * IDENTITY_RIGHT * size;
|
||||
glm::vec3 pUp = position + orientation * IDENTITY_UP * size;
|
||||
glm::vec3 pFront = position + orientation * IDENTITY_FRONT * size;
|
||||
|
||||
glm::vec3 red(1.0f, 0.0f, 0.0f);
|
||||
geometryCache->renderLine(batch, position, pRight, red, jointLineIDs._right);
|
||||
|
@ -473,7 +472,7 @@ float MIN_JOINT_MASS = 1.0f;
|
|||
float VERY_BIG_MASS = 1.0e6f;
|
||||
|
||||
// virtual
|
||||
void SkeletonModel::buildShapes() {
|
||||
void SkeletonModel::computeBoundingShape() {
|
||||
if (_geometry == NULL || _rig->jointStatesEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
@ -483,36 +482,86 @@ void SkeletonModel::buildShapes() {
|
|||
// rootJointIndex == -1 if the avatar model has no skeleton
|
||||
return;
|
||||
}
|
||||
computeBoundingShape(geometry);
|
||||
}
|
||||
|
||||
void SkeletonModel::computeBoundingShape(const FBXGeometry& geometry) {
|
||||
// compute default joint transforms
|
||||
int numStates = _rig->getJointStateCount();
|
||||
QVector<glm::mat4> transforms;
|
||||
transforms.fill(glm::mat4(), numStates);
|
||||
// BOUNDING SHAPE HACK: before we measure the bounds of the joints we use IK to put the
|
||||
// hands and feet into positions that are more correct than the default pose.
|
||||
|
||||
// Measure limb lengths so we can specify IK targets that will pull hands and feet tight to body
|
||||
QVector<QString> endEffectors;
|
||||
endEffectors.push_back("RightHand");
|
||||
endEffectors.push_back("LeftHand");
|
||||
endEffectors.push_back("RightFoot");
|
||||
endEffectors.push_back("LeftFoot");
|
||||
|
||||
QVector<QString> baseJoints;
|
||||
baseJoints.push_back("RightArm");
|
||||
baseJoints.push_back("LeftArm");
|
||||
baseJoints.push_back("RightUpLeg");
|
||||
baseJoints.push_back("LeftUpLeg");
|
||||
|
||||
for (int i = 0; i < endEffectors.size(); ++i) {
|
||||
QString tipName = endEffectors[i];
|
||||
QString baseName = baseJoints[i];
|
||||
float limbLength = 0.0f;
|
||||
int tipIndex = _rig->indexOfJoint(tipName);
|
||||
if (tipIndex == -1) {
|
||||
continue;
|
||||
}
|
||||
// save tip's relative rotation for later
|
||||
glm::quat tipRotation = _rig->getJointState(tipIndex).getRotationInConstrainedFrame();
|
||||
|
||||
// IK on each endpoint
|
||||
int jointIndex = tipIndex;
|
||||
QVector<int> freeLineage;
|
||||
float priority = 1.0f;
|
||||
while (jointIndex > -1) {
|
||||
JointState limbJoint = _rig->getJointState(jointIndex);
|
||||
freeLineage.push_back(jointIndex);
|
||||
if (limbJoint.getName() == baseName) {
|
||||
glm::vec3 targetPosition = limbJoint.getPosition() - glm::vec3(0.0f, 1.5f * limbLength, 0.0f);
|
||||
// do IK a few times to make sure the endpoint gets close to its target
|
||||
for (int j = 0; j < 5; ++j) {
|
||||
_rig->inverseKinematics(tipIndex,
|
||||
targetPosition,
|
||||
glm::quat(),
|
||||
priority,
|
||||
freeLineage,
|
||||
glm::mat4());
|
||||
}
|
||||
break;
|
||||
}
|
||||
limbLength += limbJoint.getDistanceToParent();
|
||||
jointIndex = limbJoint.getParentIndex();
|
||||
}
|
||||
|
||||
// since this IK target is totally bogus we restore the tip's relative rotation
|
||||
_rig->setJointRotationInConstrainedFrame(tipIndex, tipRotation, priority);
|
||||
}
|
||||
|
||||
// recompute all joint model-frame transforms
|
||||
glm::mat4 rootTransform = glm::scale(_scale) * glm::translate(_offset) * geometry.offset;
|
||||
for (int i = 0; i < _rig->getJointStateCount(); i++) {
|
||||
_rig->updateJointState(i, rootTransform);
|
||||
}
|
||||
// END BOUNDING SHAPE HACK
|
||||
|
||||
// compute bounding box that encloses all shapes
|
||||
Extents totalExtents;
|
||||
totalExtents.reset();
|
||||
totalExtents.addPoint(glm::vec3(0.0f));
|
||||
int numStates = _rig->getJointStateCount();
|
||||
for (int i = 0; i < numStates; i++) {
|
||||
// compute the default transform of this joint
|
||||
const JointState& state = _rig->getJointState(i);
|
||||
int parentIndex = state.getParentIndex();
|
||||
if (parentIndex == -1) {
|
||||
transforms[i] = _rig->getJointTransform(i);
|
||||
} else {
|
||||
glm::quat modifiedRotation = state.getPreRotation() * state.getDefaultRotation() * state.getPostRotation();
|
||||
transforms[i] = transforms[parentIndex] * glm::translate(state.getTranslation())
|
||||
* state.getPreTransform() * glm::mat4_cast(modifiedRotation) * state.getPostTransform();
|
||||
}
|
||||
|
||||
// Each joint contributes a sphere at its position
|
||||
glm::vec3 axis(state.getBoneRadius());
|
||||
glm::vec3 jointPosition = extractTranslation(transforms[i]);
|
||||
totalExtents.addPoint(jointPosition + axis);
|
||||
totalExtents.addPoint(jointPosition - axis);
|
||||
// HACK WORKAROUND: ignore joints that may have bad translation (e.g. have been flagged as such with zero radius)
|
||||
if (state.getBoneRadius() > 0.0f) {
|
||||
// Each joint contributes a sphere at its position
|
||||
glm::vec3 axis(state.getBoneRadius());
|
||||
glm::vec3 jointPosition = state.getPosition();
|
||||
totalExtents.addPoint(jointPosition + axis);
|
||||
totalExtents.addPoint(jointPosition - axis);
|
||||
}
|
||||
}
|
||||
|
||||
// compute bounding shape parameters
|
||||
|
@ -524,6 +573,11 @@ void SkeletonModel::computeBoundingShape(const FBXGeometry& geometry) {
|
|||
|
||||
glm::vec3 rootPosition = _rig->getJointState(geometry.rootJointIndex).getPosition();
|
||||
_boundingCapsuleLocalOffset = 0.5f * (totalExtents.maximum + totalExtents.minimum) - rootPosition;
|
||||
|
||||
// RECOVER FROM BOUNINDG SHAPE HACK: now that we're all done, restore the default pose
|
||||
for (int i = 0; i < numStates; i++) {
|
||||
_rig->restoreJointRotation(i, 1.0f, 1.0f);
|
||||
}
|
||||
}
|
||||
|
||||
void SkeletonModel::renderBoundingCollisionShapes(gpu::Batch& batch, float alpha) {
|
||||
|
@ -542,7 +596,7 @@ void SkeletonModel::renderBoundingCollisionShapes(gpu::Batch& batch, float alpha
|
|||
glm::vec4(0.6f, 0.6f, 0.8f, alpha));
|
||||
|
||||
// draw a yellow sphere at the capsule bottom point
|
||||
glm::vec3 bottomPoint = topPoint - glm::vec3(0.0f, -_boundingCapsuleHeight, 0.0f);
|
||||
glm::vec3 bottomPoint = topPoint - glm::vec3(0.0f, _boundingCapsuleHeight, 0.0f);
|
||||
glm::vec3 axis = topPoint - bottomPoint;
|
||||
transform.setTranslation(bottomPoint);
|
||||
batch.setModelTransform(transform);
|
||||
|
|
|
@ -94,7 +94,6 @@ public:
|
|||
/// \return whether or not the head was found.
|
||||
glm::vec3 getDefaultEyeModelPosition() const;
|
||||
|
||||
void computeBoundingShape(const FBXGeometry& geometry);
|
||||
void renderBoundingCollisionShapes(gpu::Batch& batch, float alpha);
|
||||
float getBoundingCapsuleRadius() const { return _boundingCapsuleRadius; }
|
||||
float getBoundingCapsuleHeight() const { return _boundingCapsuleHeight; }
|
||||
|
@ -114,7 +113,7 @@ signals:
|
|||
|
||||
protected:
|
||||
|
||||
void buildShapes();
|
||||
void computeBoundingShape();
|
||||
|
||||
/// \param jointIndex index of joint in model
|
||||
/// \param position position of joint in model-frame
|
||||
|
|
|
@ -66,16 +66,7 @@ Overlay::~Overlay() {
|
|||
|
||||
void Overlay::setProperties(const QScriptValue& properties) {
|
||||
QScriptValue color = properties.property("color");
|
||||
if (color.isValid()) {
|
||||
QScriptValue red = color.property("red");
|
||||
QScriptValue green = color.property("green");
|
||||
QScriptValue blue = color.property("blue");
|
||||
if (red.isValid() && green.isValid() && blue.isValid()) {
|
||||
_color.red = red.toVariant().toInt();
|
||||
_color.green = green.toVariant().toInt();
|
||||
_color.blue = blue.toVariant().toInt();
|
||||
}
|
||||
}
|
||||
xColorFromScriptValue(properties.property("color"), _color);
|
||||
|
||||
if (properties.property("alpha").isValid()) {
|
||||
setAlpha(properties.property("alpha").toVariant().toFloat());
|
||||
|
|
|
@ -30,6 +30,7 @@
|
|||
#include "Grid3DOverlay.h"
|
||||
#include "TextOverlay.h"
|
||||
#include "Text3DOverlay.h"
|
||||
#include "Web3DOverlay.h"
|
||||
|
||||
|
||||
Overlays::Overlays() : _nextOverlayID(1) {
|
||||
|
@ -170,6 +171,8 @@ unsigned int Overlays::addOverlay(const QString& type, const QScriptValue& prope
|
|||
thisOverlay = std::make_shared<LocalModelsOverlay>(Application::getInstance()->getEntityClipboardRenderer());
|
||||
} else if (type == ModelOverlay::TYPE) {
|
||||
thisOverlay = std::make_shared<ModelOverlay>();
|
||||
} else if (type == Web3DOverlay::TYPE) {
|
||||
thisOverlay = std::make_shared<Web3DOverlay>();
|
||||
}
|
||||
|
||||
if (thisOverlay) {
|
||||
|
|
163
interface/src/ui/overlays/Web3DOverlay.cpp
Normal file
163
interface/src/ui/overlays/Web3DOverlay.cpp
Normal file
|
@ -0,0 +1,163 @@
|
|||
//
|
||||
// Web3DOverlay.cpp
|
||||
//
|
||||
//
|
||||
// Created by Clement on 7/1/14.
|
||||
// Modified and renamed by Zander Otavka on 8/4/15
|
||||
// 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 "Web3DOverlay.h"
|
||||
|
||||
#include <QtScript/QScriptValue>
|
||||
#include <QtGui/QOpenGLContext>
|
||||
#include <QtQuick/QQuickItem>
|
||||
|
||||
|
||||
#include <DeferredLightingEffect.h>
|
||||
#include <DependencyManager.h>
|
||||
#include <GeometryCache.h>
|
||||
#include <GeometryUtil.h>
|
||||
#include <TextureCache.h>
|
||||
#include <PathUtils.h>
|
||||
#include <gpu/Batch.h>
|
||||
#include <RegisteredMetaTypes.h>
|
||||
#include <AbstractViewStateInterface.h>
|
||||
|
||||
#include <OffscreenQmlSurface.h>
|
||||
|
||||
// #include "Application.h"
|
||||
// #include "GeometryUtil.h"
|
||||
|
||||
static const float DPI = 30.47f;
|
||||
static const float METERS_TO_INCHES = 39.3701f;
|
||||
static const float INCHES_TO_METERS = 1.0f / 39.3701f;
|
||||
|
||||
QString const Web3DOverlay::TYPE = "web3d";
|
||||
|
||||
Web3DOverlay::Web3DOverlay() : _dpi(DPI) { }
|
||||
|
||||
Web3DOverlay::Web3DOverlay(const Web3DOverlay* Web3DOverlay) :
|
||||
Billboard3DOverlay(Web3DOverlay),
|
||||
_url(Web3DOverlay->_url),
|
||||
_dpi(Web3DOverlay->_dpi),
|
||||
_resolution(Web3DOverlay->_resolution)
|
||||
{
|
||||
}
|
||||
|
||||
Web3DOverlay::~Web3DOverlay() {
|
||||
if (_webSurface) {
|
||||
_webSurface->pause();
|
||||
_webSurface->disconnect(_connection);
|
||||
// The lifetime of the QML surface MUST be managed by the main thread
|
||||
// Additionally, we MUST use local variables copied by value, rather than
|
||||
// member variables, since they would implicitly refer to a this that
|
||||
// is no longer valid
|
||||
auto webSurface = _webSurface;
|
||||
AbstractViewStateInterface::instance()->postLambdaEvent([webSurface] {
|
||||
webSurface->deleteLater();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
void Web3DOverlay::update(float deltatime) {
|
||||
applyTransformTo(_transform);
|
||||
}
|
||||
|
||||
void Web3DOverlay::render(RenderArgs* args) {
|
||||
if (!_visible || !getParentVisible()) {
|
||||
return;
|
||||
}
|
||||
|
||||
QOpenGLContext * currentContext = QOpenGLContext::currentContext();
|
||||
QSurface * currentSurface = currentContext->surface();
|
||||
if (!_webSurface) {
|
||||
_webSurface = new OffscreenQmlSurface();
|
||||
_webSurface->create(currentContext);
|
||||
_webSurface->setBaseUrl(QUrl::fromLocalFile(PathUtils::resourcesPath() + "/qml/"));
|
||||
_webSurface->load("WebEntity.qml");
|
||||
_webSurface->resume();
|
||||
_webSurface->getRootItem()->setProperty("url", _url);
|
||||
_webSurface->resize(QSize(_resolution.x, _resolution.y));
|
||||
_connection = QObject::connect(_webSurface, &OffscreenQmlSurface::textureUpdated, [&](GLuint textureId) {
|
||||
_texture = textureId;
|
||||
});
|
||||
currentContext->makeCurrent(currentSurface);
|
||||
}
|
||||
|
||||
vec2 size = _resolution / _dpi * INCHES_TO_METERS;
|
||||
vec2 halfSize = size / 2.0f;
|
||||
vec4 color(toGlm(getColor()), getAlpha());
|
||||
|
||||
applyTransformTo(_transform, true);
|
||||
Transform transform = _transform;
|
||||
if (glm::length2(getDimensions()) != 1.0f) {
|
||||
transform.postScale(vec3(getDimensions(), 1.0f));
|
||||
}
|
||||
|
||||
Q_ASSERT(args->_batch);
|
||||
gpu::Batch& batch = *args->_batch;
|
||||
if (_texture) {
|
||||
batch._glActiveBindTexture(GL_TEXTURE0, GL_TEXTURE_2D, _texture);
|
||||
} else {
|
||||
batch.setResourceTexture(0, DependencyManager::get<TextureCache>()->getWhiteTexture());
|
||||
}
|
||||
|
||||
batch.setModelTransform(transform);
|
||||
DependencyManager::get<DeferredLightingEffect>()->bindSimpleProgram(batch, true, false, false, true);
|
||||
DependencyManager::get<GeometryCache>()->renderQuad(batch, halfSize * -1.0f, halfSize, vec2(0), vec2(1), color);
|
||||
batch.setResourceTexture(0, args->_whiteTexture); // restore default white color after me
|
||||
}
|
||||
|
||||
void Web3DOverlay::setProperties(const QScriptValue &properties) {
|
||||
Billboard3DOverlay::setProperties(properties);
|
||||
|
||||
QScriptValue urlValue = properties.property("url");
|
||||
if (urlValue.isValid()) {
|
||||
QString newURL = urlValue.toVariant().toString();
|
||||
if (newURL != _url) {
|
||||
setURL(newURL);
|
||||
}
|
||||
}
|
||||
|
||||
QScriptValue resolution = properties.property("resolution");
|
||||
if (resolution.isValid()) {
|
||||
vec2FromScriptValue(resolution, _resolution);
|
||||
}
|
||||
|
||||
|
||||
QScriptValue dpi = properties.property("dpi");
|
||||
if (dpi.isValid()) {
|
||||
_dpi = dpi.toVariant().toFloat();
|
||||
}
|
||||
}
|
||||
|
||||
QScriptValue Web3DOverlay::getProperty(const QString& property) {
|
||||
if (property == "url") {
|
||||
return _url;
|
||||
}
|
||||
if (property == "dpi") {
|
||||
return _dpi;
|
||||
}
|
||||
return Billboard3DOverlay::getProperty(property);
|
||||
}
|
||||
|
||||
void Web3DOverlay::setURL(const QString& url) {
|
||||
_url = url;
|
||||
_isLoaded = false;
|
||||
}
|
||||
|
||||
bool Web3DOverlay::findRayIntersection(const glm::vec3& origin, const glm::vec3& direction, float& distance, BoxFace& face) {
|
||||
//// Make sure position and rotation is updated.
|
||||
applyTransformTo(_transform, true);
|
||||
vec2 size = _resolution / _dpi * INCHES_TO_METERS * vec2(getDimensions());
|
||||
// Produce the dimensions of the overlay based on the image's aspect ratio and the overlay's scale.
|
||||
return findRayRectangleIntersection(origin, direction, getRotation(), getPosition(), size, distance);
|
||||
}
|
||||
|
||||
Web3DOverlay* Web3DOverlay::createClone() const {
|
||||
return new Web3DOverlay(this);
|
||||
}
|
50
interface/src/ui/overlays/Web3DOverlay.h
Normal file
50
interface/src/ui/overlays/Web3DOverlay.h
Normal file
|
@ -0,0 +1,50 @@
|
|||
//
|
||||
// Created by Bradley Austin Davis on 2015/08/31
|
||||
// Copyright 2015 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_Web3DOverlay_h
|
||||
#define hifi_Web3DOverlay_h
|
||||
|
||||
#include "Billboard3DOverlay.h"
|
||||
|
||||
class OffscreenQmlSurface;
|
||||
|
||||
class Web3DOverlay : public Billboard3DOverlay {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
static QString const TYPE;
|
||||
virtual QString getType() const { return TYPE; }
|
||||
|
||||
Web3DOverlay();
|
||||
Web3DOverlay(const Web3DOverlay* Web3DOverlay);
|
||||
virtual ~Web3DOverlay();
|
||||
|
||||
virtual void render(RenderArgs* args);
|
||||
|
||||
virtual void update(float deltatime);
|
||||
|
||||
// setters
|
||||
void setURL(const QString& url);
|
||||
|
||||
virtual void setProperties(const QScriptValue& properties);
|
||||
virtual QScriptValue getProperty(const QString& property);
|
||||
|
||||
virtual bool findRayIntersection(const glm::vec3& origin, const glm::vec3& direction, float& distance, BoxFace& face);
|
||||
|
||||
virtual Web3DOverlay* createClone() const;
|
||||
|
||||
private:
|
||||
OffscreenQmlSurface* _webSurface{ nullptr };
|
||||
QMetaObject::Connection _connection;
|
||||
uint32_t _texture{ 0 };
|
||||
QString _url;
|
||||
float _dpi;
|
||||
vec2 _resolution{ 640, 480 };
|
||||
};
|
||||
|
||||
#endif // hifi_Web3DOverlay_h
|
|
@ -10,7 +10,7 @@
|
|||
//
|
||||
|
||||
#include "AnimationHandle.h"
|
||||
|
||||
#include "AnimationLogging.h"
|
||||
|
||||
void AnimationHandle::setURL(const QUrl& url) {
|
||||
if (_url != url) {
|
||||
|
@ -51,8 +51,8 @@ void AnimationHandle::setMaskedJoints(const QStringList& maskedJoints) {
|
|||
}
|
||||
|
||||
void AnimationHandle::setRunning(bool running, bool doRestoreJoints) {
|
||||
if (running && isRunning()) {
|
||||
// if we're already running, this is the same as a restart
|
||||
if (running && isRunning() && (getFadePerSecond() >= 0.0f)) {
|
||||
// if we're already running, this is the same as a restart -- unless we're fading out.
|
||||
setFrameIndex(getFirstFrame());
|
||||
return;
|
||||
}
|
||||
|
|
|
@ -9,30 +9,31 @@
|
|||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
//
|
||||
|
||||
#include "Rig.h"
|
||||
|
||||
#include <glm/gtx/vector_angle.hpp>
|
||||
#include <queue>
|
||||
|
||||
#include "AnimationHandle.h"
|
||||
#include "AnimationLogging.h"
|
||||
#include "Rig.h"
|
||||
|
||||
void Rig::HeadParameters::dump() const {
|
||||
qCDebug(animation, "HeadParameters =");
|
||||
qCDebug(animation, " leanSideways = %0.5f", leanSideways);
|
||||
qCDebug(animation, " leanForward = %0.5f", leanForward);
|
||||
qCDebug(animation, " torsoTwist = %0.5f", torsoTwist);
|
||||
qCDebug(animation, " leanSideways = %0.5f", (double)leanSideways);
|
||||
qCDebug(animation, " leanForward = %0.5f", (double)leanForward);
|
||||
qCDebug(animation, " torsoTwist = %0.5f", (double)torsoTwist);
|
||||
glm::vec3 axis = glm::axis(localHeadOrientation);
|
||||
float theta = glm::angle(localHeadOrientation);
|
||||
qCDebug(animation, " localHeadOrientation axis = (%.5f, %.5f, %.5f), theta = %0.5f", axis.x, axis.y, axis.z, theta);
|
||||
qCDebug(animation, " localHeadOrientation axis = (%.5f, %.5f, %.5f), theta = %0.5f", (double)axis.x, (double)axis.y, (double)axis.z, (double)theta);
|
||||
axis = glm::axis(worldHeadOrientation);
|
||||
theta = glm::angle(worldHeadOrientation);
|
||||
qCDebug(animation, " worldHeadOrientation axis = (%.5f, %.5f, %.5f), theta = %0.5f", axis.x, axis.y, axis.z, theta);
|
||||
qCDebug(animation, " worldHeadOrientation axis = (%.5f, %.5f, %.5f), theta = %0.5f", (double)axis.x, (double)axis.y, (double)axis.z, (double)theta);
|
||||
axis = glm::axis(modelRotation);
|
||||
theta = glm::angle(modelRotation);
|
||||
qCDebug(animation, " modelRotation axis = (%.5f, %.5f, %.5f), theta = %0.5f", axis.x, axis.y, axis.z, theta);
|
||||
qCDebug(animation, " modelTranslation = (%.5f, %.5f, %.5f)", modelTranslation.x, modelTranslation.y, modelTranslation.z);
|
||||
qCDebug(animation, " eyeLookAt = (%.5f, %.5f, %.5f)", eyeLookAt.x, eyeLookAt.y, eyeLookAt.z);
|
||||
qCDebug(animation, " eyeSaccade = (%.5f, %.5f, %.5f)", eyeSaccade.x, eyeSaccade.y, eyeSaccade.z);
|
||||
qCDebug(animation, " modelRotation axis = (%.5f, %.5f, %.5f), theta = %0.5f", (double)axis.x, (double)axis.y, (double)axis.z, (double)theta);
|
||||
qCDebug(animation, " modelTranslation = (%.5f, %.5f, %.5f)", (double)modelTranslation.x, (double)modelTranslation.y, (double)modelTranslation.z);
|
||||
qCDebug(animation, " eyeLookAt = (%.5f, %.5f, %.5f)", (double)eyeLookAt.x, (double)eyeLookAt.y, (double)eyeLookAt.z);
|
||||
qCDebug(animation, " eyeSaccade = (%.5f, %.5f, %.5f)", (double)eyeSaccade.x, (double)eyeSaccade.y, (double)eyeSaccade.z);
|
||||
qCDebug(animation, " leanJointIndex = %.d", leanJointIndex);
|
||||
qCDebug(animation, " neckJointIndex = %.d", neckJointIndex);
|
||||
qCDebug(animation, " leftEyeJointIndex = %.d", leftEyeJointIndex);
|
||||
|
@ -103,7 +104,7 @@ AnimationHandlePointer Rig::addAnimationByRole(const QString& role, const QStrin
|
|||
const QString& base = "https://hifi-public.s3.amazonaws.com/ozan/anim/standard_anims/";
|
||||
if (role == "walk") {
|
||||
standard = base + "walk_fwd.fbx";
|
||||
} else if (role == "backup") {
|
||||
} else if (role == "backup") {
|
||||
standard = base + "walk_bwd.fbx";
|
||||
} else if (role == "leftTurn") {
|
||||
standard = base + "turn_left.fbx";
|
||||
|
@ -411,9 +412,30 @@ void Rig::computeMotionAnimationState(float deltaTime, const glm::vec3& worldPos
|
|||
}
|
||||
bool isMoving = false;
|
||||
glm::vec3 front = worldRotation * IDENTITY_FRONT;
|
||||
float forwardSpeed = glm::dot(worldVelocity, front);
|
||||
float rightLateralSpeed = glm::dot(worldVelocity, worldRotation * IDENTITY_RIGHT);
|
||||
float rightTurningSpeed = glm::orientedAngle(front, _lastFront, IDENTITY_UP) / deltaTime;
|
||||
glm::vec3 right = worldRotation * IDENTITY_RIGHT;
|
||||
const float PERCEPTIBLE_DELTA = 0.001f;
|
||||
const float PERCEPTIBLE_SPEED = 0.1f;
|
||||
// It can be more accurate/smooth to use velocity rather than position,
|
||||
// but some modes (e.g., hmd standing) update position without updating velocity.
|
||||
// It's very hard to debug hmd standing. (Look down at yourself, or have a second person observe. HMD third person is a bit undefined...)
|
||||
// So, let's create our own workingVelocity from the worldPosition...
|
||||
glm::vec3 positionDelta = worldPosition - _lastPosition;
|
||||
glm::vec3 workingVelocity = positionDelta / deltaTime;
|
||||
// But for smoothest (non-hmd standing) results, go ahead and use velocity:
|
||||
#if !WANT_DEBUG
|
||||
// Note: Separately, we've arranged for starting/stopping animations by role (as we've done here) to pick up where they've left off when fading,
|
||||
// so that you wouldn't notice the start/stop if it happens fast enough (e.g., one frame). But the print below would still be noisy.
|
||||
if (!positionDelta.x && !positionDelta.y && !positionDelta.z) {
|
||||
workingVelocity = worldVelocity;
|
||||
}
|
||||
#endif
|
||||
|
||||
float forwardSpeed = glm::dot(workingVelocity, front);
|
||||
float rightLateralSpeed = glm::dot(workingVelocity, right);
|
||||
float rightTurningDelta = glm::orientedAngle(front, _lastFront, IDENTITY_UP);
|
||||
float rightTurningSpeed = rightTurningDelta / deltaTime;
|
||||
bool isTurning = (std::abs(rightTurningDelta) > PERCEPTIBLE_DELTA) && (std::abs(rightTurningSpeed) > PERCEPTIBLE_SPEED);
|
||||
bool isStrafing = std::abs(rightLateralSpeed) > PERCEPTIBLE_SPEED;
|
||||
auto updateRole = [&](const QString& role, bool isOn) {
|
||||
isMoving = isMoving || isOn;
|
||||
if (isOn) {
|
||||
|
@ -428,14 +450,13 @@ void Rig::computeMotionAnimationState(float deltaTime, const glm::vec3& worldPos
|
|||
}
|
||||
}
|
||||
};
|
||||
updateRole("walk", forwardSpeed > 0.01f);
|
||||
updateRole("backup", forwardSpeed < -0.01f);
|
||||
bool isTurning = std::abs(rightTurningSpeed) > 0.5f;
|
||||
updateRole("rightTurn", isTurning && (rightTurningSpeed > 0));
|
||||
updateRole("leftTurn", isTurning && (rightTurningSpeed < 0));
|
||||
bool isStrafing = !isTurning && (std::abs(rightLateralSpeed) > 0.01f);
|
||||
updateRole("walk", forwardSpeed > PERCEPTIBLE_SPEED);
|
||||
updateRole("backup", forwardSpeed < -PERCEPTIBLE_SPEED);
|
||||
updateRole("rightTurn", isTurning && (rightTurningSpeed > 0.0f));
|
||||
updateRole("leftTurn", isTurning && (rightTurningSpeed < 0.0f));
|
||||
isStrafing = isStrafing && !isMoving;
|
||||
updateRole("rightStrafe", isStrafing && (rightLateralSpeed > 0.0f));
|
||||
updateRole("leftStrafe", isStrafing && (rightLateralSpeed < 0.0f));
|
||||
updateRole("leftStrafe", isStrafing && (rightLateralSpeed < 0.0f));
|
||||
updateRole("idle", !isMoving); // Must be last, as it makes isMoving bogus.
|
||||
_lastFront = front;
|
||||
_lastPosition = worldPosition;
|
||||
|
|
|
@ -313,7 +313,7 @@ AudioInjector* AudioInjector::playSound(const QString& soundUrl, const float vol
|
|||
|
||||
QByteArray samples = sound->getByteArray();
|
||||
if (stretchFactor == 1.0f) {
|
||||
return playSound(samples, options, NULL);
|
||||
return playSoundAndDelete(samples, options, NULL);
|
||||
}
|
||||
|
||||
soxr_io_spec_t spec = soxr_io_spec(SOXR_INT16_I, SOXR_INT16_I);
|
||||
|
@ -333,9 +333,16 @@ AudioInjector* AudioInjector::playSound(const QString& soundUrl, const float vol
|
|||
qCDebug(audio) << "Unable to resample" << soundUrl << "from" << nInputSamples << "@" << standardRate << "to" << nOutputSamples << "@" << resampledRate;
|
||||
resampled = samples;
|
||||
}
|
||||
return playSound(resampled, options, NULL);
|
||||
return playSoundAndDelete(resampled, options, NULL);
|
||||
}
|
||||
|
||||
AudioInjector* AudioInjector::playSoundAndDelete(const QByteArray& buffer, const AudioInjectorOptions options, AbstractAudioInterface* localInterface) {
|
||||
AudioInjector* sound = playSound(buffer, options, localInterface);
|
||||
sound->triggerDeleteAfterFinish();
|
||||
return sound;
|
||||
}
|
||||
|
||||
|
||||
AudioInjector* AudioInjector::playSound(const QByteArray& buffer, const AudioInjectorOptions options, AbstractAudioInterface* localInterface) {
|
||||
QThread* injectorThread = new QThread();
|
||||
injectorThread->setObjectName("Audio Injector Thread");
|
||||
|
|
|
@ -46,6 +46,7 @@ public:
|
|||
|
||||
void setLocalAudioInterface(AbstractAudioInterface* localAudioInterface) { _localAudioInterface = localAudioInterface; }
|
||||
|
||||
static AudioInjector* playSoundAndDelete(const QByteArray& buffer, const AudioInjectorOptions options, AbstractAudioInterface* localInterface);
|
||||
static AudioInjector* playSound(const QByteArray& buffer, const AudioInjectorOptions options, AbstractAudioInterface* localInterface);
|
||||
static AudioInjector* playSound(const QString& soundUrl, const float volume, const float stretchFactor, const glm::vec3 position);
|
||||
|
||||
|
|
|
@ -114,12 +114,14 @@ float HandData::getBaseScale() const {
|
|||
}
|
||||
|
||||
glm::vec3 PalmData::getFingerDirection() const {
|
||||
const glm::vec3 LOCAL_FINGER_DIRECTION(0.0f, 0.0f, 1.0f);
|
||||
// finger points along yAxis in hand-frame
|
||||
const glm::vec3 LOCAL_FINGER_DIRECTION(0.0f, 1.0f, 0.0f);
|
||||
return glm::normalize(_owningHandData->localToWorldDirection(_rawRotation * LOCAL_FINGER_DIRECTION));
|
||||
}
|
||||
|
||||
glm::vec3 PalmData::getNormal() const {
|
||||
const glm::vec3 LOCAL_PALM_DIRECTION(0.0f, -1.0f, 0.0f);
|
||||
// palm normal points along zAxis in hand-frame
|
||||
const glm::vec3 LOCAL_PALM_DIRECTION(0.0f, 0.0f, 1.0f);
|
||||
return glm::normalize(_owningHandData->localToWorldDirection(_rawRotation * LOCAL_PALM_DIRECTION));
|
||||
}
|
||||
|
||||
|
|
|
@ -64,12 +64,13 @@ public:
|
|||
bool findSpherePenetration(const glm::vec3& penetratorCenter, float penetratorRadius, glm::vec3& penetration,
|
||||
const PalmData*& collidingPalm) const;
|
||||
|
||||
glm::quat getBaseOrientation() const;
|
||||
|
||||
friend class AvatarData;
|
||||
protected:
|
||||
AvatarData* _owningAvatarData;
|
||||
std::vector<PalmData> _palms;
|
||||
|
||||
glm::quat getBaseOrientation() const;
|
||||
glm::vec3 getBasePosition() const;
|
||||
float getBaseScale() const;
|
||||
|
||||
|
@ -95,6 +96,7 @@ public:
|
|||
|
||||
void setRawRotation(const glm::quat rawRotation) { _rawRotation = rawRotation; };
|
||||
glm::quat getRawRotation() const { return _rawRotation; }
|
||||
glm::quat getRotation() const { return _owningHandData->getBaseOrientation() * _rawRotation; }
|
||||
void setRawPosition(const glm::vec3& pos) { _rawPosition = pos; }
|
||||
void setRawVelocity(const glm::vec3& velocity) { _rawVelocity = velocity; }
|
||||
const glm::vec3& getRawVelocity() const { return _rawVelocity; }
|
||||
|
@ -147,6 +149,7 @@ public:
|
|||
glm::vec3 getNormal() const;
|
||||
|
||||
private:
|
||||
// unless marked otherwise, these are all in the model-frame
|
||||
glm::quat _rawRotation;
|
||||
glm::vec3 _rawPosition;
|
||||
glm::vec3 _rawVelocity;
|
||||
|
@ -156,6 +159,7 @@ private:
|
|||
glm::vec3 _tipPosition;
|
||||
glm::vec3 _tipVelocity;
|
||||
glm::vec3 _totalPenetration; // accumulator for per-frame penetrations
|
||||
|
||||
unsigned int _controllerButtons;
|
||||
unsigned int _lastControllerButtons;
|
||||
float _trigger;
|
||||
|
|
|
@ -57,6 +57,11 @@ public:
|
|||
|
||||
// Rendering support
|
||||
|
||||
// Stop requesting renders, but don't do full deactivation
|
||||
// needed to work around the issues caused by Oculus
|
||||
// processing messages in the middle of submitFrame
|
||||
virtual void stop() = 0;
|
||||
|
||||
/**
|
||||
* Called by the application before the frame rendering. Can be used for
|
||||
* render timing related calls (for instance, the Oculus begin frame timing
|
||||
|
@ -120,6 +125,7 @@ public:
|
|||
virtual void resetSensors() {}
|
||||
virtual float devicePixelRatio() { return 1.0; }
|
||||
|
||||
|
||||
static const QString& MENU_PATH();
|
||||
signals:
|
||||
void recommendedFramebufferSizeChanged(const QSize & size);
|
||||
|
|
|
@ -30,3 +30,4 @@ void NullDisplayPlugin::finishFrame() {}
|
|||
|
||||
void NullDisplayPlugin::activate() {}
|
||||
void NullDisplayPlugin::deactivate() {}
|
||||
void NullDisplayPlugin::stop() {}
|
||||
|
|
|
@ -17,6 +17,7 @@ public:
|
|||
|
||||
void activate() override;
|
||||
void deactivate() override;
|
||||
void stop() override;
|
||||
|
||||
virtual glm::uvec2 getRecommendedRenderSize() const override;
|
||||
virtual bool hasFocus() const override;
|
||||
|
|
|
@ -16,7 +16,9 @@
|
|||
|
||||
OpenGLDisplayPlugin::OpenGLDisplayPlugin() {
|
||||
connect(&_timer, &QTimer::timeout, this, [&] {
|
||||
emit requestRender();
|
||||
if (_active) {
|
||||
emit requestRender();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -56,10 +58,17 @@ void OpenGLDisplayPlugin::customizeContext() {
|
|||
}
|
||||
|
||||
void OpenGLDisplayPlugin::activate() {
|
||||
_active = true;
|
||||
_timer.start(1);
|
||||
}
|
||||
|
||||
void OpenGLDisplayPlugin::stop() {
|
||||
_active = false;
|
||||
_timer.stop();
|
||||
}
|
||||
|
||||
void OpenGLDisplayPlugin::deactivate() {
|
||||
_active = false;
|
||||
_timer.stop();
|
||||
|
||||
makeCurrent();
|
||||
|
|
|
@ -25,7 +25,7 @@ public:
|
|||
|
||||
virtual void activate() override;
|
||||
virtual void deactivate() override;
|
||||
|
||||
virtual void stop() override;
|
||||
virtual bool eventFilter(QObject* receiver, QEvent* event) override;
|
||||
|
||||
virtual void display(GLuint sceneTexture, const glm::uvec2& sceneSize) override;
|
||||
|
@ -44,6 +44,7 @@ protected:
|
|||
mutable QTimer _timer;
|
||||
ProgramPtr _program;
|
||||
ShapeWrapperPtr _plane;
|
||||
bool _active{ false };
|
||||
bool _vsyncSupported{ false };
|
||||
};
|
||||
|
||||
|
|
|
@ -325,19 +325,18 @@ void OculusDisplayPlugin::customizeContext() {
|
|||
//_texture = DependencyManager::get<TextureCache>()->
|
||||
// getImageTexture(PathUtils::resourcesPath() + "/images/cube_texture.png");
|
||||
uvec2 mirrorSize = toGlm(_window->geometry().size());
|
||||
_mirrorFbo = MirrorFboPtr(new MirrorFramebufferWrapper(_hmd));
|
||||
_mirrorFbo->Init(mirrorSize);
|
||||
|
||||
_sceneFbo = SwapFboPtr(new SwapFramebufferWrapper(_hmd));
|
||||
_sceneFbo->Init(getRecommendedRenderSize());
|
||||
#endif
|
||||
enableVsync(false);
|
||||
isVsyncEnabled();
|
||||
}
|
||||
|
||||
void OculusDisplayPlugin::deactivate() {
|
||||
#if (OVR_MAJOR_VERSION >= 6)
|
||||
makeCurrent();
|
||||
_sceneFbo.reset();
|
||||
_mirrorFbo.reset();
|
||||
doneCurrent();
|
||||
PerformanceTimer::setActive(false);
|
||||
|
||||
|
@ -350,11 +349,6 @@ void OculusDisplayPlugin::deactivate() {
|
|||
}
|
||||
|
||||
void OculusDisplayPlugin::display(GLuint finalTexture, const glm::uvec2& sceneSize) {
|
||||
static bool inDisplay = false;
|
||||
if (inDisplay) {
|
||||
return;
|
||||
}
|
||||
inDisplay = true;
|
||||
#if (OVR_MAJOR_VERSION >= 6)
|
||||
using namespace oglplus;
|
||||
// Need to make sure only the display plugin is responsible for
|
||||
|
@ -383,12 +377,14 @@ void OculusDisplayPlugin::display(GLuint finalTexture, const glm::uvec2& sceneSi
|
|||
the UI visible in the output window (unlikely). This should be done before
|
||||
_sceneFbo->Increment or we're be using the wrong texture
|
||||
*/
|
||||
_sceneFbo->Bound(Framebuffer::Target::Read, [&] {
|
||||
glBlitFramebuffer(
|
||||
0, 0, _sceneFbo->size.x, _sceneFbo->size.y,
|
||||
0, 0, windowSize.x, windowSize.y,
|
||||
GL_COLOR_BUFFER_BIT, GL_NEAREST);
|
||||
});
|
||||
if (_enableMirror) {
|
||||
_sceneFbo->Bound(Framebuffer::Target::Read, [&] {
|
||||
glBlitFramebuffer(
|
||||
0, 0, _sceneFbo->size.x, _sceneFbo->size.y,
|
||||
0, 0, windowSize.x, windowSize.y,
|
||||
GL_COLOR_BUFFER_BIT, GL_NEAREST);
|
||||
});
|
||||
}
|
||||
|
||||
{
|
||||
PerformanceTimer("OculusSubmit");
|
||||
|
@ -409,6 +405,7 @@ void OculusDisplayPlugin::display(GLuint finalTexture, const glm::uvec2& sceneSi
|
|||
The other alternative for mirroring is to use the Oculus mirror texture support, which
|
||||
will contain the post-distorted and fully composited scene regardless of how many layers
|
||||
we send.
|
||||
Currently generates an error.
|
||||
*/
|
||||
//auto mirrorSize = _mirrorFbo->size;
|
||||
//_mirrorFbo->Bound(Framebuffer::Target::Read, [&] {
|
||||
|
@ -420,21 +417,10 @@ void OculusDisplayPlugin::display(GLuint finalTexture, const glm::uvec2& sceneSi
|
|||
|
||||
++_frameIndex;
|
||||
#endif
|
||||
inDisplay = false;
|
||||
}
|
||||
|
||||
// Pass input events on to the application
|
||||
bool OculusDisplayPlugin::eventFilter(QObject* receiver, QEvent* event) {
|
||||
#if (OVR_MAJOR_VERSION >= 6)
|
||||
if (event->type() == QEvent::Resize) {
|
||||
QResizeEvent* resizeEvent = static_cast<QResizeEvent*>(event);
|
||||
qDebug() << resizeEvent->size().width() << " x " << resizeEvent->size().height();
|
||||
auto newSize = toGlm(resizeEvent->size());
|
||||
makeCurrent();
|
||||
_mirrorFbo->Resize(newSize);
|
||||
doneCurrent();
|
||||
}
|
||||
#endif
|
||||
return WindowOpenGLDisplayPlugin::eventFilter(receiver, event);
|
||||
}
|
||||
|
||||
|
@ -444,7 +430,9 @@ bool OculusDisplayPlugin::eventFilter(QObject* receiver, QEvent* event) {
|
|||
otherwise the swapbuffer delay will interefere with the framerate of the headset
|
||||
*/
|
||||
void OculusDisplayPlugin::finishFrame() {
|
||||
//swapBuffers();
|
||||
if (_enableMirror) {
|
||||
swapBuffers();
|
||||
}
|
||||
doneCurrent();
|
||||
};
|
||||
|
||||
|
|
|
@ -58,6 +58,7 @@ private:
|
|||
mat4 _compositeEyeProjections[2];
|
||||
uvec2 _desiredFramebufferSize;
|
||||
ovrTrackingState _trackingState;
|
||||
bool _enableMirror{ false };
|
||||
|
||||
#if (OVR_MAJOR_VERSION >= 6)
|
||||
ovrHmd _hmd;
|
||||
|
@ -70,7 +71,6 @@ private:
|
|||
ovrLayerEyeFov& getSceneLayer();
|
||||
ovrHmdDesc _hmdDesc;
|
||||
SwapFboPtr _sceneFbo;
|
||||
MirrorFboPtr _mirrorFbo;
|
||||
ovrLayerEyeFov _sceneLayer;
|
||||
#endif
|
||||
#if (OVR_MAJOR_VERSION == 7)
|
||||
|
|
|
@ -2677,8 +2677,21 @@ FBXGeometry* extractFBXGeometry(const FBXNode& node, const QVariantHash& mapping
|
|||
foreach (const glm::vec3& vertex, extracted.mesh.vertices) {
|
||||
averageRadius += glm::distance(vertex, averageVertex);
|
||||
}
|
||||
jointShapeInfo.averageRadius = averageRadius * radiusScale;
|
||||
jointShapeInfo.averageRadius = averageRadius * radiusScale / (float)jointShapeInfo.numVertices;
|
||||
}
|
||||
|
||||
// BUG: the boneBegin and/or boneEnd are incorrect for meshes that are "connected
|
||||
// under the bone" without weights. Unfortunately we haven't been able to find it yet.
|
||||
// Although the the mesh vertices are correct in the model-frame, the joint's transform
|
||||
// in the same frame is just BAD.
|
||||
//
|
||||
// HACK WORKAROUND: prevent these shapes from contributing to the collision capsule by setting
|
||||
// some key members of jointShapeInfo to zero:
|
||||
jointShapeInfo.numVertices = 0;
|
||||
jointShapeInfo.sumVertexWeights = 0.0f;
|
||||
jointShapeInfo.numVertexWeights = 0;
|
||||
jointShapeInfo.boneBegin = glm::vec3(0.0f);
|
||||
jointShapeInfo.averageRadius = 0.0f;
|
||||
}
|
||||
extracted.mesh.isEye = (maxJointIndex == geometry.leftEyeJointIndex || maxJointIndex == geometry.rightEyeJointIndex);
|
||||
|
||||
|
@ -2728,7 +2741,6 @@ FBXGeometry* extractFBXGeometry(const FBXNode& node, const QVariantHash& mapping
|
|||
// the average radius to the average point.
|
||||
if (jointShapeInfo.numVertexWeights == 0
|
||||
&& jointShapeInfo.numVertices > 0) {
|
||||
jointShapeInfo.averageRadius /= (float)jointShapeInfo.numVertices;
|
||||
joint.boneRadius = jointShapeInfo.averageRadius;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -34,6 +34,8 @@ Q_LOGGING_CATEGORY(inputplugins, "hifi.inputplugins")
|
|||
const unsigned int LEFT_MASK = 0;
|
||||
const unsigned int RIGHT_MASK = 1U << 1;
|
||||
|
||||
#ifdef HAVE_SIXENSE
|
||||
|
||||
const int CALIBRATION_STATE_IDLE = 0;
|
||||
const int CALIBRATION_STATE_X = 1;
|
||||
const int CALIBRATION_STATE_Y = 2;
|
||||
|
@ -47,11 +49,15 @@ const float NECK_Z = 0.3f; // meters
|
|||
|
||||
const float CONTROLLER_THRESHOLD = 0.35f;
|
||||
|
||||
#endif
|
||||
|
||||
#ifdef __APPLE__
|
||||
typedef int (*SixenseBaseFunction)();
|
||||
typedef int (*SixenseTakeIntFunction)(int);
|
||||
#ifdef HAVE_SIXENSE
|
||||
typedef int (*SixenseTakeIntAndSixenseControllerData)(int, sixenseControllerData*);
|
||||
#endif
|
||||
#endif
|
||||
|
||||
const QString SixenseManager::NAME = "Sixense";
|
||||
|
||||
|
@ -475,6 +481,7 @@ void SixenseManager::handlePoseEvent(glm::vec3 position, glm::quat rotation, int
|
|||
// Qsh = angleAxis(PI, zAxis) * angleAxis(-PI/2, xAxis)
|
||||
//
|
||||
const glm::vec3 xAxis = glm::vec3(1.0f, 0.0f, 0.0f);
|
||||
const glm::vec3 yAxis = glm::vec3(0.0f, 1.0f, 0.0f);
|
||||
const glm::vec3 zAxis = glm::vec3(0.0f, 0.0f, 1.0f);
|
||||
const glm::quat sixenseToHand = glm::angleAxis(PI, zAxis) * glm::angleAxis(-PI/2.0f, xAxis);
|
||||
|
||||
|
@ -485,13 +492,15 @@ void SixenseManager::handlePoseEvent(glm::vec3 position, glm::quat rotation, int
|
|||
const glm::quat preOffset = glm::angleAxis(sign * PI / 2.0f, zAxis);
|
||||
|
||||
// Finally, there is a post-offset (same for both hands) to get the hand's rest orientation
|
||||
// (fingers forward, palm down) aligned properly in the avatar's model-frame.
|
||||
const glm::quat postOffset = glm::angleAxis(PI / 2.0f, xAxis);
|
||||
// (fingers forward, palm down) aligned properly in the avatar's model-frame,
|
||||
// and then a flip about the yAxis to get into model-frame.
|
||||
const glm::quat postOffset = glm::angleAxis(PI, yAxis) * glm::angleAxis(PI / 2.0f, xAxis);
|
||||
|
||||
// The total rotation of the hand uses the formula:
|
||||
//
|
||||
// rotation = postOffset * Qsh^ * (measuredRotation * preOffset) * Qsh
|
||||
//
|
||||
// TODO: find a shortcut with fewer rotations.
|
||||
rotation = postOffset * glm::inverse(sixenseToHand) * rotation * preOffset * sixenseToHand;
|
||||
|
||||
_poseStateMap[makeInput(JointChannel(index)).getChannel()] = UserInputMapper::PoseValue(position, rotation);
|
||||
|
|
|
@ -362,18 +362,17 @@ void ViveControllerManager::handlePoseEvent(const mat4& mat, int index) {
|
|||
//
|
||||
// Qoffset = glm::inverse(deltaRotation when hand is posed fingers forward, palm down)
|
||||
//
|
||||
// An approximate offset for the Vive can be obtained by inpection:
|
||||
// An approximate offset for the Vive can be obtained by inspection:
|
||||
//
|
||||
// Qoffset = glm::inverse(glm::angleAxis(sign * PI/4.0f, zAxis) * glm::angleAxis(PI/2.0f, xAxis))
|
||||
//
|
||||
|
||||
// Finally there is another flip around the yAxis to re-align from model to Vive space, so the full equation is:
|
||||
// So the full equation is:
|
||||
//
|
||||
// Q = yFlip * combinedMeasurement * viveToHand
|
||||
// Q = combinedMeasurement * viveToHand
|
||||
//
|
||||
// Q = yFlip * (deltaQ * QOffset) * (yFlip * quarterTurnAboutX)
|
||||
// Q = (deltaQ * QOffset) * (yFlip * quarterTurnAboutX)
|
||||
//
|
||||
// Q = yFlip * (deltaQ * inverse(deltaQForAlignedHand)) * (yFlip * quarterTurnAboutX)
|
||||
// Q = (deltaQ * inverse(deltaQForAlignedHand)) * (yFlip * quarterTurnAboutX)
|
||||
|
||||
const glm::quat quarterX = glm::angleAxis(PI / 2.0f, glm::vec3(1.0f, 0.0f, 0.0f));
|
||||
const glm::quat yFlip = glm::angleAxis(PI, glm::vec3(0.0f, 1.0f, 0.0f));
|
||||
|
@ -381,7 +380,7 @@ void ViveControllerManager::handlePoseEvent(const mat4& mat, int index) {
|
|||
const glm::quat signedQuaterZ = glm::angleAxis(sign * PI / 2.0f, glm::vec3(0.0f, 0.0f, 1.0f));
|
||||
const glm::quat eighthX = glm::angleAxis(PI / 4.0f, glm::vec3(1.0f, 0.0f, 0.0f));
|
||||
const glm::quat offset = glm::inverse(signedQuaterZ * eighthX);
|
||||
rotation = yFlip * rotation * offset * yFlip * quarterX;
|
||||
rotation = rotation * offset * yFlip * quarterX;
|
||||
|
||||
position += rotation * glm::vec3(0, 0, -CONTROLLER_LENGTH_OFFSET);
|
||||
|
||||
|
|
27
libraries/shared/src/Finally.h
Normal file
27
libraries/shared/src/Finally.h
Normal file
|
@ -0,0 +1,27 @@
|
|||
//
|
||||
// Created by Bradley Austin Davis on 2015/09/01
|
||||
// Copyright 2013-2105 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
|
||||
//
|
||||
|
||||
// Simulates a java finally block by executing a lambda when an instance leaves
|
||||
// scope
|
||||
|
||||
#include <functional>
|
||||
|
||||
#pragma once
|
||||
#ifndef hifi_Finally_h
|
||||
#define hifi_Finally_h
|
||||
|
||||
class Finally {
|
||||
public:
|
||||
template <typename F>
|
||||
Finally(F f) : _f(f) {}
|
||||
~Finally() { _f(); }
|
||||
private:
|
||||
std::function<void()> _f;
|
||||
};
|
||||
|
||||
#endif
|
|
@ -197,9 +197,23 @@ QScriptValue xColorToScriptValue(QScriptEngine *engine, const xColor& color) {
|
|||
}
|
||||
|
||||
void xColorFromScriptValue(const QScriptValue &object, xColor& color) {
|
||||
color.red = object.property("red").toVariant().toInt();
|
||||
color.green = object.property("green").toVariant().toInt();
|
||||
color.blue = object.property("blue").toVariant().toInt();
|
||||
if (!object.isValid()) {
|
||||
return;
|
||||
}
|
||||
if (object.isNumber()) {
|
||||
color.red = color.green = color.blue = (uint8_t)object.toUInt32();
|
||||
} else if (object.isString()) {
|
||||
QColor qcolor(object.toString());
|
||||
if (qcolor.isValid()) {
|
||||
color.red = (uint8_t)qcolor.red();
|
||||
color.blue = (uint8_t)qcolor.blue();
|
||||
color.green = (uint8_t)qcolor.green();
|
||||
}
|
||||
} else {
|
||||
color.red = object.property("red").toVariant().toInt();
|
||||
color.green = object.property("green").toVariant().toInt();
|
||||
color.blue = object.property("blue").toVariant().toInt();
|
||||
}
|
||||
}
|
||||
|
||||
QScriptValue qColorToScriptValue(QScriptEngine* engine, const QColor& color) {
|
||||
|
|
Loading…
Reference in a new issue