diff --git a/examples/controllers/breakdanceToy.js b/examples/controllers/breakdanceToy.js new file mode 100644 index 0000000000..db3b418c01 --- /dev/null +++ b/examples/controllers/breakdanceToy.js @@ -0,0 +1,582 @@ +// +// breakdanceToy.js +// examples +// +// Created by Brad Hefta-Gaub on August 24, 2015 +// 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/"; + +// helpers +// Computes the penetration between a point and a sphere (centered at the origin) +// if point is inside sphere: returns true and stores the result in 'penetration' +// (the vector that would move the point outside the sphere) +// otherwise returns false +function findSphereHit(point, sphereRadius) { + var EPSILON = 0.000001; //smallish positive number - used as margin of error for some computations + var vectorLength = Vec3.length(point); + if (vectorLength < EPSILON) { + return true; + } + var distance = vectorLength - sphereRadius; + if (distance < 0.0) { + return true; + } + return false; +} + +function findSpherePointHit(sphereCenter, sphereRadius, point) { + return findSphereHit(Vec3.subtract(point,sphereCenter), sphereRadius); +} + +function findSphereSphereHit(firstCenter, firstRadius, secondCenter, secondRadius) { + return findSpherePointHit(firstCenter, firstRadius + secondRadius, secondCenter); +} + + +function getPositionPuppet() { + var DISTANCE_IN_FRONT = 2; + var DISTANCE_UP = 0.4; + var DISTANCE_TO_SIDE = 0.0; + + var up = Quat.getUp(MyAvatar.orientation); + var front = Quat.getFront(MyAvatar.orientation); + var right = Quat.getRight(MyAvatar.orientation); + var left = Vec3.multiply(right, -1); + + var upOffset = Vec3.multiply(up, DISTANCE_UP); + var leftOffset = Vec3.multiply(left, DISTANCE_TO_SIDE); + var frontOffset = Vec3.multiply(front, DISTANCE_IN_FRONT); + + var offset = Vec3.sum(Vec3.sum(leftOffset, frontOffset), upOffset); + var position = Vec3.sum(MyAvatar.position, offset); + return position; +} + + + +function getPositionLeftFront() { + var DISTANCE_IN_FRONT = 0.6; + var DISTANCE_UP = 0.4; + var DISTANCE_TO_SIDE = 0.3; + + var up = Quat.getUp(MyAvatar.orientation); + var front = Quat.getFront(MyAvatar.orientation); + var right = Quat.getRight(MyAvatar.orientation); + var left = Vec3.multiply(right, -1); + + var upOffset = Vec3.multiply(up, DISTANCE_UP); + var leftOffset = Vec3.multiply(left, DISTANCE_TO_SIDE); + var frontOffset = Vec3.multiply(front, DISTANCE_IN_FRONT); + + var offset = Vec3.sum(Vec3.sum(leftOffset, frontOffset), upOffset); + var position = Vec3.sum(MyAvatar.position, offset); + return position; +} + +function getPositionLeftSide() { + var DISTANCE_IN_FRONT = 0.0; + var DISTANCE_UP = 0.5; + var DISTANCE_TO_SIDE = 0.9; + + var up = Quat.getUp(MyAvatar.orientation); + var front = Quat.getFront(MyAvatar.orientation); + var right = Quat.getRight(MyAvatar.orientation); + var left = Vec3.multiply(right, -1); + + var upOffset = Vec3.multiply(up, DISTANCE_UP); + var leftOffset = Vec3.multiply(left, DISTANCE_TO_SIDE); + var frontOffset = Vec3.multiply(front, DISTANCE_IN_FRONT); + + var offset = Vec3.sum(Vec3.sum(leftOffset, frontOffset), upOffset); + var position = Vec3.sum(MyAvatar.position, offset); + return position; +} + +function getPositionLeftOverhead() { + var DISTANCE_IN_FRONT = 0.2; + var DISTANCE_UP = 1; + var DISTANCE_TO_SIDE = 0.3; + + var up = Quat.getUp(MyAvatar.orientation); + var front = Quat.getFront(MyAvatar.orientation); + var right = Quat.getRight(MyAvatar.orientation); + var left = Vec3.multiply(right, -1); + + var upOffset = Vec3.multiply(up, DISTANCE_UP); + var leftOffset = Vec3.multiply(left, DISTANCE_TO_SIDE); + var frontOffset = Vec3.multiply(front, DISTANCE_IN_FRONT); + + var offset = Vec3.sum(Vec3.sum(leftOffset, frontOffset), upOffset); + var position = Vec3.sum(MyAvatar.position, offset); + return position; +} + +function getPositionLeftLowered() { + var DISTANCE_IN_FRONT = 0.2; + var DISTANCE_DOWN = 0.1; + var DISTANCE_TO_SIDE = 0.3; + + var up = Quat.getUp(MyAvatar.orientation); + var front = Quat.getFront(MyAvatar.orientation); + var right = Quat.getRight(MyAvatar.orientation); + var left = Vec3.multiply(right, -1); + + var downOffset = Vec3.multiply(up, DISTANCE_DOWN); + var leftOffset = Vec3.multiply(left, DISTANCE_TO_SIDE); + var frontOffset = Vec3.multiply(front, DISTANCE_IN_FRONT); + + var offset = Vec3.sum(Vec3.sum(leftOffset, frontOffset), downOffset ); + var position = Vec3.sum(MyAvatar.position, offset); + return position; +} + +function getPositionLeftOnBase() { + var DISTANCE_IN_FRONT = 0.2; + var DISTANCE_DOWN = -0.4; + var DISTANCE_TO_SIDE = 0.3; + + var up = Quat.getUp(MyAvatar.orientation); + var front = Quat.getFront(MyAvatar.orientation); + var right = Quat.getRight(MyAvatar.orientation); + var left = Vec3.multiply(right, -1); + + var downOffset = Vec3.multiply(up, DISTANCE_DOWN); + var leftOffset = Vec3.multiply(left, DISTANCE_TO_SIDE); + var frontOffset = Vec3.multiply(front, DISTANCE_IN_FRONT); + + var offset = Vec3.sum(Vec3.sum(leftOffset, frontOffset), downOffset ); + var position = Vec3.sum(MyAvatar.position, offset); + return position; +} + +function getPositionRightFront() { + var DISTANCE_IN_FRONT = 0.6; + var DISTANCE_UP = 0.4; + var DISTANCE_TO_SIDE = 0.3; + + var up = Quat.getUp(MyAvatar.orientation); + var front = Quat.getFront(MyAvatar.orientation); + var right = Quat.getRight(MyAvatar.orientation); + + var upOffset = Vec3.multiply(up, DISTANCE_UP); + var rightOffset = Vec3.multiply(right, DISTANCE_TO_SIDE); + var frontOffset = Vec3.multiply(front, DISTANCE_IN_FRONT); + + var offset = Vec3.sum(Vec3.sum(rightOffset, frontOffset), upOffset); + var position = Vec3.sum(MyAvatar.position, offset); + return position; +} + +function getPositionRightSide() { + var DISTANCE_IN_FRONT = 0.0; + var DISTANCE_UP = 0.5; + var DISTANCE_TO_SIDE = 0.9; + + var up = Quat.getUp(MyAvatar.orientation); + var front = Quat.getFront(MyAvatar.orientation); + var right = Quat.getRight(MyAvatar.orientation); + + var upOffset = Vec3.multiply(up, DISTANCE_UP); + var rightOffset = Vec3.multiply(right, DISTANCE_TO_SIDE); + var frontOffset = Vec3.multiply(front, DISTANCE_IN_FRONT); + + var offset = Vec3.sum(Vec3.sum(rightOffset, frontOffset), upOffset); + var position = Vec3.sum(MyAvatar.position, offset); + return position; +} + +function getPositionRightOverhead() { + var DISTANCE_IN_FRONT = 0.2; + var DISTANCE_UP = 1; + var DISTANCE_TO_SIDE = 0.3; + + var up = Quat.getUp(MyAvatar.orientation); + var front = Quat.getFront(MyAvatar.orientation); + var right = Quat.getRight(MyAvatar.orientation); + + var upOffset = Vec3.multiply(up, DISTANCE_UP); + var rightOffset = Vec3.multiply(right, DISTANCE_TO_SIDE); + var frontOffset = Vec3.multiply(front, DISTANCE_IN_FRONT); + + var offset = Vec3.sum(Vec3.sum(rightOffset, frontOffset), upOffset); + var position = Vec3.sum(MyAvatar.position, offset); + return position; +} + +function getPositionRightLowered() { + var DISTANCE_IN_FRONT = 0.2; + var DISTANCE_DOWN = 0.1; + var DISTANCE_TO_SIDE = 0.3; + + var up = Quat.getUp(MyAvatar.orientation); + var front = Quat.getFront(MyAvatar.orientation); + var right = Quat.getRight(MyAvatar.orientation); + + var downOffset = Vec3.multiply(up, DISTANCE_DOWN); + var rightOffset = Vec3.multiply(right, DISTANCE_TO_SIDE); + var frontOffset = Vec3.multiply(front, DISTANCE_IN_FRONT); + + var offset = Vec3.sum(Vec3.sum(rightOffset, frontOffset), downOffset ); + var position = Vec3.sum(MyAvatar.position, offset); + return position; +} + +function getPositionRightOnBase() { + var DISTANCE_IN_FRONT = 0.2; + var DISTANCE_DOWN = -0.4; + var DISTANCE_TO_SIDE = 0.3; + + var up = Quat.getUp(MyAvatar.orientation); + var front = Quat.getFront(MyAvatar.orientation); + var right = Quat.getRight(MyAvatar.orientation); + + var downOffset = Vec3.multiply(up, DISTANCE_DOWN); + var rightOffset = Vec3.multiply(right, DISTANCE_TO_SIDE); + var frontOffset = Vec3.multiply(front, DISTANCE_IN_FRONT); + + var offset = Vec3.sum(Vec3.sum(rightOffset, frontOffset), downOffset ); + var position = Vec3.sum(MyAvatar.position, offset); + return position; +} + + +// We will also demonstrate some 3D overlays. We will create a couple of cubes, spheres, and lines +// our 3D cube that moves around... +var handSize = 0.25; +var leftCubePosition = MyAvatar.getLeftPalmPosition(); +var rightCubePosition = MyAvatar.getRightPalmPosition(); + +var text = Overlays.addOverlay("text", { + x: 100, + y: 300, + width: 900, + height: 50, + backgroundColor: { red: 0, green: 0, blue: 0}, + color: { red: 255, green: 255, blue: 255}, + topMargin: 4, + leftMargin: 4, + text: "POSE...", + alpha: 1, + backgroundAlpha: 0.5 + }); + +var leftHand= Overlays.addOverlay("cube", { + position: leftCubePosition, + size: handSize, + color: { red: 0, green: 0, blue: 255}, + alpha: 1, + solid: false + }); + +var rightHand= Overlays.addOverlay("cube", { + position: rightCubePosition, + size: handSize, + color: { red: 255, green: 0, blue: 0}, + alpha: 1, + solid: false + }); + + +var targetSize = 0.3; +var targetColor = { red: 128, green: 128, blue: 128}; +var targetColorHit = { red: 0, green: 255, blue: 0}; +var moveCycleColor = { red: 255, green: 255, blue: 0}; + +var TEMPORARY_LIFETIME = 60; + +var animationSettings = JSON.stringify({ + fps: 30, + running: true, + loop: true, + firstFrame: 1, + lastFrame: 10000 +}); + +var naturalDimensions = { x: 1.63, y: 1.67, z: 0.31 }; +var dimensions = Vec3.multiply(naturalDimensions, 0.3); + +var puppetEntityID = Entities.addEntity({ + type: "Model", + modelURL: "https://hifi-public.s3.amazonaws.com/models/Bboys/bboy1/bboy1.fbx", + animationURL: "http://s3.amazonaws.com/hifi-public/animations/Breakdancing/breakdance_ready.fbx", + animationSettings: animationSettings, + position: getPositionPuppet(), + ignoreForCollisions: true, + dimensions: dimensions, + lifetime: TEMPORARY_LIFETIME + }); + +var leftOnBase = Overlays.addOverlay("cube", { + position: getPositionLeftOnBase(), + size: targetSize, + color: targetColor, + alpha: 1, + solid: false + }); + + +var leftLowered = Overlays.addOverlay("cube", { + position: getPositionLeftLowered(), + size: targetSize, + color: targetColor, + alpha: 1, + solid: false + }); + + +var leftOverhead = Overlays.addOverlay("cube", { + position: getPositionLeftOverhead(), + size: targetSize, + color: targetColor, + alpha: 1, + solid: false + }); + +var leftSide= Overlays.addOverlay("cube", { + position: getPositionLeftSide(), + size: targetSize, + color: targetColor, + alpha: 1, + solid: false + }); + + +var leftFront= Overlays.addOverlay("cube", { + position: getPositionLeftFront(), + size: targetSize, + color: targetColor, + alpha: 1, + solid: false + }); + +var rightOnBase = Overlays.addOverlay("cube", { + position: getPositionRightOnBase(), + size: targetSize, + color: targetColor, + alpha: 1, + solid: false + }); + +var rightLowered = Overlays.addOverlay("cube", { + position: getPositionRightLowered(), + size: targetSize, + color: targetColor, + alpha: 1, + solid: false + }); + + +var rightOverhead = Overlays.addOverlay("cube", { + position: getPositionRightOverhead(), + size: targetSize, + color: targetColor, + alpha: 1, + solid: false + }); + +var rightSide= Overlays.addOverlay("cube", { + position: getPositionRightSide(), + size: targetSize, + color: targetColor, + alpha: 1, + solid: false + }); + + +var rightFront= Overlays.addOverlay("cube", { + position: getPositionRightFront(), + size: targetSize, + color: targetColor, + alpha: 1, + solid: false + }); + +var startDate = new Date(); +var lastTime = startDate.getTime(); + +var NO_POSE = 0; +var LEFT_ON_BASE = 1; +var LEFT_OVERHEAD = 2; +var LEFT_LOWERED = 4; +var LEFT_SIDE = 8; +var LEFT_FRONT = 16; +var RIGHT_ON_BASE = 32; +var RIGHT_OVERHEAD = 64; +var RIGHT_LOWERED = 128; +var RIGHT_SIDE = 256; +var RIGHT_FRONT = 512; + +var lastPoseValue = NO_POSE; + +//http://s3.amazonaws.com/hifi-public/animations/Breakdancing/breakdance_ready.fbx +//http://s3.amazonaws.com/hifi-public/animations/Breakdancing/bboy_pose_to_idle.fbx +//http://s3.amazonaws.com/hifi-public/animations/Breakdancing/bboy_uprock.fbx +//http://s3.amazonaws.com/hifi-public/animations/Breakdancing/bboy_uprock_start.fbx +//http://s3.amazonaws.com/hifi-public/animations/Breakdancing/breakdance_footwork_1.fbx +//http://s3.amazonaws.com/hifi-public/animations/Breakdancing/breakdance_footwork_2.fbx +//http://s3.amazonaws.com/hifi-public/animations/Breakdancing/breakdance_footwork_3.fbx +//http://s3.amazonaws.com/hifi-public/animations/Breakdancing/breakdance_footwork_to_freeze.fbx +//http://s3.amazonaws.com/hifi-public/animations/Breakdancing/breakdance_footwork_to_idle.fbx +//http://s3.amazonaws.com/hifi-public/animations/Breakdancing/breakdance_freeze_var_1.fbx +//http://s3.amazonaws.com/hifi-public/animations/Breakdancing/breakdance_freeze_var_2.fbx +//http://s3.amazonaws.com/hifi-public/animations/Breakdancing/breakdance_freeze_var_3.fbx +//http://s3.amazonaws.com/hifi-public/animations/Breakdancing/breakdance_freeze_var_4.fbx +//http://s3.amazonaws.com/hifi-public/animations/Breakdancing/breakdance_freezes.fbx +//http://s3.amazonaws.com/hifi-public/animations/Breakdancing/breakdance_swipes.fbx +//http://s3.amazonaws.com/hifi-public/animations/Breakdancing/breakdance_uprock.fbx +//http://s3.amazonaws.com/hifi-public/animations/Breakdancing/breakdance_uprock_var_1.fbx +//http://s3.amazonaws.com/hifi-public/animations/Breakdancing/breakdance_uprock_var_1_end.fbx +//http://s3.amazonaws.com/hifi-public/animations/Breakdancing/breakdance_uprock_var_1_start.fbx +//http://s3.amazonaws.com/hifi-public/animations/Breakdancing/breakdance_uprock_var_2.fbx +//http://s3.amazonaws.com/hifi-public/animations/Breakdancing/flair.fbx + + +var poses = Array(); +/* +poses[0 ] = { name: "no pose", animation: "http://s3.amazonaws.com/hifi-public/animations/Breakdancing/breakdance_ready.fbx" }; +poses[LEFT_OVERHEAD ] = { name: "Left Overhead" , animation: "http://s3.amazonaws.com/hifi-public/animations/Breakdancing/breakdance_ready.fbx" }; +poses[LEFT_LOWERED ] = { name: "Left Lowered", animation: "http://s3.amazonaws.com/hifi-public/animations/Breakdancing/breakdance_ready.fbx" }; +poses[LEFT_SIDE ] = { name: "Left Side", animation: "http://s3.amazonaws.com/hifi-public/animations/Breakdancing/breakdance_ready.fbx" }; +poses[LEFT_FRONT ] = { name: "Left Front", animation: "http://s3.amazonaws.com/hifi-public/animations/Breakdancing/breakdance_ready.fbx" }; +poses[RIGHT_OVERHEAD ] = { name: "Right Overhead", animation: "http://s3.amazonaws.com/hifi-public/animations/Breakdancing/breakdance_ready.fbx" }; +poses[RIGHT_LOWERED ] = { name: "Right Lowered", animation: "http://s3.amazonaws.com/hifi-public/animations/Breakdancing/breakdance_ready.fbx" }; +poses[RIGHT_SIDE ] = { name: "Right Side", animation: "http://s3.amazonaws.com/hifi-public/animations/Breakdancing/breakdance_ready.fbx" }; +poses[RIGHT_FRONT ] = { name: "Right Front", animation: "http://s3.amazonaws.com/hifi-public/animations/Breakdancing/breakdance_ready.fbx" }; +*/ + +poses[LEFT_ON_BASE + RIGHT_ON_BASE ] = { name: "Left On Base + Right On Base", animation: "http://s3.amazonaws.com/hifi-public/animations/Breakdancing/breakdance_ready.fbx" }; + +poses[LEFT_OVERHEAD + RIGHT_ON_BASE ] = { name: "Left Overhead + Right On Base", animation: "http://s3.amazonaws.com/hifi-public/animations/Breakdancing/breakdance_ready.fbx" }; +poses[LEFT_LOWERED + RIGHT_ON_BASE ] = { name: "Left Lowered + Right On Base", animation: "http://s3.amazonaws.com/hifi-public/animations/Breakdancing/breakdance_ready.fbx" }; +poses[LEFT_SIDE + RIGHT_ON_BASE ] = { name: "Left Side + Right On Base", animation: "http://s3.amazonaws.com/hifi-public/animations/Breakdancing/breakdance_ready.fbx" }; +poses[LEFT_FRONT + RIGHT_ON_BASE ] = { name: "Left Front + Right On Base", animation: "http://s3.amazonaws.com/hifi-public/animations/Breakdancing/breakdance_ready.fbx" }; + +poses[LEFT_ON_BASE + RIGHT_OVERHEAD ] = { name: "Left On Base + Right Overhead", animation: "http://s3.amazonaws.com/hifi-public/animations/Breakdancing/breakdance_ready.fbx" }; +poses[LEFT_ON_BASE + RIGHT_LOWERED ] = { name: "Left On Base + Right Lowered", animation: "http://s3.amazonaws.com/hifi-public/animations/Breakdancing/breakdance_ready.fbx" }; +poses[LEFT_ON_BASE + RIGHT_SIDE ] = { name: "Left On Base + Right Side", animation: "http://s3.amazonaws.com/hifi-public/animations/Breakdancing/breakdance_ready.fbx" }; +poses[LEFT_ON_BASE + RIGHT_FRONT ] = { name: "Left On Base + Right Front", animation: "http://s3.amazonaws.com/hifi-public/animations/Breakdancing/breakdance_ready.fbx" }; + + + +poses[LEFT_OVERHEAD + RIGHT_OVERHEAD ] = { name: "Left Overhead + Right Overhead", animation: "http://s3.amazonaws.com/hifi-public/animations/Breakdancing/bboy_uprock.fbx" }; +poses[LEFT_LOWERED + RIGHT_OVERHEAD ] = { name: "Left Lowered + Right Overhead", animation: "http://s3.amazonaws.com/hifi-public/animations/Breakdancing/breakdance_footwork_1.fbx" }; +poses[LEFT_SIDE + RIGHT_OVERHEAD ] = { name: "Left Side + Right Overhead", animation: "http://s3.amazonaws.com/hifi-public/animations/Breakdancing/breakdance_footwork_2.fbx" }; +poses[LEFT_FRONT + RIGHT_OVERHEAD ] = { name: "Left Front + Right Overhead", animation: "http://s3.amazonaws.com/hifi-public/animations/Breakdancing/breakdance_footwork_3.fbx" }; + +poses[LEFT_OVERHEAD + RIGHT_LOWERED ] = { name: "Left Overhead + Right Lowered", animation: "http://s3.amazonaws.com/hifi-public/animations/Breakdancing/breakdance_footwork_to_freeze.fbx" }; +poses[LEFT_LOWERED + RIGHT_LOWERED ] = { name: "Left Lowered + Right Lowered", animation: "http://s3.amazonaws.com/hifi-public/animations/Breakdancing/breakdance_footwork_to_idle.fbx" }; +poses[LEFT_SIDE + RIGHT_LOWERED ] = { name: "Left Side + Right Lowered", animation: "http://s3.amazonaws.com/hifi-public/animations/Breakdancing/breakdance_freeze_var_1.fbx" }; +poses[LEFT_FRONT + RIGHT_LOWERED ] = { name: "Left Front + Right Lowered", animation: "http://s3.amazonaws.com/hifi-public/animations/Breakdancing/breakdance_freeze_var_2.fbx" }; + +poses[LEFT_OVERHEAD + RIGHT_SIDE ] = { name: "Left Overhead + Right Side", animation: "http://s3.amazonaws.com/hifi-public/animations/Breakdancing/breakdance_freeze_var_3.fbx" }; +poses[LEFT_LOWERED + RIGHT_SIDE ] = { name: "Left Lowered + Right Side", animation: "http://s3.amazonaws.com/hifi-public/animations/Breakdancing/breakdance_freeze_var_4.fbx" }; +poses[LEFT_SIDE + RIGHT_SIDE ] = { name: "Left Side + Right Side", animation: "http://s3.amazonaws.com/hifi-public/animations/Breakdancing/breakdance_freezes.fbx" }; +poses[LEFT_FRONT + RIGHT_SIDE ] = { name: "Left Front + Right Side", animation: "http://s3.amazonaws.com/hifi-public/animations/Breakdancing/breakdance_swipes.fbx" }; + +poses[LEFT_OVERHEAD + RIGHT_FRONT ] = { name: "Left Overhead + Right Front", animation: "http://s3.amazonaws.com/hifi-public/animations/Breakdancing/breakdance_uprock.fbx" }; +poses[LEFT_LOWERED + RIGHT_FRONT ] = { name: "Left Lowered + Right Front", animation: "http://s3.amazonaws.com/hifi-public/animations/Breakdancing/breakdance_uprock_var_1.fbx" }; +poses[LEFT_SIDE + RIGHT_FRONT ] = { name: "Left Side + Right Front", animation: "http://s3.amazonaws.com/hifi-public/animations/Breakdancing/breakdance_uprock_var_2.fbx" }; +poses[LEFT_FRONT + RIGHT_FRONT ] = { name: "Left Front + Right Front", animation: "http://s3.amazonaws.com/hifi-public/animations/Breakdancing/breakdance_uprock_var_1_end.fbx" }; + + +Script.update.connect(function(deltaTime) { + var date= new Date(); + var now= date.getTime(); + var elapsed = now - lastTime; + var inMoveCycle = false; + + var leftHandPos = MyAvatar.getLeftPalmPosition(); + var rightHandPos = MyAvatar.getRightPalmPosition(); + + Overlays.editOverlay(leftHand, { position: leftHandPos } ); + Overlays.editOverlay(rightHand, { position: rightHandPos } ); + + var hitTargetLeftOnBase = findSphereSphereHit(leftHandPos, handSize/2, getPositionLeftOnBase(), targetSize/2); + var hitTargetLeftOverhead = findSphereSphereHit(leftHandPos, handSize/2, getPositionLeftOverhead(), targetSize/2); + var hitTargetLeftLowered = findSphereSphereHit(leftHandPos, handSize/2, getPositionLeftLowered(), targetSize/2); + var hitTargetLeftSide = findSphereSphereHit(leftHandPos, handSize/2, getPositionLeftSide(), targetSize/2); + var hitTargetLeftFront = findSphereSphereHit(leftHandPos, handSize/2, getPositionLeftFront(), targetSize/2); + + var hitTargetRightOnBase = findSphereSphereHit(rightHandPos, handSize/2, getPositionRightOnBase(), targetSize/2); + var hitTargetRightOverhead = findSphereSphereHit(rightHandPos, handSize/2, getPositionRightOverhead(), targetSize/2); + var hitTargetRightLowered = findSphereSphereHit(rightHandPos, handSize/2, getPositionRightLowered(), targetSize/2); + var hitTargetRightSide = findSphereSphereHit(rightHandPos, handSize/2, getPositionRightSide(), targetSize/2); + var hitTargetRightFront = findSphereSphereHit(rightHandPos, handSize/2, getPositionRightFront(), targetSize/2); + + + // determine target colors + var targetColorLeftOnBase = hitTargetLeftOnBase ? targetColorHit : targetColor; + var targetColorLeftOverhead = hitTargetLeftOverhead ? targetColorHit : targetColor; + var targetColorLeftLowered = hitTargetLeftLowered ? targetColorHit : targetColor; + var targetColorLeftSide = hitTargetLeftSide ? targetColorHit : targetColor; + var targetColorLeftFront = hitTargetLeftFront ? targetColorHit : targetColor; + + var targetColorRightOnBase = hitTargetRightOnBase ? targetColorHit : targetColor; + var targetColorRightOverhead = hitTargetRightOverhead ? targetColorHit : targetColor; + var targetColorRightLowered = hitTargetRightLowered ? targetColorHit : targetColor; + var targetColorRightSide = hitTargetRightSide ? targetColorHit : targetColor; + var targetColorRightFront = hitTargetRightFront ? targetColorHit : targetColor; + + // calculate a combined arm pose based on left and right hits + var poseValue = NO_POSE; + poseValue += hitTargetLeftOnBase ? LEFT_ON_BASE : 0; + poseValue += hitTargetLeftOverhead ? LEFT_OVERHEAD : 0; + poseValue += hitTargetLeftLowered ? LEFT_LOWERED : 0; + poseValue += hitTargetLeftSide ? LEFT_SIDE : 0; + poseValue += hitTargetLeftFront ? LEFT_FRONT : 0; + poseValue += hitTargetRightOnBase ? RIGHT_ON_BASE : 0; + poseValue += hitTargetRightOverhead ? RIGHT_OVERHEAD : 0; + poseValue += hitTargetRightLowered ? RIGHT_LOWERED : 0; + poseValue += hitTargetRightSide ? RIGHT_SIDE : 0; + poseValue += hitTargetRightFront ? RIGHT_FRONT : 0; + + if (poses[poseValue] == undefined) { + Overlays.editOverlay(text, { text: "no pose -- value:" + poseValue }); + } else { + Overlays.editOverlay(text, { text: "pose:" + poses[poseValue].name + "\n" + "animation:" + poses[poseValue].animation }); + var props = Entities.getEntityProperties(puppetEntityID); + Entities.editEntity(puppetEntityID, { + animationURL: poses[poseValue].animation, + lifetime: TEMPORARY_LIFETIME + props.age // renew lifetime + }); + } + + lastPoseValue = poseValue; + + Overlays.editOverlay(leftOnBase, { position: getPositionLeftOnBase(), color: targetColorLeftOnBase } ); + Overlays.editOverlay(leftOverhead, { position: getPositionLeftOverhead(), color: targetColorLeftOverhead } ); + Overlays.editOverlay(leftLowered, { position: getPositionLeftLowered(), color: targetColorLeftLowered } ); + Overlays.editOverlay(leftSide, { position: getPositionLeftSide() , color: targetColorLeftSide } ); + Overlays.editOverlay(leftFront, { position: getPositionLeftFront() , color: targetColorLeftFront } ); + + Overlays.editOverlay(rightOnBase, { position: getPositionRightOnBase(), color: targetColorRightOnBase } ); + Overlays.editOverlay(rightOverhead, { position: getPositionRightOverhead(), color: targetColorRightOverhead } ); + Overlays.editOverlay(rightLowered, { position: getPositionRightLowered(), color: targetColorRightLowered } ); + Overlays.editOverlay(rightSide, { position: getPositionRightSide() , color: targetColorRightSide } ); + Overlays.editOverlay(rightFront, { position: getPositionRightFront() , color: targetColorRightFront } ); + }); + +Script.scriptEnding.connect(function() { + Overlays.deleteOverlay(leftHand); + Overlays.deleteOverlay(rightHand); + + Overlays.deleteOverlay(text); + Overlays.deleteOverlay(leftOnBase); + Overlays.deleteOverlay(leftOverhead); + Overlays.deleteOverlay(leftLowered); + Overlays.deleteOverlay(leftSide); + Overlays.deleteOverlay(leftFront); + Overlays.deleteOverlay(rightOnBase); + Overlays.deleteOverlay(rightOverhead); + Overlays.deleteOverlay(rightLowered); + Overlays.deleteOverlay(rightSide); + Overlays.deleteOverlay(rightFront); + + print("puppetEntityID:"+puppetEntityID); + Entities.deleteEntity(puppetEntityID); +}); \ No newline at end of file diff --git a/examples/edit.js b/examples/edit.js index 988e0a04b1..d778ff324d 100644 --- a/examples/edit.js +++ b/examples/edit.js @@ -448,12 +448,31 @@ var toolBar = (function () { } if (newPolyVoxButton === toolBar.clicked(clickedOverlay)) { - createNewEntity({ + var polyVoxId = createNewEntity({ type: "PolyVox", dimensions: { x: 10, y: 10, z: 10 }, voxelVolumeSize: {x:16, y:16, z:16}, - voxelSurfaceStyle: 1 + voxelSurfaceStyle: 2 }); + for (var x = 1; x <= 14; x++) { + Entities.setVoxel(polyVoxId, {x: x, y: 1, z: 1}, 255); + Entities.setVoxel(polyVoxId, {x: x, y: 14, z: 1}, 255); + Entities.setVoxel(polyVoxId, {x: x, y: 1, z: 14}, 255); + Entities.setVoxel(polyVoxId, {x: x, y: 14, z: 14}, 255); + } + for (var y = 2; y <= 13; y++) { + Entities.setVoxel(polyVoxId, {x: 1, y: y, z: 1}, 255); + Entities.setVoxel(polyVoxId, {x: 14, y: y, z: 1}, 255); + Entities.setVoxel(polyVoxId, {x: 1, y: y, z: 14}, 255); + Entities.setVoxel(polyVoxId, {x: 14, y: y, z: 14}, 255); + } + for (var z = 2; z <= 13; z++) { + Entities.setVoxel(polyVoxId, {x: 1, y: 1, z: z}, 255); + Entities.setVoxel(polyVoxId, {x: 14, y: 1, z: z}, 255); + Entities.setVoxel(polyVoxId, {x: 1, y: 14, z: z}, 255); + Entities.setVoxel(polyVoxId, {x: 14, y: 14, z: z}, 255); + } + return true; } diff --git a/examples/libraries/entityList.js b/examples/libraries/entityList.js index 241ffb2766..0fd1cd5a06 100644 --- a/examples/libraries/entityList.js +++ b/examples/libraries/entityList.js @@ -83,5 +83,11 @@ EntityListTool = function(opts) { } }); + webView.visibilityChanged.connect(function (visible) { + if (visible) { + that.sendUpdate(); + } + }); + return that; }; diff --git a/examples/libraries/toolBars.js b/examples/libraries/toolBars.js index abe8de8cc3..7ab68cab1c 100644 --- a/examples/libraries/toolBars.js +++ b/examples/libraries/toolBars.js @@ -10,7 +10,7 @@ // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // -Overlay2D = function(properties, overlay) { // overlay is an optionnal variable +Overlay2D = function(properties, overlay) { // overlay is an optional variable if (!(typeof(properties) === 'undefined')) { if(typeof(overlay) === 'undefined') { overlay = Overlays.addOverlay("image", properties); diff --git a/examples/toys/magBalls/ballController.js b/examples/toys/magBalls/ballController.js new file mode 100644 index 0000000000..0f178b2804 --- /dev/null +++ b/examples/toys/magBalls/ballController.js @@ -0,0 +1,103 @@ +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() { +} diff --git a/examples/toys/magBalls/constants.js b/examples/toys/magBalls/constants.js new file mode 100644 index 0000000000..d154910f91 --- /dev/null +++ b/examples/toys/magBalls/constants.js @@ -0,0 +1,140 @@ +// +// 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; +BALL_SIZE = 0.08 * SCALE; +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; + +BALL_DIMENSIONS = { + x: BALL_SIZE, + y: BALL_SIZE, + z: BALL_SIZE +}; + +BALL_COLOR = { + red: 128, + green: 128, + blue: 128 +}; + +STICK_DIMENSIONS = { + x: STICK_LENGTH / 6, + y: STICK_LENGTH / 6, + z: STICK_LENGTH +}; + +BALL_DISTANCE = STICK_LENGTH + BALL_SIZE; + +BALL_PROTOTYPE = { + type: "Sphere", + name: BALL_NAME, + dimensions: BALL_DIMENSIONS, + color: BALL_COLOR, + ignoreCollisions: true, + collisionsWillMove: false +}; + +// 2 millimeters +BALL_EPSILON = (.002) / BALL_DISTANCE; + +LINE_DIMENSIONS = { + x: 5, + y: 5, + z: 5 +} + +LINE_PROTOTYPE = { + type: "Line", + name: EDGE_NAME, + color: COLORS.CYAN, + dimensions: LINE_DIMENSIONS, + lineWidth: 5, + visible: true, + ignoreCollisions: true, + collisionsWillMove: false, +} + +EDGE_PROTOTYPE = LINE_PROTOTYPE; + +// var EDGE_PROTOTYPE = { +// type: "Sphere", +// name: EDGE_NAME, +// color: { red: 0, green: 255, blue: 255 }, +// //dimensions: STICK_DIMENSIONS, +// dimensions: { x: 0.02, y: 0.02, z: 0.02 }, +// rotation: rotation, +// visible: true, +// ignoreCollisions: true, +// collisionsWillMove: false +// } + + diff --git a/examples/toys/magBalls/debugUtils.js b/examples/toys/magBalls/debugUtils.js new file mode 100644 index 0000000000..8dadd34679 --- /dev/null +++ b/examples/toys/magBalls/debugUtils.js @@ -0,0 +1,95 @@ +findMatchingNode = function(position, nodePositions) { + for (var nodeId in nodePositions) { + var nodePos = nodePositions[nodeId]; + var distance = Vec3.distance(position, nodePos); + if (distance < 0.03) { + return nodeId; + } + } +} + +repairConnections = function() { + var ids = Entities.findEntities(MyAvatar.position, 50); + + // Find all the balls and record their positions + var nodePositions = {}; + for (var i in ids) { + var id = ids[i]; + var properties = Entities.getEntityProperties(id); + if (properties.name == BALL_NAME) { + nodePositions[id] = properties.position; + } + } + + // Now check all the edges to see if they're valid (point to balls) + // and ensure that the balls point back to them + var ballsToEdges = {}; + for (var i in ids) { + var id = ids[i]; + var properties = Entities.getEntityProperties(id); + if (properties.name == EDGE_NAME) { + var startPos = properties.position; + var endPos = Vec3.sum(startPos, properties.linePoints[1]); + var magBallData = getMagBallsData(id); + var update = false; + if (!magBallData.start) { + var startNode = findMatchingNode(startPos, nodePositions); + if (startNode) { + logDebug("Found start node " + startNode) + magBallData.start = startNode; + update = true; + } + } + if (!magBallData.end) { + var endNode = findMatchingNode(endPos, nodePositions); + if (endNode) { + logDebug("Found end node " + endNode) + magBallData.end = endNode; + update = true; + } + } + if (!magBallData.start || !magBallData.end) { + logDebug("Didn't find both ends"); + Entities.deleteEntity(id); + continue; + } + if (!ballsToEdges[magBallData.start]) { + ballsToEdges[magBallData.start] = [ id ]; + } else { + ballsToEdges[magBallData.start].push(id); + } + if (!ballsToEdges[magBallData.end]) { + ballsToEdges[magBallData.end] = [ id ]; + } else { + ballsToEdges[magBallData.end].push(id); + } + if (update) { + logDebug("Updating incomplete edge " + id); + magBallData.length = BALL_DISTANCE; + setMagBallsData(id, magBallData); + } + } + } + for (var nodeId in ballsToEdges) { + var magBallData = getMagBallsData(nodeId); + var edges = magBallData.edges || []; + var edgeHash = {}; + for (var i in edges) { + edgeHash[edges[i]] = true; + } + var update = false; + for (var i in ballsToEdges[nodeId]) { + var edgeId = ballsToEdges[nodeId][i]; + if (!edgeHash[edgeId]) { + update = true; + edgeHash[edgeId] = true; + edges.push(edgeId); + } + } + if (update) { + logDebug("Fixing node with missing edge data"); + magBallData.edges = edges; + setMagBallsData(nodeId, magBallData); + } + } +} diff --git a/examples/toys/magBalls/edgeSpring.js b/examples/toys/magBalls/edgeSpring.js new file mode 100644 index 0000000000..852c9257c2 --- /dev/null +++ b/examples/toys/magBalls/edgeSpring.js @@ -0,0 +1,45 @@ + +EdgeSpring = function(edgeId, graph) { + this.edgeId = edgeId; + this.graph = graph; + + var magBallsData = getMagBallsData(this.edgeId); + this.start = magBallsData.start; + this.end = magBallsData.end; + this.desiredLength = magBallsData.length || BALL_DISTANCE; +} + +EdgeSpring.prototype.adjust = function(results) { + var startPos = this.getAdjustedPosition(this.start, results); + var endPos = this.getAdjustedPosition(this.end, results); + var vector = Vec3.subtract(endPos, startPos); + var length = Vec3.length(vector); + var variance = this.getVariance(length); + + if (Math.abs(variance) <= this.MAX_VARIANCE) { + return false; + } + + // adjust by halves until we fall below our variance + var adjustmentVector = Vec3.multiply(variance / 4, vector); + + var newStartPos = Vec3.sum(Vec3.multiply(-1, adjustmentVector), startPos); + var newEndPos = Vec3.sum(adjustmentVector, endPos); + results[this.start] = newStartPos; + results[this.end] = newEndPos; + return true; +} + +EdgeSpring.prototype.MAX_VARIANCE = 0.005; + +EdgeSpring.prototype.getAdjustedPosition = function(nodeId, results) { + if (results[nodeId]) { + return results[nodeId]; + } + return this.graph.getNodePosition(nodeId); +} + +EdgeSpring.prototype.getVariance = function(length) { + var difference = this.desiredLength - length; + return difference / this.desiredLength; +} diff --git a/examples/toys/magBalls/graph.js b/examples/toys/magBalls/graph.js new file mode 100644 index 0000000000..df02ee3628 --- /dev/null +++ b/examples/toys/magBalls/graph.js @@ -0,0 +1,281 @@ +// +// 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 +// + +// A collection of nodes and edges connecting them. +Graph = function() { + /* Structure of nodes tree + this.nodes: { + nodeId1: { + edgeId1: true + } + nodeId2: { + edgeId1: true + }, + // Nodes can many edges + nodeId3: { + edgeId2: true + edgeId3: true + edgeId4: true + edgeId5: true + }, + // Nodes can have 0 edges + nodeId5: { + }, + ... + } + */ + this.nodes = {}; + /* Structure of edge tree + this.edges: { + edgeId1: { + // Every edge should have exactly two + nodeId1: true + nodeId2: true + }, + edgeId2: { + nodeId3: true + nodeId4: true + }, + ... + } + */ + this.edges = {}; +} + +Graph.prototype.createNodeEntity = function(properties) { + throw "Unimplemented"; +} + +Graph.prototype.createNode = function(properties) { + var nodeId = this.createNodeEntity(properties); + this.nodes[nodeId] = {}; + this.validate(); + return nodeId; +} + +Graph.prototype.createEdgeEntity = function(nodeA, nodeB) { + throw "Unimplemented"; +} + +Graph.prototype.createEdge = function(nodeA, nodeB) { + if (nodeA == nodeB) { + throw "Error: self connection not supported"; + } + var newEdgeId = this.createEdgeEntity(nodeA, nodeB); + + // Create the bidirectional linkage + this.edges[newEdgeId] = {}; + this.edges[newEdgeId][nodeA] = true; + this.edges[newEdgeId][nodeB] = true; + this.nodes[nodeA][newEdgeId] = true; + this.nodes[nodeB][newEdgeId] = true; + + this.validate(); +} + +Graph.prototype.getEdges = function(nodeId) { + var edges = this.nodes[nodeId]; + var result = {}; + for (var edgeId in edges) { + for (var otherNodeId in this.edges[edgeId]) { + if (otherNodeId != nodeId) { + result[edgeId] = otherNodeId; + } + } + } + return result; +} + +Graph.prototype.getConnectedNodes = function(nodeId) { + var edges = this.getEdges(nodeId); + var result = {}; + for (var edgeId in edges) { + var otherNodeId = edges[edgeId]; + result[otherNodeId] = edgeId; + } + return result; +} + +Graph.prototype.getEdgeLength = function(edgeId) { + var nodesInEdge = Object.keys(this.edges[edgeId]); + return this.getNodeDistance(nodesInEdge[0], nodesInEdge[1]); +} + +Graph.prototype.getNodeDistance = function(a, b) { + var apos = this.getNodePosition(a); + var bpos = this.getNodePosition(b); + return Vec3.distance(apos, bpos); +} + +Graph.prototype.getNodePosition = function(node) { + var properties = Entities.getEntityProperties(node); + return properties.position; +} + +Graph.prototype.breakEdges = function(nodeId) { + for (var edgeId in this.nodes[nodeId]) { + this.destroyEdge(edgeId); + } +} + +Graph.prototype.findNearestNode = function(position, maxDist) { + var resultId = null; + var resultDist = 0; + for (var nodeId in this.nodes) { + var nodePosition = this.getNodePosition(nodeId); + var curDist = Vec3.distance(nodePosition, position); + if (!maxDist || curDist <= maxDist) { + if (!resultId || curDist < resultDist) { + resultId = nodeId; + resultDist = curDist; + } + } + } + return resultId; +} + +Graph.prototype.findMatchingNodes = function(selector) { + var result = {}; + for (var nodeId in this.nodes) { + if (selector(nodeId)) { + result[nodeId] = true; + } + } + return result; +} + +Graph.prototype.destroyEdge = function(edgeId) { + logDebug("Deleting edge " + edgeId); + for (var nodeId in this.edges[edgeId]) { + delete this.nodes[nodeId][edgeId]; + } + delete this.edges[edgeId]; + Entities.deleteEntity(edgeId); + this.validate(); +} + +Graph.prototype.destroyNode = function(nodeId) { + logDebug("Deleting node " + nodeId); + this.breakEdges(nodeId); + delete this.nodes[nodeId]; + Entities.deleteEntity(nodeId); + this.validate(); +} + +Graph.prototype.deleteAll = function() { + var nodeIds = Object.keys(this.nodes); + for (var i in nodeIds) { + var nodeId = nodeIds[i]; + this.destroyNode(nodeId); + } +} + +Graph.prototype.areConnected = function(nodeIdA, nodeIdB) { + for (var edgeId in this.nodes[nodeIdA]) { + if (this.nodes[nodeIdB][edgeId]) { + return true; + } + } + return false; +} + +forEachValue = function(val, operation) { + if( typeof val === 'string' ) { + operation(val); + } else if (typeof val === 'object') { + if (val.constructor === Array) { + for (var i in val) { + operation(val[i]); + } + } else { + for (var v in val) { + operation(v); + } + } + } +} + +Graph.prototype.findShortestPath = function(start, end, options) { + var queue = [ start ]; + var prev = {}; + if (options && options.exclude) { + forEachValue(options.exclude, function(value) { + prev[value] = value; + }); + logDebug("exclude " + prev); + } + var found = false; + while (!found && Object.keys(queue).length) { + var current = queue.shift(); + for (var ballId in this.getConnectedNodes(current)) { + if (prev[ballId]) { + // already visited node + continue; + } + // record optimal path + prev[ballId] = current; + if (ballId == end) { + found = true; + break; + } + queue.push(ballId); + } + } + + if (!found) { + logDebug("Exhausted search"); + return; + } + + var result = [ end ]; + while (result[0] != start) { + result.unshift(prev[result[0]]); + } + + return result; +} + +Graph.prototype.validate = function() { + var error = false; + for (nodeId in this.nodes) { + for (edgeId in this.nodes[nodeId]) { + var edge = this.edges[edgeId]; + if (!edge) { + logError("Error: node " + nodeId + " refers to unknown edge " + edgeId); + error = true; + continue; + } + if (!edge[nodeId]) { + logError("Error: node " + nodeId + " refers to edge " + edgeId + " but not vice versa"); + error = true; + continue; + } + } + } + + for (edgeId in this.edges) { + for (nodeId in this.edges[edgeId]) { + var node = this.nodes[nodeId]; + if (!node) { + logError("Error: edge " + edgeId + " refers to unknown node " + nodeId); + error = true; + continue; + } + if (!node[edgeId]) { + logError("Error: edge " + edgeId + " refers to node " + nodeId + " but not vice versa"); + error = true; + continue; + } + } + } + + if (error) { + logDebug(JSON.stringify({ edges: this.edges, balls: this.nodes }, null, 2)); + } + return error; +} \ No newline at end of file diff --git a/examples/toys/magBalls/handController.js b/examples/toys/magBalls/handController.js new file mode 100644 index 0000000000..998d22c6f8 --- /dev/null +++ b/examples/toys/magBalls/handController.js @@ -0,0 +1,127 @@ +// +// 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"); +} + + diff --git a/examples/toys/magBalls/highlighter.js b/examples/toys/magBalls/highlighter.js new file mode 100644 index 0000000000..149d9ec5b7 --- /dev/null +++ b/examples/toys/magBalls/highlighter.js @@ -0,0 +1,70 @@ +// +// 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 +// +var SELECTION_OVERLAY = { + position: { + x: 0, + y: 0, + z: 0 + }, + color: { + red: 255, + green: 255, + blue: 0 + }, + alpha: 1, + size: 1.0, + solid: false, + //colorPulse: 1.0, + //pulseMin: 0.5, + //pulseMax: 1.0, + visible: false, + lineWidth: 1.0, + borderSize: 1.4, +}; + +Highlighter = function() { + this.highlightCube = Overlays.addOverlay("cube", this.SELECTION_OVERLAY); + this.hightlighted = null; + var _this = this; + Script.scriptEnding.connect(function() { + _this.onCleanup(); + }); +}; + +Highlighter.prototype.onCleanup = function() { + Overlays.deleteOverlay(this.highlightCube); +} + +Highlighter.prototype.highlight = function(entityId) { + if (entityId != this.hightlighted) { + this.hightlighted = entityId; + this.updateHighlight(); + } +} + +Highlighter.prototype.setSize = function(newSize) { + Overlays.editOverlay(this.highlightCube, { + size: newSize + }); +} + +Highlighter.prototype.updateHighlight = function() { + if (this.hightlighted) { + var properties = Entities.getEntityProperties(this.hightlighted); + // logDebug("Making highlight " + this.highlightCube + " visible @ " + vec3toStr(properties.position)); + Overlays.editOverlay(this.highlightCube, { + position: properties.position, + visible: true + }); + } else { + // logDebug("Making highlight invisible"); + Overlays.editOverlay(this.highlightCube, { + visible: false + }); + } +} \ No newline at end of file diff --git a/examples/toys/magBalls/magBalls.js b/examples/toys/magBalls/magBalls.js new file mode 100644 index 0000000000..187c550073 --- /dev/null +++ b/examples/toys/magBalls/magBalls.js @@ -0,0 +1,293 @@ +// +// 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 +// + +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.lastUpdateAge = 0; + this.stable = false; + this.adjustIterations = 0; + this.selectedNodes = {}; + this.edgeObjects = {}; + + this.refresh(); + + var _this = this; + Script.update.connect(function(deltaTime) { + _this.onUpdate(deltaTime); + }); + + Script.scriptEnding.connect(function() { + _this.onCleanup(); + }); +} + +MagBalls.prototype = Object.create( Graph.prototype ); + +MagBalls.prototype.onUpdate = function(deltaTime) { + this.lastUpdateAge += deltaTime; + if (this.lastUpdateAge > UPDATE_INTERVAL) { + this.lastUpdateAge = 0; + if (!this.stable) { + this.adjustIterations += 1; + // logDebug("Update"); + var adjusted = false; + var nodeAdjustResults = {}; + var fixupEdges = {}; + + for(var edgeId in this.edges) { + adjusted |= this.edgeObjects[edgeId].adjust(nodeAdjustResults); + } + for (var nodeId in nodeAdjustResults) { + var curPos = this.getNodePosition(nodeId); + var newPos = nodeAdjustResults[nodeId]; + var distance = Vec3.distance(curPos, newPos); + for (var edgeId in this.nodes[nodeId]) { + fixupEdges[edgeId] = true; + } + // logDebug("Moving node Id " + nodeId + " " + (distance * 1000).toFixed(3) + " mm"); + Entities.editEntity(nodeId, { position: newPos, color: COLORS.RED }); + } + + for (var edgeId in fixupEdges) { + this.fixupEdge(edgeId); + } + + Script.setTimeout(function(){ + for (var nodeId in nodeAdjustResults) { + Entities.editEntity(nodeId, { color: BALL_COLOR }); + } + }, ((UPDATE_INTERVAL * 1000) / 2)); + + if (!adjusted || this.adjustIterations > this.MAX_ADJUST_ITERATIONS) { + this.adjustIterations = 0; + this.stable = true; + } + } + } +} + +MagBalls.prototype.createNodeEntity = function(customProperies) { + var nodeId = Entities.addEntity(mergeObjects(BALL_PROTOTYPE, customProperies)); + return nodeId; +} + +MagBalls.prototype.createEdgeEntity = function(nodeIdA, nodeIdB) { + var apos = this.getNodePosition(nodeIdA); + var bpos = this.getNodePosition(nodeIdB); + var edgeId = Entities.addEntity(mergeObjects(EDGE_PROTOTYPE, { + position: apos, + linePoints: [ ZERO_VECTOR, Vec3.subtract(bpos, apos) ], + userData: JSON.stringify({ + magBalls: { + start: nodeIdA, + end: nodeIdB, + length: BALL_DISTANCE + } + }) + })); + this.edgeObjects[edgeId] = new EdgeSpring(edgeId, this); + return edgeId; +} + +MagBalls.prototype.findPotentialEdges = function(nodeId) { + var variances = {}; + for (var otherNodeId in this.nodes) { + // can't self connect + if (otherNodeId == nodeId) { + continue; + } + + // can't doubly connect + if (this.areConnected(otherNodeId, nodeId)) { + continue; + } + + // Check distance to attempt + var distance = this.getNodeDistance(nodeId, otherNodeId); + var variance = this.getVariance(distance); + if (Math.abs(variance) > 0.25) { + continue; + } + + variances[otherNodeId] = variance; + } + 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.releaseBall = function(releasedBall) { + delete this.selectedNodes[releasedBall]; + logDebug("Released ball: " + 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. + // FIXME (play a pop sound) + for (var nodeId in this.nodes) { + if (nodeId == releasedBall) { + continue; + } + var distance = this.getNodeDistance(releasedBall, nodeId); + if (distance < BALL_SIZE) { + this.destroyNode(releasedBall); + return; + } + } + + var targets = this.findPotentialEdges(releasedBall); + if (!targets || !Object.keys(targets).length) { +// this.destroyNode(releasedBall); + } + for (var otherBallId in targets) { + this.createEdge(otherBallId, releasedBall); + } + this.validate(); +} + + +MagBalls.prototype.getVariance = function(distance) { + // FIXME different balls or edges might have different ideas of variance... + // let something else handle this + var offset = (BALL_DISTANCE - distance); + var variance = offset / BALL_DISTANCE + return variance; +} + +// remove unconnected balls +MagBalls.prototype.clean = function() { + // do nothing unless there are at least 2 balls and one edge + if (Object.keys(this.nodes).length < 2 || !Object.keys(this.edges).length) { + return; + } + var disconnectedNodes = {}; + for (var nodeId in this.nodes) { + if (!Object.keys(this.nodes[nodeId]).length) { + disconnectedNodes[nodeId] = true; + } + } + for (var nodeId in disconnectedNodes) { + this.destroyNode(nodeId); + } +} + +// remove all balls +MagBalls.prototype.clear = function() { + if (DEBUG_MAGSTICKS) { + this.deleteAll(); + var ids = Entities.findEntities(MyAvatar.position, 50); + var result = []; + ids.forEach(function(id) { + var properties = Entities.getEntityProperties(id); + if (properties.name == BALL_NAME || properties.name == EDGE_NAME) { + Entities.deleteEntity(id); + } + }, this); + } +} + +MagBalls.prototype.destroyEdge = function(edgeId) { + Graph.prototype.destroyEdge.call(this, edgeId); + delete this.edgeObjects[edgeId]; +} + +MagBalls.prototype.destroyNode = function(nodeId) { + Graph.prototype.destroyNode.call(this, nodeId); +} + +// Scan the entity tree and load all the objects in range +MagBalls.prototype.refresh = function() { + var ids = Entities.findEntities(MyAvatar.position, 50); + for (var i in ids) { + var id = ids[i]; + var properties = Entities.getEntityProperties(id); + if (properties.name == BALL_NAME) { + this.nodes[id] = {}; + } + } + + var deleteEdges = []; + for (var i in ids) { + var id = ids[i]; + var properties = Entities.getEntityProperties(id); + if (properties.name == EDGE_NAME) { + var edgeId = id; + this.edges[edgeId] = {}; + var magBallData = getMagBallsData(id); + if (!magBallData.start || !magBallData.end) { + logWarn("Edge information is missing for " + id); + continue; + } + if (!this.nodes[magBallData.start] || !this.nodes[magBallData.end]) { + logWarn("Edge " + id + " refers to unknown nodes: " + JSON.stringify(magBallData)); + Entities.editEntity(id, { color: COLORS.RED }); + deleteEdges.push(id); + continue; + } + this.nodes[magBallData.start][edgeId] = true; + this.nodes[magBallData.end][edgeId] = true; + this.edges[edgeId][magBallData.start] = true; + this.edges[edgeId][magBallData.end] = true; + this.edgeObjects[id] = new EdgeSpring(id, this); + } + } + + if (deleteEdges.length) { + Script.setTimeout(function() { + for (var i in deleteEdges) { + var edgeId = deleteEdges[i]; + logDebug("deleting invalid edge " + edgeId); + Entities.deleteEntity(edgeId); + } + }, 1000); + } + + var edgeCount = Object.keys(this.edges).length; + var nodeCount = Object.keys(this.nodes).length; + logDebug("Found " + nodeCount + " nodes and " + edgeCount + " edges "); + this.validate(); +} + + +MagBalls.prototype.findEdgeParams = function(startBall, endBall) { + var startBallPos = this.getNodePosition(startBall); + var endBallPos = this.getNodePosition(endBall); + var vector = Vec3.subtract(endBallPos, startBallPos); + return { + position: startBallPos, + linePoints: [ ZERO_VECTOR, vector ] + }; +} + +MagBalls.prototype.fixupEdge = function(edgeId) { + var ballsInEdge = Object.keys(this.edges[edgeId]); + Entities.editEntity(edgeId, this.findEdgeParams(ballsInEdge[0], ballsInEdge[1])); +} + diff --git a/examples/toys/magBalls/magBallsMain.js b/examples/toys/magBalls/magBallsMain.js new file mode 100644 index 0000000000..e54b818e4a --- /dev/null +++ b/examples/toys/magBalls/magBallsMain.js @@ -0,0 +1,25 @@ +// +// 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) ]; diff --git a/examples/toys/magBalls/menuController.js b/examples/toys/magBalls/menuController.js new file mode 100644 index 0000000000..0a076d1ff8 --- /dev/null +++ b/examples/toys/magBalls/menuController.js @@ -0,0 +1,66 @@ +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(); +} diff --git a/examples/toys/magBalls/utils.js b/examples/toys/magBalls/utils.js new file mode 100644 index 0000000000..ea1446f858 --- /dev/null +++ b/examples/toys/magBalls/utils.js @@ -0,0 +1,106 @@ +// +// 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 +// + +vec3toStr = function (v, digits) { + if (!digits) { digits = 3; } + return "{ " + v.x.toFixed(digits) + ", " + v.y.toFixed(digits) + ", " + v.z.toFixed(digits)+ " }"; +} + +scaleLine = function (start, end, scale) { + var v = Vec3.subtract(end, start); + var length = Vec3.length(v); + v = Vec3.multiply(scale, v); + return Vec3.sum(start, v); +} + +findAction = function(name) { + var actions = Controller.getAllActions(); + for (var i = 0; i < actions.length; i++) { + if (actions[i].actionName == name) { + return i; + } + } + return 0; +} + +addLine = function(origin, vector, color) { + if (!color) { + color = COLORS.WHITE + } + return Entities.addEntity(mergeObjects(LINE_PROTOTYPE, { + position: origin, + linePoints: [ + ZERO_VECTOR, + vector, + ], + color: color + })); +} + +// FIXME fetch from a subkey of user data to support non-destructive modifications +setEntityUserData = function(id, data) { + var json = JSON.stringify(data) + Entities.editEntity(id, { userData: json }); +} + +// FIXME do non-destructive modification of the existing user data +getEntityUserData = function(id) { + var results = null; + var properties = Entities.getEntityProperties(id); + if (properties.userData) { + results = JSON.parse(properties.userData); + } + return results ? results : {}; +} + +// Non-destructively modify the user data of an entity. +setEntityCustomData = function(customKey, id, data) { + var userData = getEntityUserData(id); + userData[customKey] = data; + setEntityUserData(id, userData); +} + +getEntityCustomData = function(customKey, id, defaultValue) { + var userData = getEntityUserData(id); + 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) { + result[attrname] = proto[attrname]; + } + for (var attrname in custom) { + result[attrname] = custom[attrname]; + } + return result; +} + +logWarn = function(str) { + print(str); +} + +logError = function(str) { + print(str); +} + +logInfo = function(str) { + print(str); +} + +logDebug = function(str) { + print(str); +} \ No newline at end of file diff --git a/examples/voxels.js b/examples/voxels.js index cc3453202a..d9049e2b0c 100644 --- a/examples/voxels.js +++ b/examples/voxels.js @@ -1,10 +1,206 @@ var controlHeld = false; var shiftHeld = false; +Script.include([ + "libraries/toolBars.js", +]); + +var isActive = false; +var toolIconUrl = "http://headache.hungry.com/~seth/hifi/"; +var toolHeight = 50; +var toolWidth = 50; + +var addingVoxels = false; +var deletingVoxels = false; + +offAlpha = 0.5; +onAlpha = 0.9; + function floorVector(v) { return {x: Math.floor(v.x), y: Math.floor(v.y), z: Math.floor(v.z)}; } +function vectorToString(v){ + return "{" + v.x + ", " + v.x + ", " + v.x + "}"; +} + +var toolBar = (function () { + var that = {}, + toolBar, + activeButton, + addVoxelButton, + deleteVoxelButton, + addTerrainButton; + + function initialize() { + toolBar = new ToolBar(0, 0, ToolBar.VERTICAL, "highfidelity.voxel.toolbar", function (windowDimensions, toolbar) { + return { + x: windowDimensions.x - 8*2 - toolbar.width * 2, + y: (windowDimensions.y - toolbar.height) / 2 + }; + }); + + activeButton = toolBar.addTool({ + imageURL: "http://s3.amazonaws.com/hifi-public/images/tools/polyvox.svg", + width: toolWidth, + height: toolHeight, + alpha: onAlpha, + visible: true, + }); + + addVoxelButton = toolBar.addTool({ + imageURL: toolIconUrl + "voxel-add.svg", + subImage: { x: 0, y: Tool.IMAGE_WIDTH, width: Tool.IMAGE_WIDTH, height: Tool.IMAGE_HEIGHT }, + width: toolWidth, + height: toolHeight, + alpha: offAlpha, + visible: false + }); + + deleteVoxelButton = toolBar.addTool({ + imageURL: toolIconUrl + "voxel-delete.svg", + subImage: { x: 0, y: Tool.IMAGE_WIDTH, width: Tool.IMAGE_WIDTH, height: Tool.IMAGE_HEIGHT }, + width: toolWidth, + height: toolHeight, + alpha: offAlpha, + visible: false + }); + + addTerrainButton = toolBar.addTool({ + imageURL: toolIconUrl + "voxel-terrain.svg", + subImage: { x: 0, y: Tool.IMAGE_WIDTH, width: Tool.IMAGE_WIDTH, height: Tool.IMAGE_HEIGHT }, + width: toolWidth, + height: toolHeight, + alpha: onAlpha, + visible: false + }); + + that.setActive(false); + } + + + that.setActive = function(active) { + if (active != isActive) { + isActive = active; + that.showTools(isActive); + } + toolBar.selectTool(activeButton, isActive); + }; + + // Sets visibility of tool buttons, excluding the power button + that.showTools = function(doShow) { + toolBar.showTool(addVoxelButton, doShow); + toolBar.showTool(deleteVoxelButton, doShow); + toolBar.showTool(addTerrainButton, doShow); + }; + + that.mousePressEvent = function (event) { + var clickedOverlay = Overlays.getOverlayAtPoint({ x: event.x, y: event.y }); + + if (activeButton === toolBar.clicked(clickedOverlay)) { + that.setActive(!isActive); + return true; + } + + if (addVoxelButton === toolBar.clicked(clickedOverlay)) { + if (addingVoxels) { + addingVoxels = false; + deletingVoxels = false; + toolBar.setAlpha(offAlpha, addVoxelButton); + toolBar.setAlpha(offAlpha, deleteVoxelButton); + toolBar.selectTool(addVoxelButton, false); + toolBar.selectTool(deleteVoxelButton, false); + } else { + addingVoxels = true; + deletingVoxels = false; + toolBar.setAlpha(onAlpha, addVoxelButton); + toolBar.setAlpha(offAlpha, deleteVoxelButton); + } + return true; + } + + if (deleteVoxelButton === toolBar.clicked(clickedOverlay)) { + if (deletingVoxels) { + deletingVoxels = false; + addingVoxels = false; + toolBar.setAlpha(offAlpha, addVoxelButton); + toolBar.setAlpha(offAlpha, deleteVoxelButton); + } else { + deletingVoxels = true; + addingVoxels = false; + toolBar.setAlpha(offAlpha, addVoxelButton); + toolBar.setAlpha(onAlpha, deleteVoxelButton); + } + return true; + } + + if (addTerrainButton === toolBar.clicked(clickedOverlay)) { + addTerrainBlock(); + return true; + } + } + + Window.domainChanged.connect(function() { + that.setActive(false); + }); + + that.cleanup = function () { + toolBar.cleanup(); + // Overlays.deleteOverlay(activeButton); + }; + + + initialize(); + return that; +}()); + + +function addTerrainBlock() { + + var myPosDiv16 = Vec3.multiply(Vec3.sum(MyAvatar.position, {x:8, x:8, z:8}), 1.0 / 16.0); + var myPosDiv16Floored = floorVector(myPosDiv16); + var baseLocation = Vec3.multiply(myPosDiv16Floored, 16.0); + + if (baseLocation.y + 8 > MyAvatar.position.y) { + baseLocation.y -= 16; + } + + print("myPosDiv16 is " + vectorToString(myPosDiv16)); + print("MyPosDiv16Floored is " + vectorToString(myPosDiv16Floored)); + print("baseLocation is " + vectorToString(baseLocation)); + + alreadyThere = Entities.findEntities(baseLocation, 1.0); + for (var i = 0; i < alreadyThere.length; i++) { + var id = alreadyThere[i]; + var properties = Entities.getEntityProperties(id); + if (properties.name == "terrain") { + print("already terrain there"); + return; + } + } + + var polyVoxId = Entities.addEntity({ + type: "PolyVox", + name: "terrain", + position: baseLocation, + dimensions: { x: 16, y: 16, z: 16 }, + voxelVolumeSize: {x:16, y:16, z:16}, + voxelSurfaceStyle: 2 + }); + Entities.setAllVoxels(polyVoxId, 255); + + for (var y = 8; y < 16; y++) { + for (var x = 0; x < 16; x++) { + for (var z = 0; z < 16; z++) { + Entities.setVoxel(polyVoxId, {x: x, y: y, z: z}, 0); + } + } + } + + return true; +} + + function attemptVoxelChange(pickRayDir, intersection) { var properties = Entities.getEntityProperties(intersection.entityID); @@ -12,25 +208,30 @@ function attemptVoxelChange(pickRayDir, intersection) { return false; } + if (addingVoxels == false && deletingVoxels == false) { + return false; + } + var voxelPosition = Entities.worldCoordsToVoxelCoords(intersection.entityID, intersection.intersection); - voxelPosition = Vec3.subtract(voxelPosition, {x: 0.5, y: 0.5, z: 0.5}); var pickRayDirInVoxelSpace = Entities.localCoordsToVoxelCoords(intersection.entityID, pickRayDir); pickRayDirInVoxelSpace = Vec3.normalize(pickRayDirInVoxelSpace); + var doAdd = addingVoxels; + var doDelete = deletingVoxels; + if (controlHeld) { - // hold control to erase a voxel + doAdd = deletingVoxels; + doDelete = addingVoxels; + } + + if (doDelete) { var toErasePosition = Vec3.sum(voxelPosition, Vec3.multiply(pickRayDirInVoxelSpace, 0.1)); return Entities.setVoxel(intersection.entityID, floorVector(toErasePosition), 0); - } else if (shiftHeld) { - // hold shift to set all voxels to 255 - return Entities.setAllVoxels(intersection.entityID, 255); - } else { - // no modifier key to add a voxel + } + if (doAdd) { var toDrawPosition = Vec3.subtract(voxelPosition, Vec3.multiply(pickRayDirInVoxelSpace, 0.1)); return Entities.setVoxel(intersection.entityID, floorVector(toDrawPosition), 255); } - - // Entities.setVoxelSphere(id, intersection.intersection, radius, 0) } function mousePressEvent(event) { @@ -38,6 +239,10 @@ function mousePressEvent(event) { return; } + if (toolBar.mousePressEvent(event)) { + return; + } + var pickRay = Camera.computePickRay(event.x, event.y); var intersection = Entities.findRayIntersection(pickRay, true); // accurate picking @@ -76,6 +281,15 @@ function keyReleaseEvent(event) { } +function cleanup() { + for (var i = 0; i < overlays.length; i++) { + Overlays.deleteOverlay(overlays[i]); + } + toolBar.cleanup(); +} + + Controller.mousePressEvent.connect(mousePressEvent); Controller.keyPressEvent.connect(keyPressEvent); Controller.keyReleaseEvent.connect(keyReleaseEvent); +Script.scriptEnding.connect(cleanup); diff --git a/ice-server/src/IceServer.cpp b/ice-server/src/IceServer.cpp index fb2c88f75e..03b06b0aef 100644 --- a/ice-server/src/IceServer.cpp +++ b/ice-server/src/IceServer.cpp @@ -55,46 +55,50 @@ void IceServer::processDatagrams() { _serverSocket.readDatagram(buffer.get(), packetSizeWithHeader, sendingSockAddr.getAddressPointer(), sendingSockAddr.getPortPointer()); - auto packet = Packet::fromReceivedPacket(std::move(buffer), packetSizeWithHeader, sendingSockAddr); - - PacketType::Value packetType = packet->getType(); - - if (packetType == PacketType::ICEServerHeartbeat) { - SharedNetworkPeer peer = addOrUpdateHeartbeatingPeer(*packet); - - // so that we can send packets to the heartbeating peer when we need, we need to activate a socket now - peer->activateMatchingOrNewSymmetricSocket(sendingSockAddr); - } else if (packetType == PacketType::ICEServerQuery) { - QDataStream heartbeatStream(packet.get()); - - // this is a node hoping to connect to a heartbeating peer - do we have the heartbeating peer? - QUuid senderUUID; - heartbeatStream >> senderUUID; - - // pull the public and private sock addrs for this peer - HifiSockAddr publicSocket, localSocket; - heartbeatStream >> publicSocket >> localSocket; - - // check if this node also included a UUID that they would like to connect to - QUuid connectRequestID; - heartbeatStream >> connectRequestID; + // make sure that this packet at least looks like something we can read + if (packetSizeWithHeader >= Packet::localHeaderSize(PacketType::ICEServerHeartbeat)) { - SharedNetworkPeer matchingPeer = _activePeers.value(connectRequestID); - - if (matchingPeer) { + auto packet = Packet::fromReceivedPacket(std::move(buffer), packetSizeWithHeader, sendingSockAddr); + + PacketType::Value packetType = packet->getType(); + + if (packetType == PacketType::ICEServerHeartbeat) { + SharedNetworkPeer peer = addOrUpdateHeartbeatingPeer(*packet); - qDebug() << "Sending information for peer" << connectRequestID << "to peer" << senderUUID; + // so that we can send packets to the heartbeating peer when we need, we need to activate a socket now + peer->activateMatchingOrNewSymmetricSocket(sendingSockAddr); + } else if (packetType == PacketType::ICEServerQuery) { + QDataStream heartbeatStream(packet.get()); - // we have the peer they want to connect to - send them pack the information for that peer - sendPeerInformationPacket(*(matchingPeer.data()), &sendingSockAddr); - - // we also need to send them to the active peer they are hoping to connect to - // create a dummy peer object we can pass to sendPeerInformationPacket - - NetworkPeer dummyPeer(senderUUID, publicSocket, localSocket); - sendPeerInformationPacket(dummyPeer, matchingPeer->getActiveSocket()); - } else { - qDebug() << "Peer" << senderUUID << "asked for" << connectRequestID << "but no matching peer found"; + // this is a node hoping to connect to a heartbeating peer - do we have the heartbeating peer? + QUuid senderUUID; + heartbeatStream >> senderUUID; + + // pull the public and private sock addrs for this peer + HifiSockAddr publicSocket, localSocket; + heartbeatStream >> publicSocket >> localSocket; + + // check if this node also included a UUID that they would like to connect to + QUuid connectRequestID; + heartbeatStream >> connectRequestID; + + SharedNetworkPeer matchingPeer = _activePeers.value(connectRequestID); + + if (matchingPeer) { + + qDebug() << "Sending information for peer" << connectRequestID << "to peer" << senderUUID; + + // we have the peer they want to connect to - send them pack the information for that peer + sendPeerInformationPacket(*(matchingPeer.data()), &sendingSockAddr); + + // we also need to send them to the active peer they are hoping to connect to + // create a dummy peer object we can pass to sendPeerInformationPacket + + NetworkPeer dummyPeer(senderUUID, publicSocket, localSocket); + sendPeerInformationPacket(dummyPeer, matchingPeer->getActiveSocket()); + } else { + qDebug() << "Peer" << senderUUID << "asked for" << connectRequestID << "but no matching peer found"; + } } } } diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index 74307ea673..a8781ee308 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -2804,7 +2804,8 @@ void Application::update(float deltaTime) { _entities.getTree()->lockForWrite(); _entitySimulation.lock(); - _physicsEngine.changeObjects(_entitySimulation.getObjectsToChange()); + VectorOfMotionStates stillNeedChange = _physicsEngine.changeObjects(_entitySimulation.getObjectsToChange()); + _entitySimulation.setObjectsToChange(stillNeedChange); _entitySimulation.unlock(); _entities.getTree()->unlock(); @@ -5047,5 +5048,7 @@ void Application::emulateMouse(Hand* hand, float click, float shift, int index) void Application::crashApplication() { QObject* object = nullptr; bool value = object->isWindowType(); + Q_UNUSED(value); + qCDebug(interfaceapp) << "Intentionally crashed Interface"; } diff --git a/interface/src/Menu.cpp b/interface/src/Menu.cpp index 1d90e2c771..4696463181 100644 --- a/interface/src/Menu.cpp +++ b/interface/src/Menu.cpp @@ -222,7 +222,7 @@ Menu::Menu() { addActionToQMenuAndActionHash(toolsMenu, MenuOption::PackageModel, 0, qApp, SLOT(packageModel())); - MenuWrapper* displayMenu = addMenu(DisplayPlugin::MENU_PATH()); + addMenu(DisplayPlugin::MENU_PATH()); { MenuWrapper* displayModeMenu = addMenu(MenuOption::OutputMenu); QActionGroup* displayModeGroup = new QActionGroup(displayModeMenu); diff --git a/interface/src/avatar/AvatarMotionState.cpp b/interface/src/avatar/AvatarMotionState.cpp index b106ee6398..cabe545f5a 100644 --- a/interface/src/avatar/AvatarMotionState.cpp +++ b/interface/src/avatar/AvatarMotionState.cpp @@ -28,15 +28,20 @@ AvatarMotionState::~AvatarMotionState() { } // virtual -uint32_t AvatarMotionState::getAndClearIncomingDirtyFlags() { +uint32_t AvatarMotionState::getIncomingDirtyFlags() { uint32_t dirtyFlags = 0; if (_body && _avatar) { dirtyFlags = _dirtyFlags; - _dirtyFlags = 0; } return dirtyFlags; } +void AvatarMotionState::clearIncomingDirtyFlags() { + if (_body && _avatar) { + _dirtyFlags = 0; + } +} + MotionType AvatarMotionState::computeObjectMotionType() const { // TODO?: support non-DYNAMIC motion for avatars? (e.g. when sitting) return MOTION_TYPE_DYNAMIC; diff --git a/interface/src/avatar/AvatarMotionState.h b/interface/src/avatar/AvatarMotionState.h index ac813e764c..1c49705f23 100644 --- a/interface/src/avatar/AvatarMotionState.h +++ b/interface/src/avatar/AvatarMotionState.h @@ -25,7 +25,8 @@ public: virtual MotionType getMotionType() const { return _motionType; } - virtual uint32_t getAndClearIncomingDirtyFlags(); + virtual uint32_t getIncomingDirtyFlags(); + virtual void clearIncomingDirtyFlags(); virtual MotionType computeObjectMotionType() const; @@ -65,6 +66,7 @@ public: friend class AvatarManager; protected: + virtual bool isReadyToComputeShape() { return true; } virtual btCollisionShape* computeNewShape(); virtual void clearObjectBackPointer(); Avatar* _avatar; diff --git a/interface/src/avatar/MyAvatar.cpp b/interface/src/avatar/MyAvatar.cpp index bf42cfa7e1..39d2637b9e 100644 --- a/interface/src/avatar/MyAvatar.cpp +++ b/interface/src/avatar/MyAvatar.cpp @@ -844,7 +844,6 @@ void MyAvatar::sendKillAvatar() { DependencyManager::get()->broadcastToNodes(std::move(killPacket), NodeSet() << NodeType::AvatarMixer); } -static int counter = 0; void MyAvatar::updateLookAtTargetAvatar() { // // Look at the avatar whose eyes are closest to the ray in direction of my avatar's head diff --git a/interface/src/avatar/SkeletonModel.cpp b/interface/src/avatar/SkeletonModel.cpp index 752fb55ce6..acd2f038f4 100644 --- a/interface/src/avatar/SkeletonModel.cpp +++ b/interface/src/avatar/SkeletonModel.cpp @@ -51,14 +51,14 @@ void SkeletonModel::initJointStates(QVector states) { int rightElbowJointIndex = rightHandJointIndex >= 0 ? geometry.joints.at(rightHandJointIndex).parentIndex : -1; int rightShoulderJointIndex = rightElbowJointIndex >= 0 ? geometry.joints.at(rightElbowJointIndex).parentIndex : -1; - _boundingRadius = _rig->initJointStates(states, parentTransform, - rootJointIndex, - leftHandJointIndex, - leftElbowJointIndex, - leftShoulderJointIndex, - rightHandJointIndex, - rightElbowJointIndex, - rightShoulderJointIndex); + _rig->initJointStates(states, parentTransform, + rootJointIndex, + leftHandJointIndex, + leftElbowJointIndex, + leftShoulderJointIndex, + rightHandJointIndex, + rightElbowJointIndex, + rightShoulderJointIndex); // Determine the default eye position for avatar scale = 1.0 int headJointIndex = _geometry->getFBXGeometry().headJointIndex; @@ -533,7 +533,6 @@ void SkeletonModel::computeBoundingShape(const FBXGeometry& geometry) { glm::vec3 rootPosition = _rig->getJointState(geometry.rootJointIndex).getPosition(); _boundingCapsuleLocalOffset = 0.5f * (totalExtents.maximum + totalExtents.minimum) - rootPosition; - _boundingRadius = 0.5f * glm::length(diagonal); } void SkeletonModel::renderBoundingCollisionShapes(gpu::Batch& batch, float alpha) { diff --git a/interface/src/scripting/WebWindowClass.cpp b/interface/src/scripting/WebWindowClass.cpp index f0fd937a7d..a549410305 100644 --- a/interface/src/scripting/WebWindowClass.cpp +++ b/interface/src/scripting/WebWindowClass.cpp @@ -45,6 +45,7 @@ WebWindowClass::WebWindowClass(const QString& title, const QString& url, int wid auto dockWidget = new QDockWidget(title, toolWindow); dockWidget->setFeatures(QDockWidget::DockWidgetMovable); + connect(dockWidget, &QDockWidget::visibilityChanged, this, &WebWindowClass::visibilityChanged); _webView = new QWebView(dockWidget); addEventBridgeToWindowObject(); diff --git a/interface/src/scripting/WebWindowClass.h b/interface/src/scripting/WebWindowClass.h index 3659c08ac2..cc6506b059 100644 --- a/interface/src/scripting/WebWindowClass.h +++ b/interface/src/scripting/WebWindowClass.h @@ -60,6 +60,7 @@ public slots: void setTitle(const QString& title); signals: + void visibilityChanged(bool visible); // Tool window void moved(glm::vec2 position); void resized(QSizeF size); void closed(); diff --git a/interface/src/ui/ApplicationCompositor.cpp b/interface/src/ui/ApplicationCompositor.cpp index 98634d7aed..da7a934008 100644 --- a/interface/src/ui/ApplicationCompositor.cpp +++ b/interface/src/ui/ApplicationCompositor.cpp @@ -206,8 +206,6 @@ void ApplicationCompositor::displayOverlayTexture(RenderArgs* renderArgs) { updateTooltips(); - auto deviceSize = qApp->getDeviceSize(); - //Handle fading and deactivation/activation of UI gpu::Batch batch; diff --git a/libraries/animation/src/Rig.cpp b/libraries/animation/src/Rig.cpp index 3e466b94d6..8adb53024c 100644 --- a/libraries/animation/src/Rig.cpp +++ b/libraries/animation/src/Rig.cpp @@ -185,14 +185,14 @@ void Rig::deleteAnimations() { _animationHandles.clear(); } -float Rig::initJointStates(QVector states, glm::mat4 parentTransform, - int rootJointIndex, - int leftHandJointIndex, - int leftElbowJointIndex, - int leftShoulderJointIndex, - int rightHandJointIndex, - int rightElbowJointIndex, - int rightShoulderJointIndex) { +void Rig::initJointStates(QVector states, glm::mat4 parentTransform, + int rootJointIndex, + int leftHandJointIndex, + int leftElbowJointIndex, + int leftShoulderJointIndex, + int rightHandJointIndex, + int rightElbowJointIndex, + int rightShoulderJointIndex) { _jointStates = states; _rootJointIndex = rootJointIndex; @@ -206,19 +206,12 @@ float Rig::initJointStates(QVector states, glm::mat4 parentTransform initJointTransforms(parentTransform); int numStates = _jointStates.size(); - float radius = 0.0f; for (int i = 0; i < numStates; ++i) { - float distance = glm::length(_jointStates[i].getPosition()); - if (distance > radius) { - radius = distance; - } _jointStates[i].buildConstraint(); } for (int i = 0; i < _jointStates.size(); i++) { _jointStates[i].slaveVisibleTransform(); } - - return radius; } // We could build and cache a dictionary, too.... diff --git a/libraries/animation/src/Rig.h b/libraries/animation/src/Rig.h index 8da20062cf..a574807eb4 100644 --- a/libraries/animation/src/Rig.h +++ b/libraries/animation/src/Rig.h @@ -92,14 +92,14 @@ public: float priority = 1.0f, bool loop = false, bool hold = false, float firstFrame = 0.0f, float lastFrame = FLT_MAX, const QStringList& maskedJoints = QStringList(), bool startAutomatically = false); - float initJointStates(QVector states, glm::mat4 parentTransform, - int rootJointIndex, - int leftHandJointIndex, - int leftElbowJointIndex, - int leftShoulderJointIndex, - int rightHandJointIndex, - int rightElbowJointIndex, - int rightShoulderJointIndex); + void initJointStates(QVector states, glm::mat4 parentTransform, + int rootJointIndex, + int leftHandJointIndex, + int leftElbowJointIndex, + int leftShoulderJointIndex, + int rightHandJointIndex, + int rightElbowJointIndex, + int rightShoulderJointIndex); bool jointStatesEmpty() { return _jointStates.isEmpty(); }; int getJointStateCount() const { return _jointStates.size(); } int indexOfJoint(const QString& jointName) ; diff --git a/libraries/display-plugins/src/display-plugins/OpenGLDisplayPlugin.cpp b/libraries/display-plugins/src/display-plugins/OpenGLDisplayPlugin.cpp index a97b58a55e..4101bebfa8 100644 --- a/libraries/display-plugins/src/display-plugins/OpenGLDisplayPlugin.cpp +++ b/libraries/display-plugins/src/display-plugins/OpenGLDisplayPlugin.cpp @@ -80,33 +80,33 @@ void OpenGLDisplayPlugin::deactivate() { // Pass input events on to the application bool OpenGLDisplayPlugin::eventFilter(QObject* receiver, QEvent* event) { switch (event->type()) { - case QEvent::MouseButtonPress: - case QEvent::MouseButtonRelease: - case QEvent::MouseButtonDblClick: - case QEvent::MouseMove: - case QEvent::Wheel: + case QEvent::MouseButtonPress: + case QEvent::MouseButtonRelease: + case QEvent::MouseButtonDblClick: + case QEvent::MouseMove: + case QEvent::Wheel: - case QEvent::TouchBegin: - case QEvent::TouchEnd: - case QEvent::TouchUpdate: + case QEvent::TouchBegin: + case QEvent::TouchEnd: + case QEvent::TouchUpdate: - case QEvent::FocusIn: - case QEvent::FocusOut: + case QEvent::FocusIn: + case QEvent::FocusOut: - case QEvent::KeyPress: - case QEvent::KeyRelease: - case QEvent::ShortcutOverride: + case QEvent::KeyPress: + case QEvent::KeyRelease: + case QEvent::ShortcutOverride: - case QEvent::DragEnter: - case QEvent::Drop: + case QEvent::DragEnter: + case QEvent::Drop: - case QEvent::Resize: - if (QCoreApplication::sendEvent(QCoreApplication::instance(), event)) { - return true; - } - break; - default: - break; + case QEvent::Resize: + if (QCoreApplication::sendEvent(QCoreApplication::instance(), event)) { + return true; + } + break; + default: + break; } return false; } diff --git a/libraries/display-plugins/src/display-plugins/oculus/OculusDisplayPlugin.h b/libraries/display-plugins/src/display-plugins/oculus/OculusDisplayPlugin.h index 75173fd2bd..42f8d5763f 100644 --- a/libraries/display-plugins/src/display-plugins/oculus/OculusDisplayPlugin.h +++ b/libraries/display-plugins/src/display-plugins/oculus/OculusDisplayPlugin.h @@ -51,20 +51,22 @@ protected: private: static const QString NAME; - - ovrHmd _hmd; - float _ipd{ OVR_DEFAULT_IPD }; - unsigned int _frameIndex; - ovrEyeRenderDesc _eyeRenderDescs[2]; + ovrPosef _eyePoses[2]; - ovrVector3f _eyeOffsets[2]; - ovrFovPort _eyeFovs[2]; + mat4 _eyeProjections[3]; mat4 _compositeEyeProjections[2]; uvec2 _desiredFramebufferSize; ovrTrackingState _trackingState; #if (OVR_MAJOR_VERSION >= 6) + ovrHmd _hmd; + float _ipd{ OVR_DEFAULT_IPD }; + unsigned int _frameIndex; + ovrEyeRenderDesc _eyeRenderDescs[2]; + ovrVector3f _eyeOffsets[2]; + ovrFovPort _eyeFovs[2]; + ovrLayerEyeFov& getSceneLayer(); ovrHmdDesc _hmdDesc; SwapFboPtr _sceneFbo; diff --git a/libraries/display-plugins/src/display-plugins/oculus/OculusLegacyDisplayPlugin.cpp b/libraries/display-plugins/src/display-plugins/oculus/OculusLegacyDisplayPlugin.cpp index c94268b336..05072e0210 100644 --- a/libraries/display-plugins/src/display-plugins/oculus/OculusLegacyDisplayPlugin.cpp +++ b/libraries/display-plugins/src/display-plugins/oculus/OculusLegacyDisplayPlugin.cpp @@ -34,7 +34,7 @@ const QString & OculusLegacyDisplayPlugin::getName() const { return NAME; } -OculusLegacyDisplayPlugin::OculusLegacyDisplayPlugin() : _ipd(OVR_DEFAULT_IPD) { +OculusLegacyDisplayPlugin::OculusLegacyDisplayPlugin() { } uvec2 OculusLegacyDisplayPlugin::getRecommendedRenderSize() const { diff --git a/libraries/display-plugins/src/display-plugins/oculus/OculusLegacyDisplayPlugin.h b/libraries/display-plugins/src/display-plugins/oculus/OculusLegacyDisplayPlugin.h index 219b6c54b3..5bce032948 100644 --- a/libraries/display-plugins/src/display-plugins/oculus/OculusLegacyDisplayPlugin.h +++ b/libraries/display-plugins/src/display-plugins/oculus/OculusLegacyDisplayPlugin.h @@ -44,7 +44,6 @@ protected: private: static const QString NAME; - float _ipd{ OVR_DEFAULT_IPD }; ovrHmd _hmd; unsigned int _frameIndex; ovrTrackingState _trackingState; diff --git a/libraries/display-plugins/src/display-plugins/stereo/StereoDisplayPlugin.cpp b/libraries/display-plugins/src/display-plugins/stereo/StereoDisplayPlugin.cpp index ae3c1f29e2..de34451c83 100644 --- a/libraries/display-plugins/src/display-plugins/stereo/StereoDisplayPlugin.cpp +++ b/libraries/display-plugins/src/display-plugins/stereo/StereoDisplayPlugin.cpp @@ -78,7 +78,7 @@ void StereoDisplayPlugin::activate() { } void StereoDisplayPlugin::updateScreen() { - for (uint32_t i = 0; i < _screenActions.size(); ++i) { + for (int i = 0; i < (int) _screenActions.size(); ++i) { if (_screenActions[i]->isChecked()) { CONTAINER->setFullscreen(qApp->screens().at(i)); break; diff --git a/libraries/entities-renderer/src/RenderablePolyVoxEntityItem.cpp b/libraries/entities-renderer/src/RenderablePolyVoxEntityItem.cpp index 5d139a719a..46c4986fa8 100644 --- a/libraries/entities-renderer/src/RenderablePolyVoxEntityItem.cpp +++ b/libraries/entities-renderer/src/RenderablePolyVoxEntityItem.cpp @@ -10,7 +10,9 @@ // #include +#include #include +#include #if defined(__GNUC__) && !defined(__clang__) #pragma GCC diagnostic push @@ -33,15 +35,15 @@ #include #include #include -#include #include #include "model/Geometry.h" -#include "gpu/Context.h" #include "EntityTreeRenderer.h" #include "polyvox_vert.h" #include "polyvox_frag.h" #include "RenderablePolyVoxEntityItem.h" +#include "EntityEditPacketSender.h" +#include "PhysicalEntitySimulation.h" gpu::PipelinePointer RenderablePolyVoxEntityItem::_pipeline = nullptr; const float MARCHING_CUBE_COLLISION_HULL_OFFSET = 0.5; @@ -53,135 +55,61 @@ EntityItemPointer RenderablePolyVoxEntityItem::factory(const EntityItemID& entit RenderablePolyVoxEntityItem::RenderablePolyVoxEntityItem(const EntityItemID& entityItemID, const EntityItemProperties& properties) : PolyVoxEntityItem(entityItemID, properties), + _mesh(new model::Mesh()), + _meshDirty(true), _xTexture(nullptr), _yTexture(nullptr), _zTexture(nullptr) { - model::Mesh* mesh = new model::Mesh(); - model::MeshPointer meshPtr(mesh); - _modelGeometry.setMesh(meshPtr); - setVoxelVolumeSize(_voxelVolumeSize); + getMeshAsync(); } RenderablePolyVoxEntityItem::~RenderablePolyVoxEntityItem() { - delete _volData; -} - -bool inUserBounds(const PolyVox::SimpleVolume* vol, PolyVoxEntityItem::PolyVoxSurfaceStyle surfaceStyle, - int x, int y, int z) { - // x, y, z are in user voxel-coords, not adjusted-for-edge voxel-coords. - switch (surfaceStyle) { - case PolyVoxEntityItem::SURFACE_MARCHING_CUBES: - case PolyVoxEntityItem::SURFACE_CUBIC: - if (x < 0 || y < 0 || z < 0 || - x >= vol->getWidth() || y >= vol->getHeight() || z >= vol->getDepth()) { - return false; - } - return true; - - case PolyVoxEntityItem::SURFACE_EDGED_CUBIC: - case PolyVoxEntityItem::SURFACE_EDGED_MARCHING_CUBES: - if (x < 0 || y < 0 || z < 0 || - x >= vol->getWidth() - 2 || y >= vol->getHeight() - 2 || z >= vol->getDepth() - 2) { - return false; - } - return true; - } - - return false; } -bool inBounds(const PolyVox::SimpleVolume* vol, int x, int y, int z) { - // x, y, z are in polyvox volume coords - return !(x < 0 || y < 0 || z < 0 || x >= vol->getWidth() || y >= vol->getHeight() || z >= vol->getDepth()); -} - - -void RenderablePolyVoxEntityItem::setVoxelVolumeSize(glm::vec3 voxelVolumeSize) { - if (_volData && voxelVolumeSize == _voxelVolumeSize) { +void RenderablePolyVoxEntityItem::setVoxelData(QByteArray voxelData) { + _voxelDataLock.lockForWrite(); + if (_voxelData == voxelData) { + _voxelDataLock.unlock(); return; } - #ifdef WANT_DEBUG - qDebug() << "resetting voxel-space size" << voxelVolumeSize.x << voxelVolumeSize.y << voxelVolumeSize.z; - #endif - - PolyVoxEntityItem::setVoxelVolumeSize(voxelVolumeSize); - - if (_volData) { - delete _volData; - } - - _onCount = 0; - - if (_voxelSurfaceStyle == SURFACE_EDGED_CUBIC || _voxelSurfaceStyle == SURFACE_EDGED_MARCHING_CUBES) { - // with _EDGED_ we maintain an extra box of voxels around those that the user asked for. This - // changes how the surface extractor acts -- mainly it becomes impossible to have holes in the - // generated mesh. The non _EDGED_ modes will leave holes in the mesh at the edges of the - // voxel space. - PolyVox::Vector3DInt32 lowCorner(0, 0, 0); - PolyVox::Vector3DInt32 highCorner(_voxelVolumeSize.x + 1, // -1 + 2 because these corners are inclusive - _voxelVolumeSize.y + 1, - _voxelVolumeSize.z + 1); - _volData = new PolyVox::SimpleVolume(PolyVox::Region(lowCorner, highCorner)); - } else { - PolyVox::Vector3DInt32 lowCorner(0, 0, 0); - PolyVox::Vector3DInt32 highCorner(_voxelVolumeSize.x - 1, // -1 because these corners are inclusive - _voxelVolumeSize.y - 1, - _voxelVolumeSize.z - 1); - _volData = new PolyVox::SimpleVolume(PolyVox::Region(lowCorner, highCorner)); - } - - // having the "outside of voxel-space" value be 255 has helped me notice some problems. - _volData->setBorderValue(255); - - #ifdef WANT_DEBUG - qDebug() << " new voxel-space size is" << _volData->getWidth() << _volData->getHeight() << _volData->getDepth(); - #endif - - // I'm not sure this is needed... the docs say that each element is initialized with its default - // constructor. I'll leave it here for now. - for (int z = 0; z < _volData->getDepth(); z++) { - for (int y = 0; y < _volData->getHeight(); y++) { - for (int x = 0; x < _volData->getWidth(); x++) { - _volData->setVoxelAt(x, y, z, 0); - } - } - } - - // It's okay to decompress the old data here, because the data includes its original dimensions along - // with the voxel data, and writing voxels outside the bounds of the new space is harmless. This allows - // adjusting of the voxel-space size without overly mangling the shape. Shrinking the space and then - // restoring the previous size (without any edits in between) will put the original shape back. + _voxelData = voxelData; + _voxelDataDirty = true; + _voxelDataLock.unlock(); decompressVolumeData(); } -void RenderablePolyVoxEntityItem::updateVoxelSurfaceStyle(PolyVoxSurfaceStyle voxelSurfaceStyle) { + +void RenderablePolyVoxEntityItem::setVoxelSurfaceStyle(PolyVoxSurfaceStyle voxelSurfaceStyle) { + if (_voxelSurfaceStyle == voxelSurfaceStyle) { + return; + } + // if we are switching to or from "edged" we need to force a resize of _volData. - bool wasEdged = (_voxelSurfaceStyle == SURFACE_EDGED_CUBIC || _voxelSurfaceStyle == SURFACE_EDGED_MARCHING_CUBES); - bool willBeEdged = (voxelSurfaceStyle == SURFACE_EDGED_CUBIC || voxelSurfaceStyle == SURFACE_EDGED_MARCHING_CUBES); + bool wasEdged = (_voxelSurfaceStyle == PolyVoxEntityItem::SURFACE_EDGED_CUBIC || + _voxelSurfaceStyle == PolyVoxEntityItem::SURFACE_EDGED_MARCHING_CUBES); + bool willBeEdged = (voxelSurfaceStyle == PolyVoxEntityItem::SURFACE_EDGED_CUBIC || + voxelSurfaceStyle == PolyVoxEntityItem::SURFACE_EDGED_MARCHING_CUBES); if (wasEdged != willBeEdged) { + _volDataLock.lockForWrite(); + _volDataDirty = true; if (_volData) { delete _volData; } _volData = nullptr; _voxelSurfaceStyle = voxelSurfaceStyle; + _volDataLock.unlock(); setVoxelVolumeSize(_voxelVolumeSize); + decompressVolumeData(); } else { _voxelSurfaceStyle = voxelSurfaceStyle; + getMesh(); } - _needsModelReload = true; } -void RenderablePolyVoxEntityItem::setVoxelData(QByteArray voxelData) { - if (voxelData == _voxelData) { - return; - } - PolyVoxEntityItem::setVoxelData(voxelData); - decompressVolumeData(); -} glm::vec3 RenderablePolyVoxEntityItem::getSurfacePositionAdjustment() const { glm::vec3 scale = getDimensions() / _voxelVolumeSize; // meters / voxel-units @@ -196,6 +124,7 @@ glm::vec3 RenderablePolyVoxEntityItem::getSurfacePositionAdjustment() const { return glm::vec3(0.0f, 0.0f, 0.0f); } + glm::mat4 RenderablePolyVoxEntityItem::voxelToLocalMatrix() const { glm::vec3 scale = getDimensions() / _voxelVolumeSize; // meters / voxel-units glm::vec3 center = getCenterPosition(); @@ -223,99 +152,34 @@ glm::mat4 RenderablePolyVoxEntityItem::worldToVoxelMatrix() const { return worldToModelMatrix; } -uint8_t RenderablePolyVoxEntityItem::getVoxel(int x, int y, int z) { - assert(_volData); - if (!inUserBounds(_volData, _voxelSurfaceStyle, x, y, z)) { - return 0; - } - - // if _voxelSurfaceStyle is SURFACE_EDGED_CUBIC, we maintain an extra layer of - // voxels all around the requested voxel space. Having the empty voxels around - // the edges changes how the surface extractor behaves. - - if (_voxelSurfaceStyle == SURFACE_EDGED_CUBIC || _voxelSurfaceStyle == SURFACE_EDGED_MARCHING_CUBES) { - return _volData->getVoxelAt(x + 1, y + 1, z + 1); - } - return _volData->getVoxelAt(x, y, z); -} - -bool RenderablePolyVoxEntityItem::setVoxelInternal(int x, int y, int z, uint8_t toValue) { - // set a voxel without recompressing the voxel data - assert(_volData); - bool result = false; - if (!inUserBounds(_volData, _voxelSurfaceStyle, x, y, z)) { - return false; - } - - result = updateOnCount(x, y, z, toValue); - - if (_voxelSurfaceStyle == SURFACE_EDGED_CUBIC || _voxelSurfaceStyle == SURFACE_EDGED_MARCHING_CUBES) { - _volData->setVoxelAt(x + 1, y + 1, z + 1, toValue); - } else { - _volData->setVoxelAt(x, y, z, toValue); - } - - return result; -} - -void RenderablePolyVoxEntityItem::clearEdges() { - // if we are in an edged mode, make sure the outside surfaces are zeroed out. - if (_voxelSurfaceStyle == SURFACE_EDGED_CUBIC || _voxelSurfaceStyle == SURFACE_EDGED_MARCHING_CUBES) { - for (int z = 0; z < _volData->getDepth(); z++) { - for (int y = 0; y < _volData->getHeight(); y++) { - for (int x = 0; x < _volData->getWidth(); x++) { - if (x == 0 || y == 0 || z == 0 || - x == _volData->getWidth() - 1 || - y == _volData->getHeight() - 1 || - z == _volData->getDepth() - 1) { - _volData->setVoxelAt(x, y, z, 0); - } - } - } - } - } -} bool RenderablePolyVoxEntityItem::setVoxel(int x, int y, int z, uint8_t toValue) { if (_locked) { return false; } + + _volDataLock.lockForWrite(); bool result = setVoxelInternal(x, y, z, toValue); if (result) { - compressVolumeData(); + _volDataDirty = true; } + _volDataLock.unlock(); + if (result) { + compressVolumeDataAndSendEditPacket(); + } + return result; } -bool RenderablePolyVoxEntityItem::updateOnCount(int x, int y, int z, uint8_t toValue) { - // keep _onCount up to date - if (!inUserBounds(_volData, _voxelSurfaceStyle, x, y, z)) { - return false; - } - - uint8_t uVoxelValue = getVoxel(x, y, z); - if (toValue != 0) { - if (uVoxelValue == 0) { - _onCount++; - return true; - } - } else { - // toValue == 0 - if (uVoxelValue != 0) { - _onCount--; - assert(_onCount >= 0); - return true; - } - } - return false; -} bool RenderablePolyVoxEntityItem::setAll(uint8_t toValue) { bool result = false; if (_locked) { - return false; + return result; } + _volDataLock.lockForWrite(); + _volDataDirty = true; for (int z = 0; z < _voxelVolumeSize.z; z++) { for (int y = 0; y < _voxelVolumeSize.y; y++) { for (int x = 0; x < _voxelVolumeSize.x; x++) { @@ -323,12 +187,15 @@ bool RenderablePolyVoxEntityItem::setAll(uint8_t toValue) { } } } + _volDataLock.unlock(); if (result) { - compressVolumeData(); + compressVolumeDataAndSendEditPacket(); } return result; } + + bool RenderablePolyVoxEntityItem::setVoxelInVolume(glm::vec3 position, uint8_t toValue) { if (_locked) { return false; @@ -341,10 +208,12 @@ bool RenderablePolyVoxEntityItem::setVoxelInVolume(glm::vec3 position, uint8_t t bool RenderablePolyVoxEntityItem::setSphereInVolume(glm::vec3 center, float radius, uint8_t toValue) { bool result = false; if (_locked) { - return false; + return result; } // This three-level for loop iterates over every voxel in the volume + _volDataLock.lockForWrite(); + _volDataDirty = true; for (int z = 0; z < _voxelVolumeSize.z; z++) { for (int y = 0; y < _voxelVolumeSize.y; y++) { for (int x = 0; x < _voxelVolumeSize.x; x++) { @@ -352,15 +221,16 @@ bool RenderablePolyVoxEntityItem::setSphereInVolume(glm::vec3 center, float radi glm::vec3 pos(x + 0.5f, y + 0.5f, z + 0.5f); // consider voxels cenetered on their coordinates // And compute how far the current position is from the center of the volume float fDistToCenter = glm::distance(pos, center); - // If the current voxel is less than 'radius' units from the center then we make it solid. + // If the current voxel is less than 'radius' units from the center then we set its value if (fDistToCenter <= radius) { result |= setVoxelInternal(x, y, z, toValue); } } } } + _volDataLock.unlock(); if (result) { - compressVolumeData(); + compressVolumeDataAndSendEditPacket(); } return result; } @@ -381,6 +251,13 @@ public: _result(glm::vec4(0.0f, 0.0f, 0.0f, 1.0f)), _vol(vol) { } + + static bool inBounds(const PolyVox::SimpleVolume* vol, int x, int y, int z) { + // x, y, z are in polyvox volume coords + return !(x < 0 || y < 0 || z < 0 || x >= vol->getWidth() || y >= vol->getHeight() || z >= vol->getDepth()); + } + + bool operator()(PolyVox::SimpleVolume::Sampler& sampler) { PolyVox::Vector3DInt32 positionIndex = sampler.getPosition(); @@ -412,8 +289,7 @@ bool RenderablePolyVoxEntityItem::findDetailedRayIntersection(const glm::vec3& o bool precisionPicking) const { // TODO -- correctly pick against marching-cube generated meshes - - if (_needsModelReload || !precisionPicking) { + if (!precisionPicking) { // just intersect with bounding box return true; } @@ -431,21 +307,13 @@ bool RenderablePolyVoxEntityItem::findDetailedRayIntersection(const glm::vec3& o glm::vec4 originInVoxel = wtvMatrix * glm::vec4(origin, 1.0f); glm::vec4 farInVoxel = wtvMatrix * glm::vec4(farPoint, 1.0f); - PolyVox::Vector3DFloat startPoint(originInVoxel.x, originInVoxel.y, originInVoxel.z); - PolyVox::Vector3DFloat endPoint(farInVoxel.x, farInVoxel.y, farInVoxel.z); - - PolyVox::RaycastResult raycastResult; - RaycastFunctor callback(_volData); - raycastResult = PolyVox::raycastWithEndpoints(_volData, startPoint, endPoint, callback); - + glm::vec4 result; + PolyVox::RaycastResult raycastResult = doRayCast(originInVoxel, farInVoxel, result); if (raycastResult == PolyVox::RaycastResults::Completed) { // the ray completed its path -- nothing was hit. return false; } - // result is in voxel-space coordinates. - glm::vec4 result = callback._result - glm::vec4(0.5f, 0.5f, 0.5f, 0.0f); - // set up ray tests against each face of the voxel. glm::vec3 minXPosition = glm::vec3(vtwMatrix * (result + glm::vec4(0.0f, 0.5f, 0.5f, 0.0f))); glm::vec3 maxXPosition = glm::vec3(vtwMatrix * (result + glm::vec4(1.0f, 0.5f, 0.5f, 0.0f))); @@ -500,269 +368,43 @@ bool RenderablePolyVoxEntityItem::findDetailedRayIntersection(const glm::vec3& o } -// compress the data in _volData and save the results. The compressed form is used during -// saves to disk and for transmission over the wire -void RenderablePolyVoxEntityItem::compressVolumeData() { - #ifdef WANT_DEBUG - auto startTime = usecTimestampNow(); - #endif +PolyVox::RaycastResult RenderablePolyVoxEntityItem::doRayCast(glm::vec4 originInVoxel, + glm::vec4 farInVoxel, + glm::vec4& result) const { + PolyVox::Vector3DFloat startPoint(originInVoxel.x, originInVoxel.y, originInVoxel.z); + PolyVox::Vector3DFloat endPoint(farInVoxel.x, farInVoxel.y, farInVoxel.z); - quint16 voxelXSize = _voxelVolumeSize.x; - quint16 voxelYSize = _voxelVolumeSize.y; - quint16 voxelZSize = _voxelVolumeSize.z; - int rawSize = voxelXSize * voxelYSize * voxelZSize; + _volDataLock.lockForRead(); + RaycastFunctor callback(_volData); + PolyVox::RaycastResult raycastResult = PolyVox::raycastWithEndpoints(_volData, startPoint, endPoint, callback); + _volDataLock.unlock(); - QByteArray uncompressedData = QByteArray(rawSize, '\0'); - - for (int z = 0; z < voxelZSize; z++) { - for (int y = 0; y < voxelYSize; y++) { - for (int x = 0; x < voxelXSize; x++) { - uint8_t uVoxelValue = getVoxel(x, y, z); - int uncompressedIndex = - z * voxelYSize * voxelXSize + - y * voxelXSize + - x; - uncompressedData[uncompressedIndex] = uVoxelValue; - } - } - } - - QByteArray newVoxelData; - QDataStream writer(&newVoxelData, QIODevice::WriteOnly | QIODevice::Truncate); - - #ifdef WANT_DEBUG - qDebug() << "compressing voxel data of size:" << voxelXSize << voxelYSize << voxelZSize; - #endif - - writer << voxelXSize << voxelYSize << voxelZSize; - - QByteArray compressedData = qCompress(uncompressedData, 9); - writer << compressedData; - - // make sure the compressed data can be sent over the wire-protocol - if (newVoxelData.size() < 1150) { - _voxelData = newVoxelData; - #ifdef WANT_DEBUG - qDebug() << "-------------- voxel compresss --------------"; - qDebug() << "raw-size =" << rawSize << " compressed-size =" << newVoxelData.size(); - #endif - } else { - // HACK -- until we have a way to allow for properties larger than MTU, don't update. - #ifdef WANT_DEBUG - qDebug() << "voxel data too large, reverting change."; - #endif - // revert the active voxel-space to the last version that fit. - decompressVolumeData(); - } - - _dirtyFlags |= EntityItem::DIRTY_SHAPE | EntityItem::DIRTY_MASS; - _needsModelReload = true; - - #ifdef WANT_DEBUG - qDebug() << "RenderablePolyVoxEntityItem::compressVolumeData" << (usecTimestampNow() - startTime) << getName(); - #endif + // result is in voxel-space coordinates. + result = callback._result - glm::vec4(0.5f, 0.5f, 0.5f, 0.0f); + return raycastResult; } -// take compressed data and expand it into _volData. -void RenderablePolyVoxEntityItem::decompressVolumeData() { - QDataStream reader(_voxelData); - quint16 voxelXSize, voxelYSize, voxelZSize; - reader >> voxelXSize; - reader >> voxelYSize; - reader >> voxelZSize; - - if (voxelXSize == 0 || voxelXSize > MAX_VOXEL_DIMENSION || - voxelYSize == 0 || voxelYSize > MAX_VOXEL_DIMENSION || - voxelZSize == 0 || voxelZSize > MAX_VOXEL_DIMENSION) { - qDebug() << "voxelSize is not reasonable, skipping decompressions." - << voxelXSize << voxelYSize << voxelZSize; - return; - } - - int rawSize = voxelXSize * voxelYSize * voxelZSize; - - QByteArray compressedData; - reader >> compressedData; - QByteArray uncompressedData = qUncompress(compressedData); - - if (uncompressedData.size() != rawSize) { - qDebug() << "PolyVox decompress -- size is (" << voxelXSize << voxelYSize << voxelZSize << ")" << - "so expected uncompressed length of" << rawSize << "but length is" << uncompressedData.size(); - return; - } - - for (int z = 0; z < voxelZSize; z++) { - for (int y = 0; y < voxelYSize; y++) { - for (int x = 0; x < voxelXSize; x++) { - int uncompressedIndex = (z * voxelYSize * voxelXSize) + (y * voxelZSize) + x; - setVoxelInternal(x, y, z, uncompressedData[uncompressedIndex]); - } - } - } - clearEdges(); - - #ifdef WANT_DEBUG - qDebug() << "--------------- voxel decompress ---------------"; - qDebug() << "raw-size =" << rawSize << " compressed-size =" << _voxelData.size(); - #endif - - _dirtyFlags |= EntityItem::DIRTY_SHAPE | EntityItem::DIRTY_MASS; - _needsModelReload = true; - getModel(); -} - // virtual ShapeType RenderablePolyVoxEntityItem::getShapeType() const { - if (_onCount > 0) { - return SHAPE_TYPE_COMPOUND; - } - return SHAPE_TYPE_NONE; + return SHAPE_TYPE_COMPOUND; } - bool RenderablePolyVoxEntityItem::isReadyToComputeShape() { - if (_needsModelReload) { + _meshLock.lockForRead(); + if (_meshDirty) { + _meshLock.unlock(); + computeShapeInfoWorker(); return false; } - - #ifdef WANT_DEBUG - qDebug() << "RenderablePolyVoxEntityItem::isReadyToComputeShape" << (!_needsModelReload); - #endif + _meshLock.unlock(); return true; } void RenderablePolyVoxEntityItem::computeShapeInfo(ShapeInfo& info) { - #ifdef WANT_DEBUG - qDebug() << "RenderablePolyVoxEntityItem::computeShapeInfo"; - #endif - ShapeType type = getShapeType(); - if (type != SHAPE_TYPE_COMPOUND) { - EntityItem::computeShapeInfo(info); - return; - } - - _points.clear(); - AABox box; - glm::mat4 vtoM = voxelToLocalMatrix(); - - if (_voxelSurfaceStyle == PolyVoxEntityItem::SURFACE_MARCHING_CUBES || - _voxelSurfaceStyle == PolyVoxEntityItem::SURFACE_EDGED_MARCHING_CUBES) { - unsigned int i = 0; - /* pull top-facing triangles into polyhedrons so they can be walked on */ - const model::MeshPointer& mesh = _modelGeometry.getMesh(); - const gpu::BufferView vertexBufferView = mesh->getVertexBuffer(); - const gpu::BufferView& indexBufferView = mesh->getIndexBuffer(); - gpu::BufferView::Iterator it = indexBufferView.cbegin(); - while (it != indexBufferView.cend()) { - uint32_t p0Index = *(it++); - uint32_t p1Index = *(it++); - uint32_t p2Index = *(it++); - - const glm::vec3& p0 = vertexBufferView.get(p0Index); - const glm::vec3& p1 = vertexBufferView.get(p1Index); - const glm::vec3& p2 = vertexBufferView.get(p2Index); - - glm::vec3 av = (p0 + p1 + p2) / 3.0f; // center of the triangular face - glm::vec3 normal = glm::normalize(glm::cross(p1 - p0, p2 - p0)); - glm::vec3 p3 = av - normal * MARCHING_CUBE_COLLISION_HULL_OFFSET; - - glm::vec3 p0Model = glm::vec3(vtoM * glm::vec4(p0, 1.0f)); - glm::vec3 p1Model = glm::vec3(vtoM * glm::vec4(p1, 1.0f)); - glm::vec3 p2Model = glm::vec3(vtoM * glm::vec4(p2, 1.0f)); - glm::vec3 p3Model = glm::vec3(vtoM * glm::vec4(p3, 1.0f)); - - box += p0Model; - box += p1Model; - box += p2Model; - box += p3Model; - - QVector pointsInPart; - pointsInPart << p0Model; - pointsInPart << p1Model; - pointsInPart << p2Model; - pointsInPart << p3Model; - // add next convex hull - QVector newMeshPoints; - _points << newMeshPoints; - // add points to the new convex hull - _points[i++] << pointsInPart; - } - } else { - unsigned int i = 0; - - for (int z = 0; z < _voxelVolumeSize.z; z++) { - for (int y = 0; y < _voxelVolumeSize.y; y++) { - for (int x = 0; x < _voxelVolumeSize.x; x++) { - if (getVoxel(x, y, z) > 0) { - - if ((x > 0 && getVoxel(x - 1, y, z) > 0) && - (y > 0 && getVoxel(x, y - 1, z) > 0) && - (z > 0 && getVoxel(x, y, z - 1) > 0) && - (x < _voxelVolumeSize.x - 1 && getVoxel(x + 1, y, z) > 0) && - (y < _voxelVolumeSize.y - 1 && getVoxel(x, y + 1, z) > 0) && - (z < _voxelVolumeSize.z - 1 && getVoxel(x, y, z + 1) > 0)) { - // this voxel has neighbors in every cardinal direction, so there's no need - // to include it in the collision hull. - continue; - } - - QVector pointsInPart; - - float offL = -0.5f; - float offH = 0.5f; - if (_voxelSurfaceStyle == PolyVoxEntityItem::SURFACE_EDGED_CUBIC) { - offL += 1.0f; - offH += 1.0f; - } - - glm::vec3 p000 = glm::vec3(vtoM * glm::vec4(x + offL, y + offL, z + offL, 1.0f)); - glm::vec3 p001 = glm::vec3(vtoM * glm::vec4(x + offL, y + offL, z + offH, 1.0f)); - glm::vec3 p010 = glm::vec3(vtoM * glm::vec4(x + offL, y + offH, z + offL, 1.0f)); - glm::vec3 p011 = glm::vec3(vtoM * glm::vec4(x + offL, y + offH, z + offH, 1.0f)); - glm::vec3 p100 = glm::vec3(vtoM * glm::vec4(x + offH, y + offL, z + offL, 1.0f)); - glm::vec3 p101 = glm::vec3(vtoM * glm::vec4(x + offH, y + offL, z + offH, 1.0f)); - glm::vec3 p110 = glm::vec3(vtoM * glm::vec4(x + offH, y + offH, z + offL, 1.0f)); - glm::vec3 p111 = glm::vec3(vtoM * glm::vec4(x + offH, y + offH, z + offH, 1.0f)); - - box += p000; - box += p001; - box += p010; - box += p011; - box += p100; - box += p101; - box += p110; - box += p111; - - pointsInPart << p000; - pointsInPart << p001; - pointsInPart << p010; - pointsInPart << p011; - pointsInPart << p100; - pointsInPart << p101; - pointsInPart << p110; - pointsInPart << p111; - - // add next convex hull - QVector newMeshPoints; - _points << newMeshPoints; - // add points to the new convex hull - _points[i++] << pointsInPart; - } - } - } - } - } - - if (_points.isEmpty()) { - EntityItem::computeShapeInfo(info); - return; - } - - glm::vec3 collisionModelDimensions = box.getDimensions(); - QByteArray b64 = _voxelData.toBase64(); - info.setParams(type, collisionModelDimensions, QString(b64)); - info.setConvexHulls(_points); + _shapeInfoLock.lockForRead(); + info = _shapeInfo; + _shapeInfoLock.unlock(); } void RenderablePolyVoxEntityItem::setXTextureURL(QString xTextureURL) { @@ -777,76 +419,21 @@ void RenderablePolyVoxEntityItem::setZTextureURL(QString zTextureURL) { PolyVoxEntityItem::setZTextureURL(zTextureURL); } -void RenderablePolyVoxEntityItem::getModel() { - #ifdef WANT_DEBUG - auto startTime = usecTimestampNow(); - #endif - - // A mesh object to hold the result of surface extraction - PolyVox::SurfaceMesh polyVoxMesh; - - switch (_voxelSurfaceStyle) { - case PolyVoxEntityItem::SURFACE_EDGED_MARCHING_CUBES: - case PolyVoxEntityItem::SURFACE_MARCHING_CUBES: { - PolyVox::MarchingCubesSurfaceExtractor> surfaceExtractor - (_volData, _volData->getEnclosingRegion(), &polyVoxMesh); - surfaceExtractor.execute(); - break; - } - case PolyVoxEntityItem::SURFACE_EDGED_CUBIC: - case PolyVoxEntityItem::SURFACE_CUBIC: { - PolyVox::CubicSurfaceExtractorWithNormals> surfaceExtractor - (_volData, _volData->getEnclosingRegion(), &polyVoxMesh); - surfaceExtractor.execute(); - break; - } - } - - // convert PolyVox mesh to a Sam mesh - auto mesh = _modelGeometry.getMesh(); - - const std::vector& vecIndices = polyVoxMesh.getIndices(); - auto indexBuffer = std::make_shared(vecIndices.size() * sizeof(uint32_t), - (gpu::Byte*)vecIndices.data()); - auto indexBufferPtr = gpu::BufferPointer(indexBuffer); - auto indexBufferView = new gpu::BufferView(indexBufferPtr, gpu::Element(gpu::SCALAR, gpu::UINT32, gpu::RAW)); - mesh->setIndexBuffer(*indexBufferView); - - - const std::vector& vecVertices = polyVoxMesh.getVertices(); - auto vertexBuffer = std::make_shared(vecVertices.size() * sizeof(PolyVox::PositionMaterialNormal), - (gpu::Byte*)vecVertices.data()); - auto vertexBufferPtr = gpu::BufferPointer(vertexBuffer); - auto vertexBufferView = new gpu::BufferView(vertexBufferPtr, - 0, - vertexBufferPtr->getSize() - sizeof(float) * 3, - sizeof(PolyVox::PositionMaterialNormal), - gpu::Element(gpu::VEC3, gpu::FLOAT, gpu::RAW)); - mesh->setVertexBuffer(*vertexBufferView); - mesh->addAttribute(gpu::Stream::NORMAL, - gpu::BufferView(vertexBufferPtr, - sizeof(float) * 3, - vertexBufferPtr->getSize() - sizeof(float) * 3, - sizeof(PolyVox::PositionMaterialNormal), - gpu::Element(gpu::VEC3, gpu::FLOAT, gpu::RAW))); - - #ifdef WANT_DEBUG - qDebug() << "---- vecIndices.size() =" << vecIndices.size(); - qDebug() << "---- vecVertices.size() =" << vecVertices.size(); - #endif - - _needsModelReload = false; - - #ifdef WANT_DEBUG - qDebug() << "RenderablePolyVoxEntityItem::getModel" << (usecTimestampNow() - startTime) << getName(); - #endif -} - void RenderablePolyVoxEntityItem::render(RenderArgs* args) { PerformanceTimer perfTimer("RenderablePolyVoxEntityItem::render"); assert(getType() == EntityTypes::PolyVox); Q_ASSERT(args->_batch); + _volDataLock.lockForRead(); + if (_volDataDirty) { + getMesh(); + } + _volDataLock.unlock(); + + _meshLock.lockForRead(); + model::MeshPointer mesh = _mesh; + _meshLock.unlock(); + if (!_pipeline) { gpu::ShaderPointer vertexShader = gpu::ShaderPointer(gpu::Shader::createVertex(std::string(polyvox_vert))); gpu::ShaderPointer pixelShader = gpu::ShaderPointer(gpu::Shader::createPixel(std::string(polyvox_frag))); @@ -867,14 +454,9 @@ void RenderablePolyVoxEntityItem::render(RenderArgs* args) { _pipeline = gpu::PipelinePointer(gpu::Pipeline::create(program, state)); } - if (_needsModelReload) { - getModel(); - } - gpu::Batch& batch = *args->_batch; batch.setPipeline(_pipeline); - auto mesh = _modelGeometry.getMesh(); Transform transform(voxelToWorldMatrix()); batch.setModelTransform(transform); batch.setInputFormat(mesh->getVertexFormat()); @@ -959,12 +541,25 @@ namespace render { } } + glm::vec3 RenderablePolyVoxEntityItem::voxelCoordsToWorldCoords(glm::vec3& voxelCoords) const { return glm::vec3(voxelToWorldMatrix() * glm::vec4(voxelCoords, 1.0f)); } glm::vec3 RenderablePolyVoxEntityItem::worldCoordsToVoxelCoords(glm::vec3& worldCoords) const { - return glm::vec3(worldToVoxelMatrix() * glm::vec4(worldCoords, 1.0f)); + glm::vec3 result = glm::vec3(worldToVoxelMatrix() * glm::vec4(worldCoords, 1.0f)); + switch (_voxelSurfaceStyle) { + case PolyVoxEntityItem::SURFACE_MARCHING_CUBES: + case PolyVoxEntityItem::SURFACE_CUBIC: + result += glm::vec3(0.5f, 0.5f, 0.5f); + break; + case PolyVoxEntityItem::SURFACE_EDGED_CUBIC: + case PolyVoxEntityItem::SURFACE_EDGED_MARCHING_CUBES: + result -= glm::vec3(0.5f, 0.5f, 0.5f); + break; + } + + return result; } glm::vec3 RenderablePolyVoxEntityItem::voxelCoordsToLocalCoords(glm::vec3& voxelCoords) const { @@ -974,3 +569,490 @@ glm::vec3 RenderablePolyVoxEntityItem::voxelCoordsToLocalCoords(glm::vec3& voxel glm::vec3 RenderablePolyVoxEntityItem::localCoordsToVoxelCoords(glm::vec3& localCoords) const { return glm::vec3(localToVoxelMatrix() * glm::vec4(localCoords, 0.0f)); } + + +void RenderablePolyVoxEntityItem::setVoxelVolumeSize(glm::vec3 voxelVolumeSize) { + if (_volData && _voxelVolumeSize == voxelVolumeSize) { + return; + } + + _volDataLock.lockForWrite(); + _volDataDirty = true; + _voxelVolumeSize = voxelVolumeSize; + + if (_volData) { + delete _volData; + } + _onCount = 0; + + if (_voxelSurfaceStyle == PolyVoxEntityItem::SURFACE_EDGED_CUBIC || + _voxelSurfaceStyle == PolyVoxEntityItem::SURFACE_EDGED_MARCHING_CUBES) { + // with _EDGED_ we maintain an extra box of voxels around those that the user asked for. This + // changes how the surface extractor acts -- mainly it becomes impossible to have holes in the + // generated mesh. The non _EDGED_ modes will leave holes in the mesh at the edges of the + // voxel space. + PolyVox::Vector3DInt32 lowCorner(0, 0, 0); + PolyVox::Vector3DInt32 highCorner(_voxelVolumeSize.x + 1, // corners are inclusive + _voxelVolumeSize.y + 1, + _voxelVolumeSize.z + 1); + _volData = new PolyVox::SimpleVolume(PolyVox::Region(lowCorner, highCorner)); + } else { + PolyVox::Vector3DInt32 lowCorner(0, 0, 0); + PolyVox::Vector3DInt32 highCorner(_voxelVolumeSize.x - 1, // -1 because these corners are inclusive + _voxelVolumeSize.y - 1, + _voxelVolumeSize.z - 1); + _volData = new PolyVox::SimpleVolume(PolyVox::Region(lowCorner, highCorner)); + } + + // having the "outside of voxel-space" value be 255 has helped me notice some problems. + _volData->setBorderValue(255); + _volDataLock.unlock(); + decompressVolumeData(); +} + + +bool RenderablePolyVoxEntityItem::inUserBounds(const PolyVox::SimpleVolume* vol, + PolyVoxEntityItem::PolyVoxSurfaceStyle surfaceStyle, + int x, int y, int z) { + // x, y, z are in user voxel-coords, not adjusted-for-edge voxel-coords. + switch (surfaceStyle) { + case PolyVoxEntityItem::SURFACE_MARCHING_CUBES: + case PolyVoxEntityItem::SURFACE_CUBIC: + if (x < 0 || y < 0 || z < 0 || + x >= vol->getWidth() || y >= vol->getHeight() || z >= vol->getDepth()) { + return false; + } + return true; + + case PolyVoxEntityItem::SURFACE_EDGED_CUBIC: + case PolyVoxEntityItem::SURFACE_EDGED_MARCHING_CUBES: + if (x < 0 || y < 0 || z < 0 || + x >= vol->getWidth() - 2 || y >= vol->getHeight() - 2 || z >= vol->getDepth() - 2) { + return false; + } + return true; + } + + return false; +} + + +uint8_t RenderablePolyVoxEntityItem::getVoxel(int x, int y, int z) { + _volDataLock.lockForRead(); + auto result = getVoxelInternal(x, y, z); + _volDataLock.unlock(); + return result; +} + + +uint8_t RenderablePolyVoxEntityItem::getVoxelInternal(int x, int y, int z) { + if (!inUserBounds(_volData, _voxelSurfaceStyle, x, y, z)) { + return 0; + } + + // if _voxelSurfaceStyle is SURFACE_EDGED_CUBIC, we maintain an extra layer of + // voxels all around the requested voxel space. Having the empty voxels around + // the edges changes how the surface extractor behaves. + + uint8_t result; + if (_voxelSurfaceStyle == PolyVoxEntityItem::SURFACE_EDGED_CUBIC || + _voxelSurfaceStyle == PolyVoxEntityItem::SURFACE_EDGED_MARCHING_CUBES) { + result = _volData->getVoxelAt(x + 1, y + 1, z + 1); + } else { + result = _volData->getVoxelAt(x, y, z); + } + + return result; +} + + +bool RenderablePolyVoxEntityItem::setVoxelInternal(int x, int y, int z, uint8_t toValue) { + // set a voxel without recompressing the voxel data + bool result = false; + if (!inUserBounds(_volData, _voxelSurfaceStyle, x, y, z)) { + return result; + } + + result = updateOnCount(x, y, z, toValue); + + assert(_volData); + if (_voxelSurfaceStyle == PolyVoxEntityItem::SURFACE_EDGED_CUBIC || + _voxelSurfaceStyle == PolyVoxEntityItem::SURFACE_EDGED_MARCHING_CUBES) { + _volData->setVoxelAt(x + 1, y + 1, z + 1, toValue); + } else { + _volData->setVoxelAt(x, y, z, toValue); + } + + return result; +} + + +bool RenderablePolyVoxEntityItem::updateOnCount(int x, int y, int z, uint8_t toValue) { + // keep _onCount up to date + if (!inUserBounds(_volData, _voxelSurfaceStyle, x, y, z)) { + return false; + } + + uint8_t uVoxelValue = getVoxelInternal(x, y, z); + if (toValue != 0) { + if (uVoxelValue == 0) { + _onCount++; + return true; + } + } else { + // toValue == 0 + if (uVoxelValue != 0) { + _onCount--; + assert(_onCount >= 0); + return true; + } + } + return false; +} + + +void RenderablePolyVoxEntityItem::decompressVolumeData() { + _threadRunning.acquire(); + QtConcurrent::run(this, &RenderablePolyVoxEntityItem::decompressVolumeDataAsync); +} + + +// take compressed data and expand it into _volData. +void RenderablePolyVoxEntityItem::decompressVolumeDataAsync() { + _voxelDataLock.lockForRead(); + QDataStream reader(_voxelData); + quint16 voxelXSize, voxelYSize, voxelZSize; + reader >> voxelXSize; + reader >> voxelYSize; + reader >> voxelZSize; + + if (voxelXSize == 0 || voxelXSize > PolyVoxEntityItem::MAX_VOXEL_DIMENSION || + voxelYSize == 0 || voxelYSize > PolyVoxEntityItem::MAX_VOXEL_DIMENSION || + voxelZSize == 0 || voxelZSize > PolyVoxEntityItem::MAX_VOXEL_DIMENSION) { + qDebug() << "voxelSize is not reasonable, skipping decompressions." + << voxelXSize << voxelYSize << voxelZSize << getName() << getID(); + _voxelDataDirty = false; + _voxelDataLock.unlock(); + _threadRunning.release(); + return; + } + + int rawSize = voxelXSize * voxelYSize * voxelZSize; + + QByteArray compressedData; + reader >> compressedData; + _voxelDataDirty = false; + _voxelDataLock.unlock(); + QByteArray uncompressedData = qUncompress(compressedData); + + if (uncompressedData.size() != rawSize) { + qDebug() << "PolyVox decompress -- size is (" << voxelXSize << voxelYSize << voxelZSize << ")" + << "so expected uncompressed length of" << rawSize << "but length is" << uncompressedData.size() + << getName() << getID(); + _threadRunning.release(); + return; + } + + _volDataLock.lockForWrite(); + if (!_volData) { + _volDataLock.unlock(); + _threadRunning.release(); + return; + } + _volDataDirty = true; + for (int z = 0; z < voxelZSize; z++) { + for (int y = 0; y < voxelYSize; y++) { + for (int x = 0; x < voxelXSize; x++) { + int uncompressedIndex = (z * voxelYSize * voxelXSize) + (y * voxelZSize) + x; + setVoxelInternal(x, y, z, uncompressedData[uncompressedIndex]); + } + } + } + _volDataLock.unlock(); + _threadRunning.release(); +} + +void RenderablePolyVoxEntityItem::compressVolumeDataAndSendEditPacket() { + _threadRunning.acquire(); + QtConcurrent::run(this, &RenderablePolyVoxEntityItem::compressVolumeDataAndSendEditPacketAsync); +} + + +// compress the data in _volData and save the results. The compressed form is used during +// saves to disk and for transmission over the wire +void RenderablePolyVoxEntityItem::compressVolumeDataAndSendEditPacketAsync() { + quint16 voxelXSize = _voxelVolumeSize.x; + quint16 voxelYSize = _voxelVolumeSize.y; + quint16 voxelZSize = _voxelVolumeSize.z; + int rawSize = voxelXSize * voxelYSize * voxelZSize; + + QByteArray uncompressedData = QByteArray(rawSize, '\0'); + + _volDataLock.lockForRead(); + for (int z = 0; z < voxelZSize; z++) { + for (int y = 0; y < voxelYSize; y++) { + for (int x = 0; x < voxelXSize; x++) { + uint8_t uVoxelValue = getVoxelInternal(x, y, z); + int uncompressedIndex = + z * voxelYSize * voxelXSize + + y * voxelXSize + + x; + uncompressedData[uncompressedIndex] = uVoxelValue; + } + } + } + _volDataLock.unlock(); + + QByteArray newVoxelData; + QDataStream writer(&newVoxelData, QIODevice::WriteOnly | QIODevice::Truncate); + + writer << voxelXSize << voxelYSize << voxelZSize; + + QByteArray compressedData = qCompress(uncompressedData, 9); + writer << compressedData; + + // make sure the compressed data can be sent over the wire-protocol + if (newVoxelData.size() > 1150) { + // HACK -- until we have a way to allow for properties larger than MTU, don't update. + // revert the active voxel-space to the last version that fit. + // XXX + qDebug() << "compressed voxel data is too large" << getName() << getID(); + _threadRunning.release(); + return; + } + + auto now = usecTimestampNow(); + setLastEdited(now); + setLastBroadcast(now); + + _voxelDataLock.lockForWrite(); + _voxelDataDirty = true; + _voxelData = newVoxelData; + _voxelDataLock.unlock(); + + EntityItemProperties properties = getProperties(); + properties.setVoxelDataDirty(); + properties.setLastEdited(now); + + EntityTreeElement* element = getElement(); + EntityTree* tree = element ? element->getTree() : nullptr; + EntitySimulation* simulation = tree ? tree->getSimulation() : nullptr; + PhysicalEntitySimulation* peSimulation = static_cast(simulation); + EntityEditPacketSender* packetSender = peSimulation ? peSimulation->getPacketSender() : nullptr; + if (packetSender) { + packetSender->queueEditEntityMessage(PacketType::EntityEdit, _id, properties); + } + _threadRunning.release(); +} + +void RenderablePolyVoxEntityItem::getMesh() { + _threadRunning.acquire(); + QtConcurrent::run(this, &RenderablePolyVoxEntityItem::getMeshAsync); +} + + +void RenderablePolyVoxEntityItem::getMeshAsync() { + model::MeshPointer mesh(new model::Mesh()); + + // A mesh object to hold the result of surface extraction + PolyVox::SurfaceMesh polyVoxMesh; + + _volDataLock.lockForRead(); + switch (_voxelSurfaceStyle) { + case PolyVoxEntityItem::SURFACE_EDGED_MARCHING_CUBES: + case PolyVoxEntityItem::SURFACE_MARCHING_CUBES: { + PolyVox::MarchingCubesSurfaceExtractor> surfaceExtractor + (_volData, _volData->getEnclosingRegion(), &polyVoxMesh); + surfaceExtractor.execute(); + break; + } + case PolyVoxEntityItem::SURFACE_EDGED_CUBIC: + case PolyVoxEntityItem::SURFACE_CUBIC: { + PolyVox::CubicSurfaceExtractorWithNormals> surfaceExtractor + (_volData, _volData->getEnclosingRegion(), &polyVoxMesh); + surfaceExtractor.execute(); + break; + } + } + + // convert PolyVox mesh to a Sam mesh + const std::vector& vecIndices = polyVoxMesh.getIndices(); + auto indexBuffer = std::make_shared(vecIndices.size() * sizeof(uint32_t), + (gpu::Byte*)vecIndices.data()); + auto indexBufferPtr = gpu::BufferPointer(indexBuffer); + auto indexBufferView = new gpu::BufferView(indexBufferPtr, gpu::Element(gpu::SCALAR, gpu::UINT32, gpu::RAW)); + mesh->setIndexBuffer(*indexBufferView); + + const std::vector& vecVertices = polyVoxMesh.getVertices(); + auto vertexBuffer = std::make_shared(vecVertices.size() * sizeof(PolyVox::PositionMaterialNormal), + (gpu::Byte*)vecVertices.data()); + auto vertexBufferPtr = gpu::BufferPointer(vertexBuffer); + gpu::Resource::Size vertexBufferSize = 0; + if (vertexBufferPtr->getSize() > sizeof(float) * 3) { + vertexBufferSize = vertexBufferPtr->getSize() - sizeof(float) * 3; + } + auto vertexBufferView = new gpu::BufferView(vertexBufferPtr, 0, vertexBufferSize, sizeof(PolyVox::PositionMaterialNormal), + gpu::Element(gpu::VEC3, gpu::FLOAT, gpu::RAW)); + mesh->setVertexBuffer(*vertexBufferView); + mesh->addAttribute(gpu::Stream::NORMAL, + gpu::BufferView(vertexBufferPtr, + sizeof(float) * 3, + vertexBufferPtr->getSize() - sizeof(float) * 3, + sizeof(PolyVox::PositionMaterialNormal), + gpu::Element(gpu::VEC3, gpu::FLOAT, gpu::RAW))); + + _meshLock.lockForWrite(); + _dirtyFlags |= EntityItem::DIRTY_SHAPE | EntityItem::DIRTY_MASS; + _mesh = mesh; + _meshDirty = true; + _meshLock.unlock(); + _volDataDirty = false; + _volDataLock.unlock(); + + _threadRunning.release(); +} + +void RenderablePolyVoxEntityItem::computeShapeInfoWorker() { + _threadRunning.acquire(); + QtConcurrent::run(this, &RenderablePolyVoxEntityItem::computeShapeInfoWorkerAsync); +} + + +void RenderablePolyVoxEntityItem::computeShapeInfoWorkerAsync() { + QVector> points; + AABox box; + glm::mat4 vtoM = voxelToLocalMatrix(); + + if (_voxelSurfaceStyle == PolyVoxEntityItem::SURFACE_MARCHING_CUBES || + _voxelSurfaceStyle == PolyVoxEntityItem::SURFACE_EDGED_MARCHING_CUBES) { + /* pull each triangle in the mesh into a polyhedron which can be collided with */ + unsigned int i = 0; + + _meshLock.lockForRead(); + model::MeshPointer mesh = _mesh; + const gpu::BufferView vertexBufferView = mesh->getVertexBuffer(); + const gpu::BufferView& indexBufferView = mesh->getIndexBuffer(); + _meshLock.unlock(); + + gpu::BufferView::Iterator it = indexBufferView.cbegin(); + while (it != indexBufferView.cend()) { + uint32_t p0Index = *(it++); + uint32_t p1Index = *(it++); + uint32_t p2Index = *(it++); + + const glm::vec3& p0 = vertexBufferView.get(p0Index); + const glm::vec3& p1 = vertexBufferView.get(p1Index); + const glm::vec3& p2 = vertexBufferView.get(p2Index); + + glm::vec3 av = (p0 + p1 + p2) / 3.0f; // center of the triangular face + glm::vec3 normal = glm::normalize(glm::cross(p1 - p0, p2 - p0)); + glm::vec3 p3 = av - normal * MARCHING_CUBE_COLLISION_HULL_OFFSET; + + glm::vec3 p0Model = glm::vec3(vtoM * glm::vec4(p0, 1.0f)); + glm::vec3 p1Model = glm::vec3(vtoM * glm::vec4(p1, 1.0f)); + glm::vec3 p2Model = glm::vec3(vtoM * glm::vec4(p2, 1.0f)); + glm::vec3 p3Model = glm::vec3(vtoM * glm::vec4(p3, 1.0f)); + + box += p0Model; + box += p1Model; + box += p2Model; + box += p3Model; + + QVector pointsInPart; + pointsInPart << p0Model; + pointsInPart << p1Model; + pointsInPart << p2Model; + pointsInPart << p3Model; + // add next convex hull + QVector newMeshPoints; + points << newMeshPoints; + // add points to the new convex hull + points[i++] << pointsInPart; + } + } else { + unsigned int i = 0; + + for (int z = 0; z < _voxelVolumeSize.z; z++) { + for (int y = 0; y < _voxelVolumeSize.y; y++) { + for (int x = 0; x < _voxelVolumeSize.x; x++) { + if (getVoxel(x, y, z) > 0) { + + if ((x > 0 && getVoxel(x - 1, y, z) > 0) && + (y > 0 && getVoxel(x, y - 1, z) > 0) && + (z > 0 && getVoxel(x, y, z - 1) > 0) && + (x < _voxelVolumeSize.x - 1 && getVoxel(x + 1, y, z) > 0) && + (y < _voxelVolumeSize.y - 1 && getVoxel(x, y + 1, z) > 0) && + (z < _voxelVolumeSize.z - 1 && getVoxel(x, y, z + 1) > 0)) { + // this voxel has neighbors in every cardinal direction, so there's no need + // to include it in the collision hull. + continue; + } + + QVector pointsInPart; + + float offL = -0.5f; + float offH = 0.5f; + if (_voxelSurfaceStyle == PolyVoxEntityItem::SURFACE_EDGED_CUBIC) { + offL += 1.0f; + offH += 1.0f; + } + + glm::vec3 p000 = glm::vec3(vtoM * glm::vec4(x + offL, y + offL, z + offL, 1.0f)); + glm::vec3 p001 = glm::vec3(vtoM * glm::vec4(x + offL, y + offL, z + offH, 1.0f)); + glm::vec3 p010 = glm::vec3(vtoM * glm::vec4(x + offL, y + offH, z + offL, 1.0f)); + glm::vec3 p011 = glm::vec3(vtoM * glm::vec4(x + offL, y + offH, z + offH, 1.0f)); + glm::vec3 p100 = glm::vec3(vtoM * glm::vec4(x + offH, y + offL, z + offL, 1.0f)); + glm::vec3 p101 = glm::vec3(vtoM * glm::vec4(x + offH, y + offL, z + offH, 1.0f)); + glm::vec3 p110 = glm::vec3(vtoM * glm::vec4(x + offH, y + offH, z + offL, 1.0f)); + glm::vec3 p111 = glm::vec3(vtoM * glm::vec4(x + offH, y + offH, z + offH, 1.0f)); + + box += p000; + box += p001; + box += p010; + box += p011; + box += p100; + box += p101; + box += p110; + box += p111; + + pointsInPart << p000; + pointsInPart << p001; + pointsInPart << p010; + pointsInPart << p011; + pointsInPart << p100; + pointsInPart << p101; + pointsInPart << p110; + pointsInPart << p111; + + // add next convex hull + QVector newMeshPoints; + points << newMeshPoints; + // add points to the new convex hull + points[i++] << pointsInPart; + } + } + } + } + } + + if (points.isEmpty()) { + _shapeInfoLock.lockForWrite(); + EntityItem::computeShapeInfo(_shapeInfo); + _shapeInfoLock.unlock(); + _threadRunning.release(); + return; + } + + glm::vec3 collisionModelDimensions = box.getDimensions(); + QByteArray b64 = _voxelData.toBase64(); + _shapeInfoLock.lockForWrite(); + _shapeInfo.setParams(SHAPE_TYPE_COMPOUND, collisionModelDimensions, QString(b64)); + _shapeInfo.setConvexHulls(points); + _shapeInfoLock.unlock(); + + _meshLock.lockForWrite(); + _meshDirty = false; + _meshLock.unlock(); + _threadRunning.release(); + return; +} diff --git a/libraries/entities-renderer/src/RenderablePolyVoxEntityItem.h b/libraries/entities-renderer/src/RenderablePolyVoxEntityItem.h index e2fcdedc31..110e8f8ab4 100644 --- a/libraries/entities-renderer/src/RenderablePolyVoxEntityItem.h +++ b/libraries/entities-renderer/src/RenderablePolyVoxEntityItem.h @@ -12,13 +12,18 @@ #ifndef hifi_RenderablePolyVoxEntityItem_h #define hifi_RenderablePolyVoxEntityItem_h +#include +#include + #include +#include + #include #include "PolyVoxEntityItem.h" #include "RenderableDebugableEntityItem.h" #include "RenderableEntityItem.h" - +#include "gpu/Context.h" class PolyVoxPayload { public: @@ -56,19 +61,16 @@ public: virtual uint8_t getVoxel(int x, int y, int z); virtual bool setVoxel(int x, int y, int z, uint8_t toValue); - bool updateOnCount(int x, int y, int z, uint8_t new_value); - void render(RenderArgs* args); virtual bool supportsDetailedRayIntersection() const { return true; } virtual bool findDetailedRayIntersection(const glm::vec3& origin, const glm::vec3& direction, bool& keepSearching, OctreeElement*& element, float& distance, BoxFace& face, void** intersectedObject, bool precisionPicking) const; - void getModel(); - virtual void setVoxelData(QByteArray voxelData); - virtual void setVoxelVolumeSize(glm::vec3 voxelVolumeSize); + virtual void setVoxelSurfaceStyle(PolyVoxSurfaceStyle voxelSurfaceStyle); + glm::vec3 getSurfacePositionAdjustment() const; glm::mat4 voxelToWorldMatrix() const; glm::mat4 worldToVoxelMatrix() const; @@ -103,33 +105,48 @@ public: std::shared_ptr scene, render::PendingChanges& pendingChanges); -protected: - virtual void updateVoxelSurfaceStyle(PolyVoxSurfaceStyle voxelSurfaceStyle); - private: // The PolyVoxEntityItem class has _voxelData which contains dimensions and compressed voxel data. The dimensions // may not match _voxelVolumeSize. - bool setVoxelInternal(int x, int y, int z, uint8_t toValue); - void compressVolumeData(); - void decompressVolumeData(); - void clearEdges(); - - PolyVox::SimpleVolume* _volData = nullptr; - model::Geometry _modelGeometry; - bool _needsModelReload = true; - - QVector> _points; // XXX + model::MeshPointer _mesh; + bool _meshDirty; // does collision-shape need to be recomputed? + mutable QReadWriteLock _meshLock{QReadWriteLock::Recursive}; NetworkTexturePointer _xTexture; NetworkTexturePointer _yTexture; NetworkTexturePointer _zTexture; - int _onCount = 0; // how many non-zero voxels are in _volData - const int MATERIAL_GPU_SLOT = 3; render::ItemID _myItem; static gpu::PipelinePointer _pipeline; + + ShapeInfo _shapeInfo; + mutable QReadWriteLock _shapeInfoLock; + + PolyVox::SimpleVolume* _volData = nullptr; + mutable QReadWriteLock _volDataLock{QReadWriteLock::Recursive}; // lock for _volData + bool _volDataDirty = false; // does getMesh need to be called? + int _onCount; // how many non-zero voxels are in _volData + + bool inUserBounds(const PolyVox::SimpleVolume* vol, PolyVoxEntityItem::PolyVoxSurfaceStyle surfaceStyle, + int x, int y, int z); + uint8_t getVoxelInternal(int x, int y, int z); + bool setVoxelInternal(int x, int y, int z, uint8_t toValue); + bool updateOnCount(int x, int y, int z, uint8_t toValue); + PolyVox::RaycastResult doRayCast(glm::vec4 originInVoxel, glm::vec4 farInVoxel, glm::vec4& result) const; + + // these are run off the main thread + void decompressVolumeData(); + void decompressVolumeDataAsync(); + void compressVolumeDataAndSendEditPacket(); + void compressVolumeDataAndSendEditPacketAsync(); + void getMesh(); + void getMeshAsync(); + void computeShapeInfoWorker(); + void computeShapeInfoWorkerAsync(); + + QSemaphore _threadRunning{1}; }; diff --git a/libraries/entities/src/EntityScriptingInterface.cpp b/libraries/entities/src/EntityScriptingInterface.cpp index 41ca6a91ac..fee2055bd0 100644 --- a/libraries/entities/src/EntityScriptingInterface.cpp +++ b/libraries/entities/src/EntityScriptingInterface.cpp @@ -431,23 +431,10 @@ bool EntityScriptingInterface::setVoxels(QUuid entityID, return false; } - auto now = usecTimestampNow(); - auto polyVoxEntity = std::dynamic_pointer_cast(entity); _entityTree->lockForWrite(); bool result = actor(*polyVoxEntity); - entity->setLastEdited(now); - entity->setLastBroadcast(now); _entityTree->unlock(); - - _entityTree->lockForRead(); - EntityItemProperties properties = entity->getProperties(); - _entityTree->unlock(); - - properties.setVoxelDataDirty(); - properties.setLastEdited(now); - - queueEntityMessage(PacketType::EntityEdit, entityID, properties); return result; } diff --git a/libraries/entities/src/PolyVoxEntityItem.cpp b/libraries/entities/src/PolyVoxEntityItem.cpp index 30dded3714..e765afd430 100644 --- a/libraries/entities/src/PolyVoxEntityItem.cpp +++ b/libraries/entities/src/PolyVoxEntityItem.cpp @@ -25,7 +25,7 @@ const glm::vec3 PolyVoxEntityItem::DEFAULT_VOXEL_VOLUME_SIZE = glm::vec3(32, 32, const float PolyVoxEntityItem::MAX_VOXEL_DIMENSION = 128.0f; const QByteArray PolyVoxEntityItem::DEFAULT_VOXEL_DATA(PolyVoxEntityItem::makeEmptyVoxelData()); const PolyVoxEntityItem::PolyVoxSurfaceStyle PolyVoxEntityItem::DEFAULT_VOXEL_SURFACE_STYLE = - PolyVoxEntityItem::SURFACE_MARCHING_CUBES; + PolyVoxEntityItem::SURFACE_EDGED_CUBIC; const QString PolyVoxEntityItem::DEFAULT_X_TEXTURE_URL = QString(""); const QString PolyVoxEntityItem::DEFAULT_Y_TEXTURE_URL = QString(""); const QString PolyVoxEntityItem::DEFAULT_Z_TEXTURE_URL = QString(""); @@ -52,6 +52,7 @@ PolyVoxEntityItem::PolyVoxEntityItem(const EntityItemID& entityItemID, const Ent EntityItem(entityItemID), _voxelVolumeSize(PolyVoxEntityItem::DEFAULT_VOXEL_VOLUME_SIZE), _voxelData(PolyVoxEntityItem::DEFAULT_VOXEL_DATA), + _voxelDataDirty(true), _voxelSurfaceStyle(PolyVoxEntityItem::DEFAULT_VOXEL_SURFACE_STYLE), _xTextureURL(PolyVoxEntityItem::DEFAULT_X_TEXTURE_URL), _yTextureURL(PolyVoxEntityItem::DEFAULT_Y_TEXTURE_URL), @@ -66,7 +67,7 @@ void PolyVoxEntityItem::setVoxelVolumeSize(glm::vec3 voxelVolumeSize) { assert((int)_voxelVolumeSize.y == _voxelVolumeSize.y); assert((int)_voxelVolumeSize.z == _voxelVolumeSize.z); - _voxelVolumeSize = voxelVolumeSize; + _voxelVolumeSize = glm::vec3(roundf(voxelVolumeSize.x), roundf(voxelVolumeSize.y), roundf(voxelVolumeSize.z)); if (_voxelVolumeSize.x < 1) { qDebug() << "PolyVoxEntityItem::setVoxelVolumeSize clamping x of" << _voxelVolumeSize.x << "to 1"; _voxelVolumeSize.x = 1; @@ -185,9 +186,16 @@ void PolyVoxEntityItem::debugDump() const { qCDebug(entities) << " getLastEdited:" << debugTime(getLastEdited(), now); } -void PolyVoxEntityItem::setVoxelSurfaceStyle(PolyVoxSurfaceStyle voxelSurfaceStyle) { - if (voxelSurfaceStyle == _voxelSurfaceStyle) { - return; - } - updateVoxelSurfaceStyle(voxelSurfaceStyle); +void PolyVoxEntityItem::setVoxelData(QByteArray voxelData) { + _voxelDataLock.lockForWrite(); + _voxelData = voxelData; + _voxelDataDirty = true; + _voxelDataLock.unlock(); +} + +const QByteArray PolyVoxEntityItem::getVoxelData() const { + _voxelDataLock.lockForRead(); + auto result = _voxelData; + _voxelDataLock.unlock(); + return result; } diff --git a/libraries/entities/src/PolyVoxEntityItem.h b/libraries/entities/src/PolyVoxEntityItem.h index 9fbaade407..c84dc9f4c1 100644 --- a/libraries/entities/src/PolyVoxEntityItem.h +++ b/libraries/entities/src/PolyVoxEntityItem.h @@ -52,8 +52,8 @@ class PolyVoxEntityItem : public EntityItem { virtual void setVoxelVolumeSize(glm::vec3 voxelVolumeSize); virtual const glm::vec3& getVoxelVolumeSize() const { return _voxelVolumeSize; } - virtual void setVoxelData(QByteArray voxelData) { _voxelData = voxelData; } - virtual const QByteArray& getVoxelData() const { return _voxelData; } + virtual void setVoxelData(QByteArray voxelData); + virtual const QByteArray getVoxelData() const; enum PolyVoxSurfaceStyle { SURFACE_MARCHING_CUBES, @@ -62,7 +62,7 @@ class PolyVoxEntityItem : public EntityItem { SURFACE_EDGED_MARCHING_CUBES }; - void setVoxelSurfaceStyle(PolyVoxSurfaceStyle voxelSurfaceStyle); + virtual void setVoxelSurfaceStyle(PolyVoxSurfaceStyle voxelSurfaceStyle) { _voxelSurfaceStyle = voxelSurfaceStyle; } // this other version of setVoxelSurfaceStyle is needed for SET_ENTITY_PROPERTY_FROM_PROPERTIES void setVoxelSurfaceStyle(uint16_t voxelSurfaceStyle) { setVoxelSurfaceStyle((PolyVoxSurfaceStyle) voxelSurfaceStyle); } virtual PolyVoxSurfaceStyle getVoxelSurfaceStyle() const { return _voxelSurfaceStyle; } @@ -104,12 +104,12 @@ class PolyVoxEntityItem : public EntityItem { virtual const QString& getZTextureURL() const { return _zTextureURL; } protected: - virtual void updateVoxelSurfaceStyle(PolyVoxSurfaceStyle voxelSurfaceStyle) { - _voxelSurfaceStyle = voxelSurfaceStyle; - } - glm::vec3 _voxelVolumeSize; // this is always 3 bytes + + mutable QReadWriteLock _voxelDataLock; QByteArray _voxelData; + bool _voxelDataDirty; + PolyVoxSurfaceStyle _voxelSurfaceStyle; QString _xTextureURL; diff --git a/libraries/physics/src/EntityMotionState.cpp b/libraries/physics/src/EntityMotionState.cpp index 4e6eb8353c..dc3c0ea4e8 100644 --- a/libraries/physics/src/EntityMotionState.cpp +++ b/libraries/physics/src/EntityMotionState.cpp @@ -99,7 +99,7 @@ void EntityMotionState::updateServerPhysicsVariables() { } // virtual -void EntityMotionState::handleEasyChanges(uint32_t flags, PhysicsEngine* engine) { +bool EntityMotionState::handleEasyChanges(uint32_t flags, PhysicsEngine* engine) { assert(entityTreeIsLocked()); updateServerPhysicsVariables(); ObjectMotionState::handleEasyChanges(flags, engine); @@ -131,13 +131,15 @@ void EntityMotionState::handleEasyChanges(uint32_t flags, PhysicsEngine* engine) if ((flags & EntityItem::DIRTY_PHYSICS_ACTIVATION) && !_body->isActive()) { _body->activate(); } + + return true; } // virtual -void EntityMotionState::handleHardAndEasyChanges(uint32_t flags, PhysicsEngine* engine) { +bool EntityMotionState::handleHardAndEasyChanges(uint32_t flags, PhysicsEngine* engine) { updateServerPhysicsVariables(); - ObjectMotionState::handleHardAndEasyChanges(flags, engine); + return ObjectMotionState::handleHardAndEasyChanges(flags, engine); } void EntityMotionState::clearObjectBackPointer() { @@ -222,6 +224,15 @@ void EntityMotionState::setWorldTransform(const btTransform& worldTrans) { #endif } + +// virtual and protected +bool EntityMotionState::isReadyToComputeShape() { + if (_entity) { + return _entity->isReadyToComputeShape(); + } + return false; +} + // virtual and protected btCollisionShape* EntityMotionState::computeNewShape() { if (_entity) { @@ -493,12 +504,11 @@ void EntityMotionState::sendUpdate(OctreeEditPacketSender* packetSender, const Q _lastStep = step; } -uint32_t EntityMotionState::getAndClearIncomingDirtyFlags() { +uint32_t EntityMotionState::getIncomingDirtyFlags() { assert(entityTreeIsLocked()); uint32_t dirtyFlags = 0; if (_body && _entity) { dirtyFlags = _entity->getDirtyFlags(); - _entity->clearDirtyFlags(); // we add DIRTY_MOTION_TYPE if the body's motion type disagrees with entity velocity settings int bodyFlags = _body->getCollisionFlags(); bool isMoving = _entity->isMoving(); @@ -510,6 +520,13 @@ uint32_t EntityMotionState::getAndClearIncomingDirtyFlags() { return dirtyFlags; } +void EntityMotionState::clearIncomingDirtyFlags() { + assert(entityTreeIsLocked()); + if (_body && _entity) { + _entity->clearDirtyFlags(); + } +} + // virtual quint8 EntityMotionState::getSimulationPriority() const { if (_entity) { diff --git a/libraries/physics/src/EntityMotionState.h b/libraries/physics/src/EntityMotionState.h index 009c931dba..f3a2e80070 100644 --- a/libraries/physics/src/EntityMotionState.h +++ b/libraries/physics/src/EntityMotionState.h @@ -29,8 +29,8 @@ public: virtual ~EntityMotionState(); void updateServerPhysicsVariables(); - virtual void handleEasyChanges(uint32_t flags, PhysicsEngine* engine); - virtual void handleHardAndEasyChanges(uint32_t flags, PhysicsEngine* engine); + virtual bool handleEasyChanges(uint32_t flags, PhysicsEngine* engine); + virtual bool handleHardAndEasyChanges(uint32_t flags, PhysicsEngine* engine); /// \return MOTION_TYPE_DYNAMIC or MOTION_TYPE_STATIC based on params set in EntityItem virtual MotionType computeObjectMotionType() const; @@ -48,7 +48,8 @@ public: bool shouldSendUpdate(uint32_t simulationStep, const QUuid& sessionID); void sendUpdate(OctreeEditPacketSender* packetSender, const QUuid& sessionID, uint32_t step); - virtual uint32_t getAndClearIncomingDirtyFlags(); + virtual uint32_t getIncomingDirtyFlags(); + virtual void clearIncomingDirtyFlags(); void incrementAccelerationNearlyGravityCount() { _accelerationNearlyGravityCount++; } void resetAccelerationNearlyGravityCount() { _accelerationNearlyGravityCount = 0; } @@ -91,6 +92,7 @@ protected: bool entityTreeIsLocked() const; #endif + virtual bool isReadyToComputeShape(); virtual btCollisionShape* computeNewShape(); virtual void clearObjectBackPointer(); virtual void setMotionType(MotionType motionType); diff --git a/libraries/physics/src/ObjectMotionState.cpp b/libraries/physics/src/ObjectMotionState.cpp index aeec85f7ea..85bddd0495 100644 --- a/libraries/physics/src/ObjectMotionState.cpp +++ b/libraries/physics/src/ObjectMotionState.cpp @@ -125,7 +125,7 @@ void ObjectMotionState::setRigidBody(btRigidBody* body) { } } -void ObjectMotionState::handleEasyChanges(uint32_t flags, PhysicsEngine* engine) { +bool ObjectMotionState::handleEasyChanges(uint32_t flags, PhysicsEngine* engine) { if (flags & EntityItem::DIRTY_POSITION) { btTransform worldTrans; if (flags & EntityItem::DIRTY_ROTATION) { @@ -156,11 +156,16 @@ void ObjectMotionState::handleEasyChanges(uint32_t flags, PhysicsEngine* engine) if (flags & EntityItem::DIRTY_MASS) { updateBodyMassProperties(); } + + return true; } -void ObjectMotionState::handleHardAndEasyChanges(uint32_t flags, PhysicsEngine* engine) { +bool ObjectMotionState::handleHardAndEasyChanges(uint32_t flags, PhysicsEngine* engine) { if (flags & EntityItem::DIRTY_SHAPE) { // make sure the new shape is valid + if (!isReadyToComputeShape()) { + return false; + } btCollisionShape* newShape = computeNewShape(); if (!newShape) { qCDebug(physics) << "Warning: failed to generate new shape!"; @@ -172,7 +177,7 @@ void ObjectMotionState::handleHardAndEasyChanges(uint32_t flags, PhysicsEngine* if (flags & EASY_DIRTY_PHYSICS_FLAGS) { handleEasyChanges(flags, engine); } - return; + return true; } } getShapeManager()->releaseShape(_shape); @@ -192,6 +197,8 @@ void ObjectMotionState::handleHardAndEasyChanges(uint32_t flags, PhysicsEngine* if (flags & HARD_DIRTY_PHYSICS_FLAGS) { engine->reinsertObject(this); } + + return true; } void ObjectMotionState::updateBodyMaterialProperties() { diff --git a/libraries/physics/src/ObjectMotionState.h b/libraries/physics/src/ObjectMotionState.h index 30394ef5fc..1bdf8b6372 100644 --- a/libraries/physics/src/ObjectMotionState.h +++ b/libraries/physics/src/ObjectMotionState.h @@ -71,8 +71,8 @@ public: ObjectMotionState(btCollisionShape* shape); ~ObjectMotionState(); - virtual void handleEasyChanges(uint32_t flags, PhysicsEngine* engine); - virtual void handleHardAndEasyChanges(uint32_t flags, PhysicsEngine* engine); + virtual bool handleEasyChanges(uint32_t flags, PhysicsEngine* engine); + virtual bool handleHardAndEasyChanges(uint32_t flags, PhysicsEngine* engine); void updateBodyMaterialProperties(); void updateBodyVelocities(); @@ -92,7 +92,8 @@ public: glm::vec3 getBodyAngularVelocity() const; virtual glm::vec3 getObjectLinearVelocityChange() const; - virtual uint32_t getAndClearIncomingDirtyFlags() = 0; + virtual uint32_t getIncomingDirtyFlags() = 0; + virtual void clearIncomingDirtyFlags() = 0; virtual MotionType computeObjectMotionType() const = 0; @@ -132,6 +133,7 @@ public: friend class PhysicsEngine; protected: + virtual bool isReadyToComputeShape() = 0; virtual btCollisionShape* computeNewShape() = 0; void setMotionType(MotionType motionType); diff --git a/libraries/physics/src/PhysicalEntitySimulation.cpp b/libraries/physics/src/PhysicalEntitySimulation.cpp index f6f02b8573..97cf6a549a 100644 --- a/libraries/physics/src/PhysicalEntitySimulation.cpp +++ b/libraries/physics/src/PhysicalEntitySimulation.cpp @@ -173,6 +173,12 @@ VectorOfMotionStates& PhysicalEntitySimulation::getObjectsToAdd() { return _tempVector; } +void PhysicalEntitySimulation::setObjectsToChange(VectorOfMotionStates& objectsToChange) { + for (auto object : objectsToChange) { + _pendingChanges.insert(static_cast(object)); + } +} + VectorOfMotionStates& PhysicalEntitySimulation::getObjectsToChange() { _tempVector.clear(); for (auto stateItr : _pendingChanges) { diff --git a/libraries/physics/src/PhysicalEntitySimulation.h b/libraries/physics/src/PhysicalEntitySimulation.h index 9c439c53b2..7599c7d1b5 100644 --- a/libraries/physics/src/PhysicalEntitySimulation.h +++ b/libraries/physics/src/PhysicalEntitySimulation.h @@ -46,11 +46,14 @@ protected: // only called by EntitySimulation public: VectorOfMotionStates& getObjectsToDelete(); VectorOfMotionStates& getObjectsToAdd(); + void setObjectsToChange(VectorOfMotionStates& objectsToChange); VectorOfMotionStates& getObjectsToChange(); void handleOutgoingChanges(VectorOfMotionStates& motionStates, const QUuid& sessionID); void handleCollisionEvents(CollisionEvents& collisionEvents); + EntityEditPacketSender* getPacketSender() { return _entityPacketSender; } + private: // incoming changes SetOfEntityMotionStates _pendingRemoves; // EntityMotionStates to be removed from PhysicsEngine (and deleted) diff --git a/libraries/physics/src/PhysicsEngine.cpp b/libraries/physics/src/PhysicsEngine.cpp index 022468633f..040055b313 100644 --- a/libraries/physics/src/PhysicsEngine.cpp +++ b/libraries/physics/src/PhysicsEngine.cpp @@ -140,7 +140,7 @@ void PhysicsEngine::addObject(ObjectMotionState* motionState) { int16_t group = motionState->computeCollisionGroup(); _dynamicsWorld->addRigidBody(body, group, getCollisionMask(group)); - motionState->getAndClearIncomingDirtyFlags(); + motionState->clearIncomingDirtyFlags(); } void PhysicsEngine::removeObject(ObjectMotionState* object) { @@ -188,15 +188,25 @@ void PhysicsEngine::addObjects(VectorOfMotionStates& objects) { } } -void PhysicsEngine::changeObjects(VectorOfMotionStates& objects) { +VectorOfMotionStates PhysicsEngine::changeObjects(VectorOfMotionStates& objects) { + VectorOfMotionStates stillNeedChange; for (auto object : objects) { - uint32_t flags = object->getAndClearIncomingDirtyFlags() & DIRTY_PHYSICS_FLAGS; + uint32_t flags = object->getIncomingDirtyFlags() & DIRTY_PHYSICS_FLAGS; if (flags & HARD_DIRTY_PHYSICS_FLAGS) { - object->handleHardAndEasyChanges(flags, this); + if (object->handleHardAndEasyChanges(flags, this)) { + object->clearIncomingDirtyFlags(); + } else { + stillNeedChange.push_back(object); + } } else if (flags & EASY_DIRTY_PHYSICS_FLAGS) { - object->handleEasyChanges(flags, this); + if (object->handleEasyChanges(flags, this)) { + object->clearIncomingDirtyFlags(); + } else { + stillNeedChange.push_back(object); + } } } + return stillNeedChange; } void PhysicsEngine::reinsertObject(ObjectMotionState* object) { diff --git a/libraries/physics/src/PhysicsEngine.h b/libraries/physics/src/PhysicsEngine.h index a974a02e51..67b38323cc 100644 --- a/libraries/physics/src/PhysicsEngine.h +++ b/libraries/physics/src/PhysicsEngine.h @@ -60,7 +60,7 @@ public: void deleteObjects(VectorOfMotionStates& objects); void deleteObjects(SetOfMotionStates& objects); // only called during teardown void addObjects(VectorOfMotionStates& objects); - void changeObjects(VectorOfMotionStates& objects); + VectorOfMotionStates changeObjects(VectorOfMotionStates& objects); void reinsertObject(ObjectMotionState* object); void stepSimulation(); diff --git a/libraries/physics/src/ShapeFactory.cpp b/libraries/physics/src/ShapeFactory.cpp index cfe7abd4db..f138587030 100644 --- a/libraries/physics/src/ShapeFactory.cpp +++ b/libraries/physics/src/ShapeFactory.cpp @@ -94,7 +94,6 @@ btCollisionShape* ShapeFactory::createShapeFromInfo(const ShapeInfo& info) { if (numSubShapes == 1) { shape = createConvexHull(info.getPoints()[0]); } else { - assert(numSubShapes > 1); auto compound = new btCompoundShape(); btTransform trans; trans.setIdentity(); diff --git a/libraries/render-utils/src/Model.cpp b/libraries/render-utils/src/Model.cpp index c2d723a323..14b1c58321 100644 --- a/libraries/render-utils/src/Model.cpp +++ b/libraries/render-utils/src/Model.cpp @@ -60,6 +60,8 @@ float Model::FAKE_DIMENSION_PLACEHOLDER = -1.0f; Model::Model(RigPointer rig, QObject* parent) : QObject(parent), + _translation(0.0f), + _rotation(), _scale(1.0f, 1.0f, 1.0f), _scaleToFit(false), _scaleToFitDimensions(0.0f), @@ -196,6 +198,13 @@ void Model::RenderPipelineLib::initLocations(gpu::ShaderPointer& program, Model: AbstractViewStateInterface* Model::_viewState = NULL; +void Model::setTranslation(const glm::vec3& translation) { + _translation = translation; +} + +void Model::setRotation(const glm::quat& rotation) { + _rotation = rotation; +} void Model::setScale(const glm::vec3& scale) { setScaleInternal(scale); @@ -434,14 +443,14 @@ void Model::initJointStates(QVector states) { int rightElbowJointIndex = rightHandJointIndex >= 0 ? geometry.joints.at(rightHandJointIndex).parentIndex : -1; int rightShoulderJointIndex = rightElbowJointIndex >= 0 ? geometry.joints.at(rightElbowJointIndex).parentIndex : -1; - _boundingRadius = _rig->initJointStates(states, parentTransform, - rootJointIndex, - leftHandJointIndex, - leftElbowJointIndex, - leftShoulderJointIndex, - rightHandJointIndex, - rightElbowJointIndex, - rightShoulderJointIndex); + _rig->initJointStates(states, parentTransform, + rootJointIndex, + leftHandJointIndex, + leftElbowJointIndex, + leftShoulderJointIndex, + rightHandJointIndex, + rightElbowJointIndex, + rightShoulderJointIndex); } bool Model::findRayIntersectionAgainstSubMeshes(const glm::vec3& origin, const glm::vec3& direction, float& distance, diff --git a/libraries/render-utils/src/Model.h b/libraries/render-utils/src/Model.h index e55bff6aca..1a960f443b 100644 --- a/libraries/render-utils/src/Model.h +++ b/libraries/render-utils/src/Model.h @@ -27,7 +27,6 @@ #include #include #include -#include "PhysicsEntity.h" #include #include @@ -54,7 +53,7 @@ inline uint qHash(const std::shared_ptr& a, uint seed) { } /// A generic 3D model displaying geometry loaded from a URL. -class Model : public QObject, public PhysicsEntity { +class Model : public QObject { Q_OBJECT public: @@ -173,6 +172,12 @@ public: /// Returns the extents of the model's mesh Extents getMeshExtents() const; + void setTranslation(const glm::vec3& translation); + void setRotation(const glm::quat& rotation); + + const glm::vec3& getTranslation() const { return _translation; } + const glm::quat& getRotation() const { return _rotation; } + void setScale(const glm::vec3& scale); const glm::vec3& getScale() const { return _scale; } @@ -233,6 +238,8 @@ protected: QSharedPointer _geometry; void setGeometry(const QSharedPointer& newGeometry); + glm::vec3 _translation; + glm::quat _rotation; glm::vec3 _scale; glm::vec3 _offset; diff --git a/libraries/render-utils/src/OglplusHelpers.cpp b/libraries/render-utils/src/OglplusHelpers.cpp index 86769436e6..619ef656d2 100644 --- a/libraries/render-utils/src/OglplusHelpers.cpp +++ b/libraries/render-utils/src/OglplusHelpers.cpp @@ -65,11 +65,11 @@ void compileProgram(ProgramPtr & result, const std::string& vs, const std::strin .Compile() ); result->Link(); - } catch (ProgramBuildError & err) { + } catch (ProgramBuildError& err) { Q_UNUSED(err); qWarning() << err.Log().c_str(); Q_ASSERT_X(false, "compileProgram", "Failed to build shader program"); - qFatal((const char*)err.Message); + qFatal("%s", (const char*) err.Message); result.reset(); } } diff --git a/libraries/render-utils/src/PhysicsEntity.cpp b/libraries/render-utils/src/PhysicsEntity.cpp deleted file mode 100644 index 5d58d87e84..0000000000 --- a/libraries/render-utils/src/PhysicsEntity.cpp +++ /dev/null @@ -1,34 +0,0 @@ -// -// PhysicsEntity.cpp -// libraries/physics/src -// -// Created by Andrew Meadows 2014.06.11 -// 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 "PhysicsEntity.h" - -PhysicsEntity::PhysicsEntity() : - _translation(0.0f), - _rotation(), - _boundingRadius(0.0f) { -} - -PhysicsEntity::~PhysicsEntity() { -} - -void PhysicsEntity::setTranslation(const glm::vec3& translation) { - if (_translation != translation) { - _translation = translation; - } -} - -void PhysicsEntity::setRotation(const glm::quat& rotation) { - if (_rotation != rotation) { - _rotation = rotation; - } -} - diff --git a/libraries/render-utils/src/PhysicsEntity.h b/libraries/render-utils/src/PhysicsEntity.h deleted file mode 100644 index f36473af26..0000000000 --- a/libraries/render-utils/src/PhysicsEntity.h +++ /dev/null @@ -1,42 +0,0 @@ -// -// PhysicsEntity.h -// libraries/physics/src -// -// Created by Andrew Meadows 2014.05.30 -// Copyright 2014 High Fidelity, Inc. -// -// Distributed under the Apache License, Version 2.0. -// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html -// - -#ifndef hifi_PhysicsEntity_h -#define hifi_PhysicsEntity_h - -#include -#include - -#include -#include - -class PhysicsEntity { - -public: - PhysicsEntity(); - virtual ~PhysicsEntity(); - - virtual void stepForward(float deltaTime) { } - - void setTranslation(const glm::vec3& translation); - void setRotation(const glm::quat& rotation); - - const glm::vec3& getTranslation() const { return _translation; } - const glm::quat& getRotation() const { return _rotation; } - float getBoundingRadius() const { return _boundingRadius; } - -protected: - glm::vec3 _translation; - glm::quat _rotation; - float _boundingRadius; -}; - -#endif // hifi_PhysicsEntity_h diff --git a/libraries/script-engine/src/WebSocketClass.cpp b/libraries/script-engine/src/WebSocketClass.cpp index c844dc3582..19148b26e9 100644 --- a/libraries/script-engine/src/WebSocketClass.cpp +++ b/libraries/script-engine/src/WebSocketClass.cpp @@ -16,16 +16,16 @@ #include "WebSocketClass.h" WebSocketClass::WebSocketClass(QScriptEngine* engine, QString url) : - _engine(engine), - _webSocket(new QWebSocket()) + _webSocket(new QWebSocket()), + _engine(engine) { initialize(); _webSocket->open(url); } WebSocketClass::WebSocketClass(QScriptEngine* engine, QWebSocket* qWebSocket) : - _engine(engine), - _webSocket(qWebSocket) + _webSocket(qWebSocket), + _engine(engine) { initialize(); }