diff --git a/examples/airGuitar.js b/examples/airGuitar.js index c0e06add30..2c3d0409fa 100644 --- a/examples/airGuitar.js +++ b/examples/airGuitar.js @@ -132,15 +132,16 @@ function checkHands(deltaTime) { } function playChord(position, volume) { - var options = new AudioInjectionOptions(); - options.position = position; - options.volume = volume; if (Audio.isInjectorPlaying(soundPlaying)) { print("stopped sound"); Audio.stopInjector(soundPlaying); } + print("Played sound: " + whichChord + " at volume " + options.volume); - soundPlaying = Audio.playSound(chords[guitarSelector + whichChord], options); + soundPlaying = Audio.playSound(chords[guitarSelector + whichChord], { + position: position, + volume: volume + }); } function keyPressEvent(event) { diff --git a/examples/audioBall.js b/examples/audioBall.js index 7e7126be06..ca666285a9 100644 --- a/examples/audioBall.js +++ b/examples/audioBall.js @@ -32,10 +32,10 @@ function updateEntity(deltaTime) { if (Math.random() < CHANCE_OF_PLAYING_SOUND) { // play a sound at the location of the entity - var options = new AudioInjectionOptions(); - options.position = entityPosition; - options.volume = 0.75; - Audio.playSound(sound, options); + Audio.playSound(sound, { + position: entityPosition, + volume: 0.75 + }); } var audioAverageLoudness = MyAvatar.audioAverageLoudness * FACTOR; diff --git a/examples/avatarCollision.js b/examples/avatarCollision.js index 6c0886464c..4bd0adf69a 100644 --- a/examples/avatarCollision.js +++ b/examples/avatarCollision.js @@ -17,9 +17,11 @@ var SOUND_TRIGGER_CLEAR = 1000; // milliseconds var SOUND_TRIGGER_DELAY = 200; // milliseconds var soundExpiry = 0; var DateObj = new Date(); -var audioOptions = new AudioInjectionOptions(); -audioOptions.volume = 0.5; -audioOptions.position = { x: 0, y: 0, z: 0 }; + +var audioOptions = { + volume: 0.5, + position: { x: 0, y: 0, z: 0 } +} var hitSounds = new Array(); hitSounds[0] = new Sound(HIFI_PUBLIC_BUCKET + "sounds/Collisions-hitsandslaps/Hit1.raw"); diff --git a/examples/birdSongs.js b/examples/birdSongs.js index 94e013d92b..680cb025ad 100644 --- a/examples/birdSongs.js +++ b/examples/birdSongs.js @@ -33,20 +33,22 @@ function maybePlaySound(deltaTime) { // Set the location and other info for the sound to play var whichBird = Math.floor(Math.random() * birds.length); //print("playing sound # " + whichBird); - var options = new AudioInjectionOptions(); - var position = { x: lowerCorner.x + Math.random() * (upperCorner.x - lowerCorner.x), - y: lowerCorner.y + Math.random() * (upperCorner.y - lowerCorner.y), - z: lowerCorner.z + Math.random() * (upperCorner.z - lowerCorner.z) }; - options.position = position; - options.volume = BIRD_MASTER_VOLUME; - // + var position = { + x: lowerCorner.x + Math.random() * (upperCorner.x - lowerCorner.x), + y: lowerCorner.y + Math.random() * (upperCorner.y - lowerCorner.y), + z: lowerCorner.z + Math.random() * (upperCorner.z - lowerCorner.z) + }; + var options = { + position: position, + volume: BIRD_MASTER_VOLUME + }; var entityId = Entities.addEntity({ - type: "Sphere", - position: position, - dimensions: { x: BIRD_SIZE, y: BIRD_SIZE, z: BIRD_SIZE }, - color: birds[whichBird].color, - lifetime: 10 - }); + type: "Sphere", + position: position, + dimensions: { x: BIRD_SIZE, y: BIRD_SIZE, z: BIRD_SIZE }, + color: birds[whichBird].color, + lifetime: 10 + }); if (useLights) { var lightId = Entities.addEntity({ diff --git a/examples/botProceduralWayPoints.js b/examples/botProceduralWayPoints.js index fc9c0dda74..0f8b369470 100644 --- a/examples/botProceduralWayPoints.js +++ b/examples/botProceduralWayPoints.js @@ -172,13 +172,11 @@ function playRandomSound() { } function playRandomFootstepSound() { - - var whichSound = Math.floor((Math.random() * footstepSounds.length)); - var options = new AudioInjectionOptions(); - options.position = Avatar.position; - options.volume = 1.0; - Audio.playSound(footstepSounds[whichSound], options); - + var whichSound = Math.floor((Math.random() * footstepSounds.length)); + Audio.playSound(footstepSounds[whichSound], { + position: Avatar.position, + volume: 1.0 + }); } // Facial Animation diff --git a/examples/bot_procedural.js b/examples/bot_procedural.js index f445162038..80f83fcdfa 100644 --- a/examples/bot_procedural.js +++ b/examples/bot_procedural.js @@ -134,13 +134,11 @@ function playRandomSound() { } function playRandomFootstepSound() { - - var whichSound = Math.floor((Math.random() * footstepSounds.length)); - var options = new AudioInjectionOptions(); - options.position = Avatar.position; - options.volume = 1.0; - Audio.playSound(footstepSounds[whichSound], options); - + var whichSound = Math.floor((Math.random() * footstepSounds.length)); + Audio.playSound(footstepSounds[whichSound], { + position: Avatar.position, + volume: 1.0 + }); } // ************************************ Facial Animation ********************************** diff --git a/examples/clap.js b/examples/clap.js index 9cc79a1c92..bf71f13cea 100644 --- a/examples/clap.js +++ b/examples/clap.js @@ -89,11 +89,11 @@ function maybePlaySound(deltaTime) { } function playClap(volume, position) { - var options = new AudioInjectionOptions(); - options.position = position; - options.volume = 1.0; var clip = Math.floor(Math.random() * numberOfSounds); - Audio.playSound(claps[clip], options); + Audio.playSound(claps[clip], { + position: position, + volume: volume + }); } var FASTEST_CLAP_INTERVAL = 150.0; diff --git a/examples/concertCamera.js b/examples/concertCamera.js deleted file mode 100644 index 7ab7785345..0000000000 --- a/examples/concertCamera.js +++ /dev/null @@ -1,72 +0,0 @@ -// -// concertCamera.js -// -// Created by Philip Rosedale on June 24, 2014 -// Copyright 2014 High Fidelity, Inc. -// -// Move a camera through a series of pre-set locations by pressing number keys -// -// Distributed under the Apache License, Version 2.0. -// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html -// - -var oldMode; -var avatarPosition; - -var cameraNumber = 0; -var freeCamera = false; - -var cameraLocations = [ {x: 7971.9, y: 241.3, z: 7304.1}, {x: 7973.0, y: 241.3, z: 7304.1}, {x: 7975.5, y: 241.3, z: 7304.1}, {x: 7972.3, y: 241.3, z: 7303.3}, {x: 7971.0, y: 241.3, z: 7304.3}, {x: 7973.5, y: 240.7, z: 7302.5} ]; -var cameraLookAts = [ {x: 7971.1, y: 241.3, z: 7304.1}, {x: 7972.1, y: 241.3, z: 7304.1}, {x: 7972.1, y: 241.3, z: 7304.1}, {x: 7972.1, y: 241.3, z: 7304.1}, {x: 7972.1, y: 241.3, z: 7304.1}, {x: 7971.3, y: 241.3, z: 7304.2} ]; - -function saveCameraState() { - oldMode = Camera.mode; - avatarPosition = MyAvatar.position; - Camera.setModeShiftPeriod(0.0); - Camera.mode = "independent"; -} - -function restoreCameraState() { - Camera.stopLooking(); - Camera.mode = oldMode; -} - -function update(deltaTime) { - if (freeCamera) { - var delta = Vec3.subtract(MyAvatar.position, avatarPosition); - if (Vec3.length(delta) > 0.05) { - cameraNumber = 0; - freeCamera = false; - restoreCameraState(); - } - } -} - -function keyPressEvent(event) { - - var choice = parseInt(event.text); - - if ((choice > 0) && (choice <= cameraLocations.length)) { - print("camera " + choice); - if (!freeCamera) { - saveCameraState(); - freeCamera = true; - } - Camera.mode = "independent"; - Camera.setPosition(cameraLocations[choice - 1]); - Camera.keepLookingAt(cameraLookAts[choice - 1]); - } - if (event.text == "ESC") { - cameraNumber = 0; - freeCamera = false; - restoreCameraState(); - } - if (event.text == "0") { - // Show camera location in log - var cameraLocation = Camera.getPosition(); - print(cameraLocation.x + ", " + cameraLocation.y + ", " + cameraLocation.z); - } -} - -Script.update.connect(update); -Controller.keyPressEvent.connect(keyPressEvent); diff --git a/examples/concertCamera_kims.js b/examples/concertCamera_kims.js deleted file mode 100644 index ff4fb632de..0000000000 --- a/examples/concertCamera_kims.js +++ /dev/null @@ -1,72 +0,0 @@ -// -// concertCamera.js -// -// Created by Philip Rosedale on June 24, 2014 -// Copyright 2014 High Fidelity, Inc. -// -// Move a camera through a series of pre-set locations by pressing number keys -// -// Distributed under the Apache License, Version 2.0. -// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html -// - -var oldMode; -var avatarPosition; - -var cameraNumber = 0; -var freeCamera = false; - -var cameraLocations = [ {x: 8027.5, y: 237.5, z: 7305.7}, {x: 8027.5, y: 237.5, z: 7306.6}, {x: 8027.5, y: 237.5, z: 7308.0}, {x: 8027.5, y: 237.5, z: 7303.0}, {x: 8030.8, y: 238.6, z: 7311.4}, {x: 8030.9, y: 237.1, z: 7308.0} ]; -var cameraLookAts = [ {x: 8027.5, y: 237.5, z: 7304.0}, {x: 8027.5, y: 237.5, z: 7305.7}, {x: 8027.5, y: 237.5, z: 7304.0}, {x: 8027.5, y: 237.5, z: 7304.0}, {x: 8027.5, y: 237.5, z: 7304.0}, {x: 8027.5, y: 237.5, z: 7304.0} ]; - -function saveCameraState() { - oldMode = Camera.mode; - avatarPosition = MyAvatar.position; - Camera.setModeShiftPeriod(0.0); - Camera.mode = "independent"; -} - -function restoreCameraState() { - Camera.stopLooking(); - Camera.mode = oldMode; -} - -function update(deltaTime) { - if (freeCamera) { - var delta = Vec3.subtract(MyAvatar.position, avatarPosition); - if (Vec3.length(delta) > 0.05) { - cameraNumber = 0; - freeCamera = false; - restoreCameraState(); - } - } -} - -function keyPressEvent(event) { - - var choice = parseInt(event.text); - - if ((choice > 0) && (choice <= cameraLocations.length)) { - print("camera " + choice); - if (!freeCamera) { - saveCameraState(); - freeCamera = true; - } - Camera.mode = "independent"; - Camera.setPosition(cameraLocations[choice - 1]); - Camera.keepLookingAt(cameraLookAts[choice - 1]); - } - if (event.text == "ESC") { - cameraNumber = 0; - freeCamera = false; - restoreCameraState(); - } - if (event.text == "0") { - // Show camera location in log - var cameraLocation = Camera.getPosition(); - print(cameraLocation.x + ", " + cameraLocation.y + ", " + cameraLocation.z); - } -} - -Script.update.connect(update); -Controller.keyPressEvent.connect(keyPressEvent); diff --git a/examples/concertCamera_kyrs.js b/examples/concertCamera_kyrs.js deleted file mode 100644 index 4c7c893783..0000000000 --- a/examples/concertCamera_kyrs.js +++ /dev/null @@ -1,72 +0,0 @@ -// -// concertCamera.js -// -// Created by Philip Rosedale on June 24, 2014 -// Copyright 2014 High Fidelity, Inc. -// -// Move a camera through a series of pre-set locations by pressing number keys -// -// Distributed under the Apache License, Version 2.0. -// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html -// - -var oldMode; -var avatarPosition; - -var cameraNumber = 0; -var freeCamera = false; - -var cameraLocations = [ {x: 2921.5, y: 251.3, z: 8254.8}, {x: 2921.5, y: 251.3, z: 8254.4}, {x: 2921.5, y: 251.3, z: 8252.2}, {x: 2921.5, y: 251.3, z: 8247.2}, {x: 2921.4, y: 251.3, z: 8255.7} ]; -var cameraLookAts = [ {x: 2921.5, y: 251.3, z: 8255.7}, {x: 2921.5, y: 251.3, z: 8255.7}, {x: 2921.5, y: 251.3, z: 8255.7}, {x: 2921.5, y: 251.3, z: 8255.7}, {x: 2921.4 , y: 251.3, z: 8255.1} ]; - -function saveCameraState() { - oldMode = Camera.mode; - avatarPosition = MyAvatar.position; - Camera.setModeShiftPeriod(0.0); - Camera.mode = "independent"; -} - -function restoreCameraState() { - Camera.stopLooking(); - Camera.mode = oldMode; -} - -function update(deltaTime) { - if (freeCamera) { - var delta = Vec3.subtract(MyAvatar.position, avatarPosition); - if (Vec3.length(delta) > 0.05) { - cameraNumber = 0; - freeCamera = false; - restoreCameraState(); - } - } -} - -function keyPressEvent(event) { - - var choice = parseInt(event.text); - - if ((choice > 0) && (choice <= cameraLocations.length)) { - print("camera " + choice); - if (!freeCamera) { - saveCameraState(); - freeCamera = true; - } - Camera.mode = "independent"; - Camera.setPosition(cameraLocations[choice - 1]); - Camera.keepLookingAt(cameraLookAts[choice - 1]); - } - if (event.text == "ESC") { - cameraNumber = 0; - freeCamera = false; - restoreCameraState(); - } - if (event.text == "0") { - // Show camera location in log - var cameraLocation = Camera.getPosition(); - print(cameraLocation.x + ", " + cameraLocation.y + ", " + cameraLocation.z); - } -} - -Script.update.connect(update); -Controller.keyPressEvent.connect(keyPressEvent); diff --git a/examples/drumStick.js b/examples/drumStick.js index d0560057c0..1af9ffc3dd 100644 --- a/examples/drumStick.js +++ b/examples/drumStick.js @@ -63,8 +63,11 @@ function checkSticks(deltaTime) { // Waiting for change in velocity direction or slowing to trigger drum sound if ((palmVelocity.y > 0.0) || (speed < STOP_SPEED)) { state[palm] = 0; - var options = new AudioInjectionOptions(); - options.position = Controller.getSpatialControlPosition(palm * 2 + 1); + + var options = { + position: Controller.getSpatialControlPosition(palm * 2 + 1); + } + if (strokeSpeed[palm] > 1.0) { strokeSpeed[palm] = 1.0; } options.volume = strokeSpeed[palm]; diff --git a/examples/editVoxels.js b/examples/editVoxels.js index e450f2d1d4..0747b9269f 100644 --- a/examples/editVoxels.js +++ b/examples/editVoxels.js @@ -68,9 +68,11 @@ var numColors = 9; var whichColor = 0; // Starting color is 'Copy' mode // Create sounds for for every script actions that require one -var audioOptions = new AudioInjectionOptions(); -audioOptions.volume = 1.0; -audioOptions.position = Vec3.sum(MyAvatar.position, { x: 0, y: 1, z: 0 } ); // start with audio slightly above the avatar +// start with audio slightly above the avatar +var audioOptions = { + position: Vec3.sum(MyAvatar.position, { x: 0, y: 1, z: 0 } ), + volume: 1.0 +}; function SoundArray() { this.audioOptions = audioOptions diff --git a/examples/entityBirds.js b/examples/entityBirds.js index bbc35a5f58..d18513ba49 100644 --- a/examples/entityBirds.js +++ b/examples/entityBirds.js @@ -135,10 +135,10 @@ function updateBirds(deltaTime) { // Tweeting behavior if (birds[i].tweeting == 0) { if (Math.random() < CHANCE_OF_TWEETING) { - var options = new AudioInjectionOptions(); - options.position = properties.position; - options.volume = 0.75; - Audio.playSound(birds[i].tweetSound, options); + Audio.playSound(birds[i].tweetSound, { + position: properties.position, + volume: 0.75 + }); birds[i].tweeting = 10; } } else { diff --git a/examples/entityScripts/playSoundOnClick.js b/examples/entityScripts/playSoundOnClick.js index 4bc523a7aa..4ab83a1952 100644 --- a/examples/entityScripts/playSoundOnClick.js +++ b/examples/entityScripts/playSoundOnClick.js @@ -14,14 +14,6 @@ (function(){ var bird; - function playSound(entityID) { - var options = new AudioInjectionOptions(); - var position = MyAvatar.position; - options.position = position; - options.volume = 0.5; - Audio.playSound(bird, options); - }; - this.preload = function(entityID) { print("preload("+entityID.id+")"); bird = new Sound("http://s3.amazonaws.com/hifi-public/sounds/Animals/bushtit_1.raw"); @@ -29,6 +21,9 @@ this.clickDownOnEntity = function(entityID, mouseEvent) { print("clickDownOnEntity()..."); - playSound(); + Audio.playSound(bird, { + position: MyAvatar.position, + volume: 0.5 + }); }; }) diff --git a/examples/entityScripts/playSoundOnEnterOrLeave.js b/examples/entityScripts/playSoundOnEnterOrLeave.js index 98702dcfdd..07be090c31 100644 --- a/examples/entityScripts/playSoundOnEnterOrLeave.js +++ b/examples/entityScripts/playSoundOnEnterOrLeave.js @@ -14,13 +14,12 @@ (function(){ var bird; - function playSound() { - var options = new AudioInjectionOptions(); - var position = MyAvatar.position; - options.position = position; - options.volume = 0.5; - Audio.playSound(bird, options); - }; + function playSound(entityID) { + Audio.playSound(bird, { + position: MyAvatar.position, + volume: 0.5 + }); + } this.preload = function(entityID) { print("preload("+entityID.id+")"); @@ -31,7 +30,7 @@ playSound(); }; - this.leaveEntity = function(entityID) { + this.leaveEntity = function(entityID) { playSound(); }; }) diff --git a/examples/frisbee.js b/examples/frisbee.js index c534a8b3fb..7e266de34b 100644 --- a/examples/frisbee.js +++ b/examples/frisbee.js @@ -177,10 +177,10 @@ function playSound(sound, position) { if (!SOUNDS_ENABLED) { return; } - var options = new AudioInjectionOptions(); - options.position = position; - options.volume = 1.0; - Audio.playSound(sound, options); + + Audio.playSound(sound,{ + position: position + }); } function cleanupFrisbees() { diff --git a/examples/grenadeLauncher.js b/examples/grenadeLauncher.js index bca067326a..e95d8dd79d 100644 --- a/examples/grenadeLauncher.js +++ b/examples/grenadeLauncher.js @@ -44,8 +44,9 @@ var targetLaunchSound = new Sound(HIFI_PUBLIC_BUCKET + "sounds/Space%20Invaders/ var gunModel = "http://public.highfidelity.io/models/attachments/HaloGun.fst"; -var audioOptions = new AudioInjectionOptions(); -audioOptions.volume = 0.9; +var audioOptions { + volume: 0.9 +} var shotsFired = 0; diff --git a/examples/gun.js b/examples/gun.js index 385664226c..76084ce013 100644 --- a/examples/gun.js +++ b/examples/gun.js @@ -43,8 +43,9 @@ var targetLaunchSound = new Sound(HIFI_PUBLIC_BUCKET + "sounds/Space%20Invaders/ var gunModel = "http://public.highfidelity.io/models/attachments/HaloGun.fst"; -var audioOptions = new AudioInjectionOptions(); -audioOptions.volume = 0.9; +var audioOptions = { + volume: 0.9 +} var shotsFired = 0; diff --git a/examples/headMove.js b/examples/headMove.js index b1f1c4ab7d..957686bb20 100644 --- a/examples/headMove.js +++ b/examples/headMove.js @@ -72,15 +72,11 @@ var WATCH_AVATAR_DISTANCE = 2.5; var sound = new Sound("http://public.highfidelity.io/sounds/Footsteps/FootstepW2Right-12db.wav"); function playSound() { - var options = new AudioInjectionOptions(); - var position = MyAvatar.position; - options.position = position; - options.volume = 1.0; - Audio.playSound(sound, options); + Audio.playSound(sound, { + position: MyAvatar.position + }); } - - function pullBack() { saveCameraState(); cameraPosition = Vec3.subtract(MyAvatar.position, Vec3.multiplyQbyV(Camera.getOrientation(), { x: 0, y: -hipsToEyes, z: -hipsToEyes * WATCH_AVATAR_DISTANCE })); diff --git a/examples/inWorldTestTone.js b/examples/inWorldTestTone.js index 590bb6c342..b3bf91d14d 100644 --- a/examples/inWorldTestTone.js +++ b/examples/inWorldTestTone.js @@ -19,11 +19,9 @@ var soundPlaying = false; function update(deltaTime) { if (!Audio.isInjectorPlaying(soundPlaying)) { - var options = new AudioInjectionOptions(); - options.position = { x:0, y:0, z:0 }; - options.volume = 1.0; - options.loop = true; - soundPlaying = Audio.playSound(sound, options); + soundPlaying = Audio.playSound(sound, { + loop: true + }); print("Started sound loop"); } } diff --git a/examples/libraries/entityPropertyDialogBox.js b/examples/libraries/entityPropertyDialogBox.js index ef597549f2..da60e0c370 100644 --- a/examples/libraries/entityPropertyDialogBox.js +++ b/examples/libraries/entityPropertyDialogBox.js @@ -22,6 +22,9 @@ EntityPropertyDialogBox = (function () { var dimensionZ; var rescalePercentage; var editModelID = -1; + var previousAnimationIsPlaying; + var previousAnimationFrameIndex; + var previousAnimationSettings; that.cleanup = function () { }; @@ -47,10 +50,15 @@ EntityPropertyDialogBox = (function () { array.push({ label: "Animation URL:", value: properties.animationURL }); index++; array.push({ label: "Animation is playing:", value: properties.animationIsPlaying }); + previousAnimationIsPlaying = properties.animationIsPlaying; index++; array.push({ label: "Animation FPS:", value: properties.animationFPS }); index++; array.push({ label: "Animation Frame:", value: properties.animationFrameIndex }); + previousAnimationFrameIndex = properties.animationFrameIndex; + index++; + array.push({ label: "Animation Settings:", value: properties.animationSettings }); + previousAnimationSettings = properties.animationSettings; index++; array.push({ label: "Textures:", value: properties.textures }); index++; @@ -237,9 +245,29 @@ EntityPropertyDialogBox = (function () { if (properties.type == "Model") { properties.modelURL = array[index++].value; properties.animationURL = array[index++].value; - properties.animationIsPlaying = array[index++].value; + + var newAnimationIsPlaying = array[index++].value; + if (previousAnimationIsPlaying != newAnimationIsPlaying) { + properties.animationIsPlaying = newAnimationIsPlaying; + } else { + delete properties.animationIsPlaying; + } + properties.animationFPS = array[index++].value; - properties.animationFrameIndex = array[index++].value; + + var newAnimationFrameIndex = array[index++].value; + if (previousAnimationFrameIndex != newAnimationFrameIndex) { + properties.animationFrameIndex = newAnimationFrameIndex; + } else { + delete properties.animationFrameIndex; + } + + var newAnimationSettings = array[index++].value; + if (previousAnimationSettings != newAnimationSettings) { + properties.animationSettings = newAnimationSettings; + } else { + delete properties.animationSettings; + } properties.textures = array[index++].value; index++; // skip textureNames label } diff --git a/examples/lobby.js b/examples/lobby.js index 63ea1654a9..1b6596efa7 100644 --- a/examples/lobby.js +++ b/examples/lobby.js @@ -37,7 +37,14 @@ var panelsCenterShift = Vec3.subtract(panelsCenter, orbCenter); var ORB_SHIFT = { x: 0, y: -1.4, z: -0.8}; -var HELMET_ATTACHMENT_URL = "https://hifi-public.s3.amazonaws.com/models/attachments/IronManMaskOnly.fbx" +var HELMET_ATTACHMENT_URL = HIFI_PUBLIC_BUCKET + "models/attachments/IronManMaskOnly.fbx" + +var droneSound = new Sound(HIFI_PUBLIC_BUCKET + "sounds/Lobby/drone.raw") +var currentDrone = null; + +var latinSound = new Sound(HIFI_PUBLIC_BUCKET + "sounds/Lobby/latin.raw") +var elevatorSound = new Sound(HIFI_PUBLIC_BUCKET + "sounds/Lobby/elevator.raw") +var currentMusak = null; function reticlePosition() { var RETICLE_DISTANCE = 1; @@ -87,6 +94,12 @@ function drawLobby() { // add an attachment on this avatar so other people see them in the lobby MyAvatar.attach(HELMET_ATTACHMENT_URL, "Neck", {x: 0, y: 0, z: 0}, Quat.fromPitchYawRollDegrees(0, 0, 0), 1.15); + + // start the drone sound + currentDrone = Audio.playSound(droneSound, { stereo: true, loop: true, localOnly: true }); + + // start one of our musak sounds + playRandomMusak(); } } @@ -112,11 +125,35 @@ function changeLobbyTextures() { Overlays.editOverlay(panelWall, textureProp); } +function playRandomMusak() { + chosenSound = null; + + if (latinSound.downloaded && elevatorSound.downloaded) { + chosenSound = Math.random < 0.5 ? latinSound : elevatorSound; + } else if (latinSound.downloaded) { + chosenSound = latinSound; + } else if (elevatorSound.downloaded) { + chosenSound = elevatorSound; + } + + if (chosenSound) { + currentMusak = Audio.playSound(chosenSound, { stereo: true, localOnly: true }) + } else { + currentMusak = null; + } +} + function cleanupLobby() { Overlays.deleteOverlay(panelWall); Overlays.deleteOverlay(orbShell); Overlays.deleteOverlay(reticle); + Audio.stopInjector(currentDrone); + currentDrone = null; + + Audio.stopInjector(currentMusak); + currentMusak = null; + panelWall = false; orbShell = false; reticle = false; diff --git a/examples/overlaysExample.js b/examples/overlaysExample.js index fef502c761..c7bc28db96 100644 --- a/examples/overlaysExample.js +++ b/examples/overlaysExample.js @@ -68,7 +68,8 @@ var text = Overlays.addOverlay("text", { color: { red: 255, green: 0, blue: 0}, topMargin: 4, leftMargin: 4, - text: "Here is some text.\nAnd a second line." + text: "Here is some text.\nAnd a second line.", + alpha: 0.7 }); // This will create an image overlay, which starts out as invisible diff --git a/examples/playSound.js b/examples/playSound.js index 4130db5b16..efcda0b42b 100644 --- a/examples/playSound.js +++ b/examples/playSound.js @@ -15,12 +15,11 @@ var bird = new Sound(HIFI_PUBLIC_BUCKET + "sounds/Animals/bushtit_1.raw"); function maybePlaySound(deltaTime) { if (Math.random() < 0.01) { - // Set the location and other info for the sound to play - var options = new AudioInjectionOptions(); - var position = MyAvatar.position; - options.position = position; - options.volume = 0.5; - Audio.playSound(bird, options); + // Set the location and other info for the sound to play + Audio.playSound(bird, { + position: MyAvatar.position, + volume: 0.5 + }); } } diff --git a/examples/playSoundLoop.js b/examples/playSoundLoop.js index 3122f13f37..b84c475d1a 100644 --- a/examples/playSoundLoop.js +++ b/examples/playSoundLoop.js @@ -20,10 +20,12 @@ var sound = new Sound(HIFI_PUBLIC_BUCKET + "sounds/Guitars/Guitar+-+Nylon+A.raw" //var sound = new Sound(HIFI_PUBLIC_BUCKET + "sounds/Cocktail+Party+Snippets/Bandcamp.wav"); var soundPlaying = false; -var options = new AudioInjectionOptions(); -options.position = Vec3.sum(Camera.getPosition(), Quat.getFront(MyAvatar.orientation)); -options.volume = 0.5; -options.loop = true; +var options = { + position: Vec3.sum(Camera.getPosition(), Quat.getFront(MyAvatar.orientation)), + volume: 0.5, + loop: true +} + var playing = false; var ball = false; diff --git a/examples/playSoundOrbit.js b/examples/playSoundOrbit.js index 2c44a4535a..d98f7d0768 100644 --- a/examples/playSoundOrbit.js +++ b/examples/playSoundOrbit.js @@ -19,24 +19,23 @@ var distance = 1; var debug = 0; function playSound() { - var options = new AudioInjectionOptions(); - currentTime += deltaTime; + currentTime += deltaTime; var s = distance * Math.sin(currentTime); var c = distance * Math.cos(currentTime); - var soundOffset = { x:s, y:0, z:c }; + var soundOffset = { x:s, y:0, z:c }; - if (debug) { - print("t=" + currentTime + "offset=" + soundOffset.x + "," + soundOffset.y + "," + soundOffset.z); - } + if (debug) { + print("t=" + currentTime + "offset=" + soundOffset.x + "," + soundOffset.y + "," + soundOffset.z); + } - var avatarPosition = MyAvatar.position; - var soundPosition = Vec3.sum(avatarPosition,soundOffset); + var avatarPosition = MyAvatar.position; + var soundPosition = Vec3.sum(avatarPosition,soundOffset); - options.position = soundPosition - options.volume = 1.0; - Audio.playSound(soundClip, options); + Audio.playSound(soundClip, { + position: soundPosition + }); } Script.setInterval(playSound, 250); diff --git a/examples/playSoundWave.js b/examples/playSoundWave.js index f152effb47..c5e69f5cd6 100644 --- a/examples/playSoundWave.js +++ b/examples/playSoundWave.js @@ -14,11 +14,10 @@ Script.include("libraries/globals.js"); var soundClip = new Sound(HIFI_PUBLIC_BUCKET + "sounds/Cocktail%20Party%20Snippets/Walken1.wav"); function playSound() { - var options = new AudioInjectionOptions(); - var position = MyAvatar.position; - options.position = position; - options.volume = 0.5; - Audio.playSound(soundClip, options); + Audio.playSound(soundClip, { + position: MyAvatar.position, + volume: 0.5 + }); } Script.setInterval(playSound, 10000); diff --git a/examples/radio.js b/examples/radio.js index 293867398a..fc09fb184e 100644 --- a/examples/radio.js +++ b/examples/radio.js @@ -15,10 +15,12 @@ var modelURL = HIFI_PUBLIC_BUCKET + "models/entities/radio/Speakers.fbx"; var soundURL = HIFI_PUBLIC_BUCKET + "sounds/FamilyStereo.raw"; var AudioRotationOffset = Quat.fromPitchYawRollDegrees(0, -90, 0); -var audioOptions = new AudioInjectionOptions(); -audioOptions.volume = 0.5; -audioOptions.loop = true; -audioOptions.isStereo = true; +var audioOptions = { + volume: 0.5, + loop: true, + stereo: true +} + var injector = null; var sound = new Sound(soundURL, audioOptions.isStereo); diff --git a/examples/spaceInvadersExample.js b/examples/spaceInvadersExample.js index 8d69f066d6..dd5ac9e875 100644 --- a/examples/spaceInvadersExample.js +++ b/examples/spaceInvadersExample.js @@ -217,7 +217,8 @@ function update(deltaTime) { if (invaderStepOfCycle % stepsPerSound == 0) { // play the move sound - var options = new AudioInjectionOptions(); + var options = {}; + if (soundInMyHead) { options.position = { x: MyAvatar.position.x + 0.0, y: MyAvatar.position.y + 0.1, @@ -225,7 +226,7 @@ function update(deltaTime) { } else { options.position = getInvaderPosition(invadersPerRow / 2, numberOfRows / 2); } - options.volume = 1.0; + Audio.playSound(moveSounds[currentMoveSound], options); // get ready for next move sound @@ -330,7 +331,7 @@ function fireMissile() { lifetime: 5 }); - var options = new AudioInjectionOptions(); + var options = {} if (soundInMyHead) { options.position = { x: MyAvatar.position.x + 0.0, y: MyAvatar.position.y + 0.1, @@ -338,7 +339,7 @@ function fireMissile() { } else { options.position = missilePosition; } - options.volume = 1.0; + Audio.playSound(shootSound, options); missileFired = true; @@ -380,7 +381,7 @@ function deleteIfInvader(possibleInvaderEntity) { Entities.deleteEntity(myMissile); // play the hit sound - var options = new AudioInjectionOptions(); + var options = {}; if (soundInMyHead) { options.position = { x: MyAvatar.position.x + 0.0, y: MyAvatar.position.y + 0.1, @@ -388,7 +389,7 @@ function deleteIfInvader(possibleInvaderEntity) { } else { options.position = getInvaderPosition(row, column); } - options.volume = 1.0; + Audio.playSound(hitSound, options); } } diff --git a/examples/toyball.js b/examples/toyball.js index b41dd2bda5..1cd6de16eb 100644 --- a/examples/toyball.js +++ b/examples/toyball.js @@ -113,10 +113,7 @@ function checkControllerSide(whichSide) { inHand: true }; Entities.editEntity(closestEntity, properties); - var options = new AudioInjectionOptions(); - options.position = ballPosition; - options.volume = 1.0; - Audio.playSound(catchSound, options); + Audio.playSound(catchSound, { position: ballPosition }); return; // exit early } @@ -156,10 +153,7 @@ function checkControllerSide(whichSide) { } // Play a new ball sound - var options = new AudioInjectionOptions(); - options.position = ballPosition; - options.volume = 1.0; - Audio.playSound(newSound, options); + Audio.playSound(newSound, { position: ballPosition}); return; // exit early } @@ -207,10 +201,7 @@ function checkControllerSide(whichSide) { rightHandEntity = false; } - var options = new AudioInjectionOptions(); - options.position = ballPosition; - options.volume = 1.0; - Audio.playSound(throwSound, options); + Audio.playSound(throwSound, { position: ballPosition }); } } } diff --git a/examples/walk.js b/examples/walk.js index 279516aa2a..5a0df72f26 100644 --- a/examples/walk.js +++ b/examples/walk.js @@ -88,7 +88,6 @@ Script.update.connect(function(deltaTime) { animateAvatar(deltaTime, speed); break; } - case state.EDIT_STANDING: { motion.curAnim = motion.selStand; motion.direction = FORWARDS; @@ -559,9 +558,11 @@ function getLeanRoll(deltaTime, speed) { function playFootstep(side) { - var options = new AudioInjectionOptions(); - options.position = Camera.getPosition(); - options.volume = 0.3; + options = { + position: Camera.getPosition(), + volume: 0.3 + } + var soundNumber = 2; // 0 to 2 if (side === RIGHT && motion.makesFootStepSounds) { Audio.playSound(walkAssets.footsteps[soundNumber + 1], options); @@ -2609,4 +2610,4 @@ function animateAvatar(deltaTime, speed) { } // Begin by setting an internal state -state.setInternalState(state.STANDING); \ No newline at end of file +state.setInternalState(state.STANDING); diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index 26037800df..c3ab4fe115 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -109,6 +109,7 @@ static unsigned STARFIELD_SEED = 1; static const int BANDWIDTH_METER_CLICK_MAX_DRAG_LENGTH = 6; // farther dragged clicks are ignored + const qint64 MAXIMUM_CACHE_SIZE = 10737418240; // 10GB static QTimer* idleTimer = NULL; @@ -159,6 +160,7 @@ Application::Application(int& argc, char** argv, QElapsedTimer &startup_time) : _lastQueriedViewFrustum(), _lastQueriedTime(usecTimestampNow()), _mirrorViewRect(QRect(MIRROR_VIEW_LEFT_PADDING, MIRROR_VIEW_TOP_PADDING, MIRROR_VIEW_WIDTH, MIRROR_VIEW_HEIGHT)), + _viewTransform(new gpu::Transform()), _scaleMirror(1.0f), _rotateMirror(0.0f), _raiseMirror(0.0f), @@ -418,7 +420,7 @@ Application::Application(int& argc, char** argv, QElapsedTimer &startup_time) : _trayIcon->show(); // set the local loopback interface for local sounds from audio scripts - AudioScriptingInterface::getInstance().setLocalLoopbackInterface(&_audio); + AudioScriptingInterface::getInstance().setLocalAudioInterface(&_audio); #ifdef HAVE_RTMIDI // setup the MIDIManager @@ -454,23 +456,22 @@ Application::~Application() { // ask the datagram processing thread to quit and wait until it is done _nodeThread->quit(); _nodeThread->wait(); + + // kill any audio injectors that are still around + AudioScriptingInterface::getInstance().stopAllInjectors(); // stop the audio process QMetaObject::invokeMethod(&_audio, "stop"); - + // ask the audio thread to quit and wait until it is done _audio.thread()->quit(); _audio.thread()->wait(); - - // kill any audio injectors that are still around - AudioScriptingInterface::getInstance().stopAllInjectors(); _octreeProcessor.terminate(); _voxelHideShowThread.terminate(); _voxelEditSender.terminate(); _entityEditSender.terminate(); - VoxelTreeElement::removeDeleteHook(&_voxels); // we don't need to do this processing on shutdown Menu::getInstance()->deleteLater(); @@ -837,12 +838,14 @@ void Application::controlledBroadcastToNodes(const QByteArray& packet, const Nod } bool Application::event(QEvent* event) { + // handle custom URL if (event->type() == QEvent::FileOpen) { QFileOpenEvent* fileEvent = static_cast(event); - if (fileEvent->url().isValid()) { - openUrl(fileEvent->url()); + + if (!fileEvent->url().isEmpty()) { + AddressManager::getInstance().handleLookupString(fileEvent->url().toLocalFile()); } return false; @@ -2796,6 +2799,8 @@ void Application::updateShadowMap() { // store view matrix without translation, which we'll use for precision-sensitive objects updateUntranslatedViewMatrix(); + // TODO: assign an equivalent viewTransform object to the application to match the current path which uses glMatrixStack + // setViewTransform(viewTransform); glEnable(GL_POLYGON_OFFSET_FILL); glPolygonOffset(1.1f, 4.0f); // magic numbers courtesy http://www.eecs.berkeley.edu/~ravir/6160/papers/shadowmaps.ppt @@ -2903,6 +2908,19 @@ void Application::displaySide(Camera& whichCamera, bool selfAvatarOnly) { // store view matrix without translation, which we'll use for precision-sensitive objects updateUntranslatedViewMatrix(-whichCamera.getPosition()); + // Equivalent to what is happening with _untranslatedViewMatrix and the _viewMatrixTranslation + // the viewTransofmr object is updatded with the correct values and saved, + // this is what is used for rendering the Entities and avatars + gpu::Transform viewTransform; + viewTransform.setTranslation(whichCamera.getPosition()); + viewTransform.setRotation(rotation); + viewTransform.postTranslate(eyeOffsetPos); + viewTransform.postRotate(eyeOffsetOrient); + if (whichCamera.getMode() == CAMERA_MODE_MIRROR) { + viewTransform.setScale(gpu::Transform::Vec3(-1.0f, 1.0f, 1.0f)); + } + setViewTransform(viewTransform); + glTranslatef(_viewMatrixTranslation.x, _viewMatrixTranslation.y, _viewMatrixTranslation.z); // Setup 3D lights (after the camera transform, so that they are positioned in world space) @@ -3020,13 +3038,16 @@ void Application::displaySide(Camera& whichCamera, bool selfAvatarOnly) { } } + + bool mirrorMode = (whichCamera.getMode() == CAMERA_MODE_MIRROR); { PerformanceTimer perfTimer("avatars"); _avatarManager.renderAvatars(mirrorMode ? Avatar::MIRROR_RENDER_MODE : Avatar::NORMAL_RENDER_MODE, false, selfAvatarOnly); } - + + { PROFILE_RANGE("DeferredLighting"); PerformanceTimer perfTimer("lighting"); @@ -3085,7 +3106,7 @@ void Application::displaySide(Camera& whichCamera, bool selfAvatarOnly) { emit renderingInWorldInterface(); } } - + if (Menu::getInstance()->isOptionChecked(MenuOption::Wireframe)) { glPolygonMode(GL_FRONT_AND_BACK, GL_FILL); } @@ -3096,6 +3117,10 @@ void Application::updateUntranslatedViewMatrix(const glm::vec3& viewMatrixTransl _viewMatrixTranslation = viewMatrixTranslation; } +void Application::setViewTransform(const gpu::Transform& view) { + (*_viewTransform) = view; +} + void Application::loadTranslatedViewMatrix(const glm::vec3& translation) { glLoadMatrixf((const GLfloat*)&_untranslatedViewMatrix); glTranslatef(translation.x + _viewMatrixTranslation.x, translation.y + _viewMatrixTranslation.y, diff --git a/interface/src/Application.h b/interface/src/Application.h index 31d4fd8da2..9ce857ee8a 100644 --- a/interface/src/Application.h +++ b/interface/src/Application.h @@ -232,6 +232,9 @@ public: const glm::vec3& getViewMatrixTranslation() const { return _viewMatrixTranslation; } void setViewMatrixTranslation(const glm::vec3& translation) { _viewMatrixTranslation = translation; } + const gpu::TransformPointer& getViewTransform() const { return _viewTransform; } + void setViewTransform(const gpu::Transform& view); + /// if you need to access the application settings, use lockSettings()/unlockSettings() QSettings* lockSettings() { _settingsMutex.lock(); return _settings; } void unlockSettings() { _settingsMutex.unlock(); } @@ -523,6 +526,7 @@ private: QRect _mirrorViewRect; RearMirrorTools* _rearMirrorTools; + gpu::TransformPointer _viewTransform; glm::mat4 _untranslatedViewMatrix; glm::vec3 _viewMatrixTranslation; glm::mat4 _projectionMatrix; diff --git a/interface/src/Audio.cpp b/interface/src/Audio.cpp index 96121d9a8c..2dc94e6027 100644 --- a/interface/src/Audio.cpp +++ b/interface/src/Audio.cpp @@ -32,19 +32,21 @@ #include #include +#include + +#include #include #include #include #include #include -#include - -#include "Audio.h" #include "Menu.h" #include "Util.h" #include "PositionalAudioStream.h" +#include "Audio.h" + static const float AUDIO_CALLBACK_MSECS = (float) NETWORK_BUFFER_LENGTH_SAMPLES_PER_CHANNEL / (float)SAMPLE_RATE * 1000.0; static const int NUMBER_OF_NOISE_SAMPLE_FRAMES = 300; @@ -1366,24 +1368,34 @@ void Audio::startDrumSound(float volume, float frequency, float duration, float _drumSoundSample = 0; } -void Audio::handleAudioByteArray(const QByteArray& audioByteArray, const AudioInjectorOptions& injectorOptions) { - if (audioByteArray.size() > 0) { - QAudioFormat localFormat = _outputFormat; +bool Audio::outputLocalInjector(bool isStereo, qreal volume, AudioInjector* injector) { + if (injector->getLocalBuffer()) { + QAudioFormat localFormat = _desiredOutputFormat; + localFormat.setChannelCount(isStereo ? 2 : 1); - if (!injectorOptions.isStereo()) { - localFormat.setChannelCount(1); - } + QAudioOutput* localOutput = new QAudioOutput(getNamedAudioDeviceForMode(QAudio::AudioOutput, _outputAudioDeviceName), + localFormat, this); + localOutput->setVolume(volume); - QAudioOutput* localSoundOutput = new QAudioOutput(getNamedAudioDeviceForMode(QAudio::AudioOutput, _outputAudioDeviceName), localFormat, this); + // add this to our list of local injected outputs, we will need to clean it up when the injector says it is done + _injectedOutputInterfaces.insert(injector, localOutput); - QIODevice* localIODevice = localSoundOutput->start(); - if (localIODevice) { - localIODevice->write(audioByteArray); - } else { - qDebug() << "Unable to handle audio byte array. Error:" << localSoundOutput->error(); - } - } else { - qDebug() << "Audio::handleAudioByteArray called with an empty byte array. Sound is likely still downloading."; + connect(injector, &AudioInjector::finished, this, &Audio::cleanupLocalOutputInterface); + + localOutput->start(injector->getLocalBuffer()); + return localOutput->state() == QAudio::ActiveState; + } + + return false; +} + +void Audio::cleanupLocalOutputInterface() { + QAudioOutput* outputInterface = _injectedOutputInterfaces.value(sender()); + if (outputInterface) { + qDebug() << "Stopping a QAudioOutput interface since injector" << sender() << "is finished"; + + outputInterface->stop(); + outputInterface->deleteLater(); } } diff --git a/interface/src/Audio.h b/interface/src/Audio.h index cc0da78198..d8e5c5386c 100644 --- a/interface/src/Audio.h +++ b/interface/src/Audio.h @@ -155,7 +155,7 @@ public slots: void selectAudioFilterBassCut(); void selectAudioFilterSmiley(); - virtual void handleAudioByteArray(const QByteArray& audioByteArray, const AudioInjectorOptions& options); + virtual bool outputLocalInjector(bool isStereo, qreal volume, AudioInjector* injector); void sendDownstreamAudioStatsPacket(); @@ -180,11 +180,11 @@ signals: void processInboundAudio(unsigned int sampleTime, const QByteArray& samples, const QAudioFormat& format); void processLocalAudio(unsigned int sampleTime, const QByteArray& samples, const QAudioFormat& format); +private slots: + void cleanupLocalOutputInterface(); private: void outputFormatChanged(); -private: - QByteArray firstInputFrame; QAudioInput* _audioInput; QAudioFormat _desiredInputFormat; @@ -256,10 +256,6 @@ private: float _iconColor; qint64 _iconPulseTimeReference; - /// Audio callback in class context. - inline void performIO(int16_t* inputLeft, int16_t* outputLeft, int16_t* outputRight); - - bool _processSpatialAudio; /// Process received audio by spatial audio hooks unsigned int _spatialAudioStart; /// Start of spatial audio interval (in sample rate time base) unsigned int _spatialAudioFinish; /// End of spatial audio interval (in sample rate time base) @@ -372,6 +368,8 @@ private: AudioOutputIODevice _audioOutputIODevice; WeakRecorderPointer _recorder; + + QHash _injectedOutputInterfaces; }; diff --git a/interface/src/Menu.cpp b/interface/src/Menu.cpp index f973ad7983..e0feb4e349 100644 --- a/interface/src/Menu.cpp +++ b/interface/src/Menu.cpp @@ -437,10 +437,15 @@ Menu::Menu() : addCheckableActionToQMenuAndActionHash(entitiesDebugMenu, MenuOption::DisplayModelElementChildProxies, 0, false); addCheckableActionToQMenuAndActionHash(entitiesDebugMenu, MenuOption::DisableLightEntities, 0, false); + addCheckableActionToQMenuAndActionHash(entitiesDebugMenu, MenuOption::DontReduceMaterialSwitches, 0, false); + addCheckableActionToQMenuAndActionHash(entitiesDebugMenu, MenuOption::RenderEntitiesAsScene, 0, false); + QMenu* entityCullingMenu = entitiesDebugMenu->addMenu("Culling"); addCheckableActionToQMenuAndActionHash(entityCullingMenu, MenuOption::DontCullOutOfViewMeshParts, 0, false); addCheckableActionToQMenuAndActionHash(entityCullingMenu, MenuOption::DontCullTooSmallMeshParts, 0, false); - addCheckableActionToQMenuAndActionHash(entityCullingMenu, MenuOption::DontReduceMaterialSwitches, 0, false); + + + QMenu* voxelOptionsMenu = developerMenu->addMenu("Voxels"); addCheckableActionToQMenuAndActionHash(voxelOptionsMenu, MenuOption::VoxelTextures); diff --git a/interface/src/Menu.h b/interface/src/Menu.h index ee29833d78..b745246780 100644 --- a/interface/src/Menu.h +++ b/interface/src/Menu.h @@ -447,6 +447,7 @@ namespace MenuOption { const QString ReloadAllScripts = "Reload All Scripts"; const QString RenderBoundingCollisionShapes = "Show Bounding Collision Shapes"; const QString RenderDualContourSurfaces = "Render Dual Contour Surfaces"; + const QString RenderEntitiesAsScene = "Render Entities as Scene"; const QString RenderFocusIndicator = "Show Eye Focus"; const QString RenderHeadCollisionShapes = "Show Head Collision Shapes"; const QString RenderHeightfields = "Render Heightfields"; diff --git a/interface/src/avatar/MyAvatar.cpp b/interface/src/avatar/MyAvatar.cpp index a5c388e7f2..292cc2fb71 100644 --- a/interface/src/avatar/MyAvatar.cpp +++ b/interface/src/avatar/MyAvatar.cpp @@ -39,6 +39,7 @@ #include "Recorder.h" #include "devices/Faceshift.h" #include "devices/OculusManager.h" +#include "renderer/AnimationHandle.h" #include "ui/TextRenderer.h" using namespace std; diff --git a/interface/src/entities/EntityTreeRenderer.cpp b/interface/src/entities/EntityTreeRenderer.cpp index 1876b6c624..99e4916c62 100644 --- a/interface/src/entities/EntityTreeRenderer.cpp +++ b/interface/src/entities/EntityTreeRenderer.cpp @@ -253,7 +253,45 @@ void EntityTreeRenderer::checkEnterLeaveEntities() { } void EntityTreeRenderer::render(RenderArgs::RenderMode renderMode) { - OctreeRenderer::render(renderMode); + bool dontRenderAsScene = !Menu::getInstance()->isOptionChecked(MenuOption::RenderEntitiesAsScene); + + if (dontRenderAsScene) { + OctreeRenderer::render(renderMode); + } else { + if (_tree) { + Model::startScene(); + RenderArgs args = { this, _viewFrustum, getSizeScale(), getBoundaryLevelAdjust(), renderMode, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }; + _tree->lockForRead(); + _tree->recurseTreeWithOperation(renderOperation, &args); + + Model::RenderMode modelRenderMode = renderMode == RenderArgs::SHADOW_RENDER_MODE + ? Model::SHADOW_RENDER_MODE : Model::DEFAULT_RENDER_MODE; + + // we must call endScene while we still have the tree locked so that no one deletes a model + // on us while rendering the scene + Model::endScene(modelRenderMode, &args); + _tree->unlock(); + + // stats... + _meshesConsidered = args._meshesConsidered; + _meshesRendered = args._meshesRendered; + _meshesOutOfView = args._meshesOutOfView; + _meshesTooSmall = args._meshesTooSmall; + + _elementsTouched = args._elementsTouched; + _itemsRendered = args._itemsRendered; + _itemsOutOfView = args._itemsOutOfView; + _itemsTooSmall = args._itemsTooSmall; + + _materialSwitches = args._materialSwitches; + _trianglesRendered = args._trianglesRendered; + _quadsRendered = args._quadsRendered; + + _translucentMeshPartsRendered = args._translucentMeshPartsRendered; + _opaqueMeshPartsRendered = args._opaqueMeshPartsRendered; + } + } deleteReleasedModels(); // seems like as good as any other place to do some memory cleanup } diff --git a/interface/src/entities/RenderableModelEntityItem.cpp b/interface/src/entities/RenderableModelEntityItem.cpp index 3cee527273..8d932b121d 100644 --- a/interface/src/entities/RenderableModelEntityItem.cpp +++ b/interface/src/entities/RenderableModelEntityItem.cpp @@ -172,7 +172,12 @@ void RenderableModelEntityItem::render(RenderArgs* args) { // TODO: this is the majority of model render time. And rendering of a cube model vs the basic Box render // is significantly more expensive. Is there a way to call this that doesn't cost us as much? PerformanceTimer perfTimer("model->render"); - _model->render(alpha, modelRenderMode, args); + bool dontRenderAsScene = !Menu::getInstance()->isOptionChecked(MenuOption::RenderEntitiesAsScene); + if (dontRenderAsScene) { + _model->render(alpha, modelRenderMode, args); + } else { + _model->renderInScene(alpha, args); + } } else { // if we couldn't get a model, then just draw a cube glColor3ub(getColor()[RED_INDEX],getColor()[GREEN_INDEX],getColor()[BLUE_INDEX]); diff --git a/interface/src/gpu/Batch.cpp b/interface/src/gpu/Batch.cpp index e176e1641a..2bf92f43cf 100644 --- a/interface/src/gpu/Batch.cpp +++ b/interface/src/gpu/Batch.cpp @@ -21,7 +21,11 @@ Batch::Batch() : _commandOffsets(), _params(), _resources(), - _data(){ + _data(), + _buffers(), + _streamFormats(), + _transforms() +{ } Batch::~Batch() { @@ -32,8 +36,10 @@ void Batch::clear() { _commandOffsets.clear(); _params.clear(); _resources.clear(); - _buffers.clear(); _data.clear(); + _buffers.clear(); + _streamFormats.clear(); + _transforms.clear(); } uint32 Batch::cacheResource(Resource* res) { @@ -128,3 +134,22 @@ void Batch::setIndexBuffer(Type type, const BufferPointer& buffer, Offset offset _params.push_back(_buffers.cache(buffer)); _params.push_back(type); } + +void Batch::setModelTransform(const TransformPointer& model) { + ADD_COMMAND(setModelTransform); + + _params.push_back(_transforms.cache(model)); +} + +void Batch::setViewTransform(const TransformPointer& view) { + ADD_COMMAND(setViewTransform); + + _params.push_back(_transforms.cache(view)); +} + +void Batch::setProjectionTransform(const TransformPointer& proj) { + ADD_COMMAND(setProjectionTransform); + + _params.push_back(_transforms.cache(proj)); +} + diff --git a/interface/src/gpu/Batch.h b/interface/src/gpu/Batch.h index f9db3b9a40..3c15fef63b 100644 --- a/interface/src/gpu/Batch.h +++ b/interface/src/gpu/Batch.h @@ -14,10 +14,10 @@ #include #include "InterfaceConfig.h" +#include "Transform.h" + #include -#include "gpu/Format.h" -#include "gpu/Resource.h" #include "gpu/Stream.h" #if defined(NSIGHT_FOUND) @@ -50,6 +50,10 @@ enum Primitive { NUM_PRIMITIVES, }; +typedef ::Transform Transform; +typedef QSharedPointer< ::gpu::Transform > TransformPointer; +typedef std::vector< TransformPointer > Transforms; + class Batch { public: typedef Stream::Slot Slot; @@ -60,11 +64,16 @@ public: void clear(); + // Drawcalls void draw(Primitive primitiveType, uint32 numVertices, uint32 startVertex = 0); void drawIndexed(Primitive primitiveType, uint32 nbIndices, uint32 startIndex = 0); void drawInstanced(uint32 nbInstances, Primitive primitiveType, uint32 nbVertices, uint32 startVertex = 0, uint32 startInstance = 0); void drawIndexedInstanced(uint32 nbInstances, Primitive primitiveType, uint32 nbIndices, uint32 startIndex = 0, uint32 startInstance = 0); + // Input Stage + // InputFormat + // InputBuffers + // IndexBuffer void setInputFormat(const Stream::FormatPointer& format); void setInputStream(Slot startChannel, const BufferStream& stream); // not a command, just unroll into a loop of setInputBuffer @@ -72,6 +81,16 @@ public: void setIndexBuffer(Type type, const BufferPointer& buffer, Offset offset); + // Transform Stage + // Vertex position is transformed by ModelTransform from object space to world space + // Then by the inverse of the ViewTransform from world space to eye space + // finaly projected into the clip space by the projection transform + // WARNING: ViewTransform transform from eye space to world space, its inverse is composed + // with the ModelTransformu to create the equivalent of the glModelViewMatrix + void setModelTransform(const TransformPointer& model); + void setViewTransform(const TransformPointer& view); + void setProjectionTransform(const TransformPointer& proj); + // TODO: As long as we have gl calls explicitely issued from interface // code, we need to be able to record and batch these calls. THe long @@ -138,11 +157,13 @@ public: COMMAND_drawIndexedInstanced, COMMAND_setInputFormat, - COMMAND_setInputBuffer, - COMMAND_setIndexBuffer, + COMMAND_setModelTransform, + COMMAND_setViewTransform, + COMMAND_setProjectionTransform, + // TODO: As long as we have gl calls explicitely issued from interface // code, we need to be able to record and batch these calls. THe long // term strategy is to get rid of any GL calls in favor of the HIFI GPU API @@ -266,6 +287,7 @@ public: typedef Cache::Vector BufferCaches; typedef Cache::Vector StreamFormatCaches; + typedef Cache::Vector TransformCaches; typedef unsigned char Byte; typedef std::vector Bytes; @@ -299,11 +321,12 @@ public: CommandOffsets _commandOffsets; Params _params; Resources _resources; + Bytes _data; BufferCaches _buffers; StreamFormatCaches _streamFormats; + TransformCaches _transforms; - Bytes _data; protected: }; diff --git a/interface/src/gpu/GLBackend.cpp b/interface/src/gpu/GLBackend.cpp index 5c2451d81f..8921dc6d1c 100644 --- a/interface/src/gpu/GLBackend.cpp +++ b/interface/src/gpu/GLBackend.cpp @@ -24,11 +24,13 @@ GLBackend::CommandCall GLBackend::_commandCalls[Batch::NUM_COMMANDS] = (&::gpu::GLBackend::do_drawIndexedInstanced), (&::gpu::GLBackend::do_setInputFormat), - (&::gpu::GLBackend::do_setInputBuffer), - (&::gpu::GLBackend::do_setIndexBuffer), + (&::gpu::GLBackend::do_setModelTransform), + (&::gpu::GLBackend::do_setViewTransform), + (&::gpu::GLBackend::do_setProjectionTransform), + (&::gpu::GLBackend::do_glEnable), (&::gpu::GLBackend::do_glDisable), @@ -111,18 +113,16 @@ static const GLenum _elementTypeToGLType[NUM_TYPES]= { GLBackend::GLBackend() : - _needInputFormatUpdate(true), _inputFormat(0), - _inputBuffersState(0), _inputBuffers(_inputBuffersState.size(), BufferPointer(0)), _inputBufferOffsets(_inputBuffersState.size(), 0), _inputBufferStrides(_inputBuffersState.size(), 0), - _indexBuffer(0), _indexBufferOffset(0), - _inputAttributeActivation(0) + _inputAttributeActivation(0), + _transform() { } @@ -183,6 +183,7 @@ void GLBackend::checkGLError() { void GLBackend::do_draw(Batch& batch, uint32 paramOffset) { updateInput(); + updateTransform(); Primitive primitiveType = (Primitive)batch._params[paramOffset + 2]._uint; GLenum mode = _primitiveToGLmode[primitiveType]; @@ -195,6 +196,7 @@ void GLBackend::do_draw(Batch& batch, uint32 paramOffset) { void GLBackend::do_drawIndexed(Batch& batch, uint32 paramOffset) { updateInput(); + updateTransform(); Primitive primitiveType = (Primitive)batch._params[paramOffset + 2]._uint; GLenum mode = _primitiveToGLmode[primitiveType]; @@ -425,6 +427,82 @@ void GLBackend::do_setIndexBuffer(Batch& batch, uint32 paramOffset) { CHECK_GL_ERROR(); } +// Transform Stage + +void GLBackend::do_setModelTransform(Batch& batch, uint32 paramOffset) { + TransformPointer modelTransform = batch._transforms.get(batch._params[paramOffset]._uint); + + if (_transform._model.isNull() || (modelTransform != _transform._model)) { + _transform._model = modelTransform; + _transform._invalidModel = true; + } +} + +void GLBackend::do_setViewTransform(Batch& batch, uint32 paramOffset) { + TransformPointer viewTransform = batch._transforms.get(batch._params[paramOffset]._uint); + + if (_transform._view.isNull() || (viewTransform != _transform._view)) { + _transform._view = viewTransform; + _transform._invalidView = true; + } +} + +void GLBackend::do_setProjectionTransform(Batch& batch, uint32 paramOffset) { + TransformPointer projectionTransform = batch._transforms.get(batch._params[paramOffset]._uint); + + if (_transform._projection.isNull() || (projectionTransform != _transform._projection)) { + _transform._projection = projectionTransform; + _transform._invalidProj = true; + } +} + +void GLBackend::updateTransform() { + if (_transform._invalidProj) { + // TODO: implement the projection matrix assignment to gl state + /* if (_transform._lastMode != GL_PROJECTION) { + glMatrixMode(GL_PROJECTION); + _transform._lastMode = GL_PROJECTION; + } + CHECK_GL_ERROR();*/ + _transform._invalidProj; + } + + if (_transform._invalidModel || _transform._invalidView) { + if (!_transform._model.isNull()) { + if (_transform._lastMode != GL_MODELVIEW) { + glMatrixMode(GL_MODELVIEW); + _transform._lastMode = GL_MODELVIEW; + } + Transform::Mat4 modelView; + if (!_transform._view.isNull()) { + Transform mvx; + Transform::inverseMult(mvx, (*_transform._view), (*_transform._model)); + mvx.getMatrix(modelView); + } else { + _transform._model->getMatrix(modelView); + } + glLoadMatrixf(reinterpret_cast< const GLfloat* >(&modelView)); + } else { + if (!_transform._view.isNull()) { + if (_transform._lastMode != GL_MODELVIEW) { + glMatrixMode(GL_MODELVIEW); + _transform._lastMode = GL_MODELVIEW; + } + Transform::Mat4 modelView; + _transform._view->getInverseMatrix(modelView); + glLoadMatrixf(reinterpret_cast< const GLfloat* >(&modelView)); + } else { + // TODO: eventually do something about the matrix when neither view nor model is specified? + // glLoadIdentity(); + } + } + CHECK_GL_ERROR(); + + _transform._invalidModel = false; + _transform._invalidView = false; + } +} + // TODO: As long as we have gl calls explicitely issued from interface // code, we need to be able to record and batch these calls. THe long // term strategy is to get rid of any GL calls in favor of the HIFI GPU API diff --git a/interface/src/gpu/GLBackend.h b/interface/src/gpu/GLBackend.h index 2cce6cbedc..0f58ec192d 100644 --- a/interface/src/gpu/GLBackend.h +++ b/interface/src/gpu/GLBackend.h @@ -52,11 +52,22 @@ public: protected: + // Draw Stage + void do_draw(Batch& batch, uint32 paramOffset); + void do_drawIndexed(Batch& batch, uint32 paramOffset); + void do_drawInstanced(Batch& batch, uint32 paramOffset); + void do_drawIndexedInstanced(Batch& batch, uint32 paramOffset); + + // Input Stage + void do_setInputFormat(Batch& batch, uint32 paramOffset); + void do_setInputBuffer(Batch& batch, uint32 paramOffset); + void do_setIndexBuffer(Batch& batch, uint32 paramOffset); + void updateInput(); bool _needInputFormatUpdate; Stream::FormatPointer _inputFormat; - typedef std::bitset InputBuffersState; InputBuffersState _inputBuffersState; + Buffers _inputBuffers; Offsets _inputBufferOffsets; Offsets _inputBufferStrides; @@ -68,18 +79,31 @@ protected: typedef std::bitset InputActivationCache; InputActivationCache _inputAttributeActivation; - void do_draw(Batch& batch, uint32 paramOffset); - void do_drawIndexed(Batch& batch, uint32 paramOffset); - void do_drawInstanced(Batch& batch, uint32 paramOffset); - void do_drawIndexedInstanced(Batch& batch, uint32 paramOffset); + // Transform Stage + void do_setModelTransform(Batch& batch, uint32 paramOffset); + void do_setViewTransform(Batch& batch, uint32 paramOffset); + void do_setProjectionTransform(Batch& batch, uint32 paramOffset); - void updateInput(); - void do_setInputFormat(Batch& batch, uint32 paramOffset); - - void do_setInputBuffer(Batch& batch, uint32 paramOffset); + void updateTransform(); + struct TransformStageState { + TransformPointer _model; + TransformPointer _view; + TransformPointer _projection; + bool _invalidModel; + bool _invalidView; + bool _invalidProj; - void do_setVertexBuffer(Batch& batch, uint32 paramOffset); - void do_setIndexBuffer(Batch& batch, uint32 paramOffset); + GLenum _lastMode; + + TransformStageState() : + _model(0), + _view(0), + _projection(0), + _invalidModel(true), + _invalidView(true), + _invalidProj(true), + _lastMode(GL_TEXTURE) {} + } _transform; // TODO: As long as we have gl calls explicitely issued from interface // code, we need to be able to record and batch these calls. THe long diff --git a/interface/src/renderer/AnimationHandle.cpp b/interface/src/renderer/AnimationHandle.cpp new file mode 100644 index 0000000000..767f941049 --- /dev/null +++ b/interface/src/renderer/AnimationHandle.cpp @@ -0,0 +1,177 @@ +// +// AnimationHandle.cpp +// interface/src/renderer +// +// Created by Andrzej Kapolka on 10/18/13. +// Copyright 2013 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 "AnimationHandle.h" +#include "Application.h" + +void AnimationHandle::setURL(const QUrl& url) { + if (_url != url) { + _animation = Application::getInstance()->getAnimationCache()->getAnimation(_url = url); + _jointMappings.clear(); + } +} + +static void insertSorted(QList& handles, const AnimationHandlePointer& handle) { + for (QList::iterator it = handles.begin(); it != handles.end(); it++) { + if (handle->getPriority() > (*it)->getPriority()) { + handles.insert(it, handle); + return; + } + } + handles.append(handle); +} + +void AnimationHandle::setPriority(float priority) { + if (_priority == priority) { + return; + } + if (isRunning()) { + _model->_runningAnimations.removeOne(_self); + if (priority < _priority) { + replaceMatchingPriorities(priority); + } + _priority = priority; + insertSorted(_model->_runningAnimations, _self); + + } else { + _priority = priority; + } +} + +void AnimationHandle::setStartAutomatically(bool startAutomatically) { + _animationLoop.setStartAutomatically(startAutomatically); + if (getStartAutomatically() && !isRunning()) { + start(); + } +} + +void AnimationHandle::setMaskedJoints(const QStringList& maskedJoints) { + _maskedJoints = maskedJoints; + _jointMappings.clear(); +} + +void AnimationHandle::setRunning(bool running) { + if (isRunning() == running) { + // if we're already running, this is the same as a restart + if (running) { + // move back to the beginning + setFrameIndex(getFirstFrame()); + } + return; + } + _animationLoop.setRunning(running); + if (isRunning()) { + if (!_model->_runningAnimations.contains(_self)) { + insertSorted(_model->_runningAnimations, _self); + } + } else { + _model->_runningAnimations.removeOne(_self); + replaceMatchingPriorities(0.0f); + } + emit runningChanged(isRunning()); +} + +AnimationHandle::AnimationHandle(Model* model) : + QObject(model), + _model(model), + _priority(1.0f) +{ +} + +AnimationDetails AnimationHandle::getAnimationDetails() const { + AnimationDetails details(_role, _url, getFPS(), _priority, getLoop(), getHold(), + getStartAutomatically(), getFirstFrame(), getLastFrame(), isRunning(), getFrameIndex()); + return details; +} + +void AnimationHandle::setAnimationDetails(const AnimationDetails& details) { + setRole(details.role); + setURL(details.url); + setFPS(details.fps); + setPriority(details.priority); + setLoop(details.loop); + setHold(details.hold); + setStartAutomatically(details.startAutomatically); + setFirstFrame(details.firstFrame); + setLastFrame(details.lastFrame); + setRunning(details.running); + setFrameIndex(details.frameIndex); + + // NOTE: AnimationDetails doesn't support maskedJoints + //setMaskedJoints(const QStringList& maskedJoints); +} + + +void AnimationHandle::simulate(float deltaTime) { + _animationLoop.simulate(deltaTime); + + // update the joint mappings if necessary/possible + if (_jointMappings.isEmpty()) { + if (_model->isActive()) { + _jointMappings = _model->getGeometry()->getJointMappings(_animation); + } + if (_jointMappings.isEmpty()) { + return; + } + if (!_maskedJoints.isEmpty()) { + const FBXGeometry& geometry = _model->getGeometry()->getFBXGeometry(); + for (int i = 0; i < _jointMappings.size(); i++) { + int& mapping = _jointMappings[i]; + if (mapping != -1 && _maskedJoints.contains(geometry.joints.at(mapping).name)) { + mapping = -1; + } + } + } + } + + const FBXGeometry& animationGeometry = _animation->getGeometry(); + if (animationGeometry.animationFrames.isEmpty()) { + stop(); + return; + } + + // TODO: When moving the loop/frame calculations to AnimationLoop class, we changed this behavior + // see AnimationLoop class for more details. Do we need to support clamping the endFrameIndex to + // the max number of frames in the geometry??? + // + // float endFrameIndex = qMin(_lastFrame, animationGeometry.animationFrames.size() - (_loop ? 0.0f : 1.0f)); + + // blend between the closest two frames + applyFrame(getFrameIndex()); +} + +void AnimationHandle::applyFrame(float frameIndex) { + const FBXGeometry& animationGeometry = _animation->getGeometry(); + int frameCount = animationGeometry.animationFrames.size(); + const FBXAnimationFrame& floorFrame = animationGeometry.animationFrames.at((int)glm::floor(frameIndex) % frameCount); + const FBXAnimationFrame& ceilFrame = animationGeometry.animationFrames.at((int)glm::ceil(frameIndex) % frameCount); + float frameFraction = glm::fract(frameIndex); + for (int i = 0; i < _jointMappings.size(); i++) { + int mapping = _jointMappings.at(i); + if (mapping != -1) { + JointState& state = _model->_jointStates[mapping]; + state.setRotationInConstrainedFrame(safeMix(floorFrame.rotations.at(i), ceilFrame.rotations.at(i), frameFraction), _priority); + } + } +} + +void AnimationHandle::replaceMatchingPriorities(float newPriority) { + for (int i = 0; i < _jointMappings.size(); i++) { + int mapping = _jointMappings.at(i); + if (mapping != -1) { + JointState& state = _model->_jointStates[mapping]; + if (_priority == state._animationPriority) { + state._animationPriority = newPriority; + } + } + } +} + diff --git a/interface/src/renderer/AnimationHandle.h b/interface/src/renderer/AnimationHandle.h new file mode 100644 index 0000000000..3956b01ebf --- /dev/null +++ b/interface/src/renderer/AnimationHandle.h @@ -0,0 +1,110 @@ +// +// AnimationHandle.h +// interface/src/renderer +// +// Created by Andrzej Kapolka on 10/18/13. +// Copyright 2013 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_AnimationHandle_h +#define hifi_AnimationHandle_h + +#include +#include +#include +#include +#include + +#include +#include + +class AnimationHandle; +class Model; + +typedef QSharedPointer AnimationHandlePointer; +typedef QWeakPointer WeakAnimationHandlePointer; + + +/// Represents a handle to a model animation. +class AnimationHandle : public QObject { + Q_OBJECT + +public: + + void setRole(const QString& role) { _role = role; } + const QString& getRole() const { return _role; } + + void setURL(const QUrl& url); + const QUrl& getURL() const { return _url; } + + void setPriority(float priority); + float getPriority() const { return _priority; } + + void setMaskedJoints(const QStringList& maskedJoints); + const QStringList& getMaskedJoints() const { return _maskedJoints; } + + + void setFPS(float fps) { _animationLoop.setFPS(fps); } + float getFPS() const { return _animationLoop.getFPS(); } + + void setLoop(bool loop) { _animationLoop.setLoop(loop); } + bool getLoop() const { return _animationLoop.getLoop(); } + + void setHold(bool hold) { _animationLoop.setHold(hold); } + bool getHold() const { return _animationLoop.getHold(); } + + void setStartAutomatically(bool startAutomatically); + bool getStartAutomatically() const { return _animationLoop.getStartAutomatically(); } + + void setFirstFrame(float firstFrame) { _animationLoop.setFirstFrame(firstFrame); } + float getFirstFrame() const { return _animationLoop.getFirstFrame(); } + + void setLastFrame(float lastFrame) { _animationLoop.setLastFrame(lastFrame); } + float getLastFrame() const { return _animationLoop.getLastFrame(); } + + void setRunning(bool running); + bool isRunning() const { return _animationLoop.isRunning(); } + + void setFrameIndex(float frameIndex) { _animationLoop.setFrameIndex(frameIndex); } + float getFrameIndex() const { return _animationLoop.getFrameIndex(); } + + AnimationDetails getAnimationDetails() const; + void setAnimationDetails(const AnimationDetails& details); + +signals: + + void runningChanged(bool running); + +public slots: + + void start() { setRunning(true); } + void stop() { setRunning(false); } + +private: + + friend class Model; + + AnimationHandle(Model* model); + + void simulate(float deltaTime); + void applyFrame(float frameIndex); + void replaceMatchingPriorities(float newPriority); + + Model* _model; + WeakAnimationHandlePointer _self; + AnimationPointer _animation; + QString _role; + QUrl _url; + float _priority; + + QStringList _maskedJoints; + QVector _jointMappings; + + AnimationLoop _animationLoop; +}; + + +#endif // hifi_AnimationHandle_h diff --git a/interface/src/renderer/Model.cpp b/interface/src/renderer/Model.cpp index 8d426d067b..8b3c656ae7 100644 --- a/interface/src/renderer/Model.cpp +++ b/interface/src/renderer/Model.cpp @@ -23,6 +23,7 @@ #include #include +#include "AnimationHandle.h" #include "Application.h" #include "Model.h" @@ -517,17 +518,7 @@ void Model::recalcuateMeshBoxes() { } } -bool Model::render(float alpha, RenderMode mode, RenderArgs* args) { - PROFILE_RANGE(__FUNCTION__); - - // render the attachments - foreach (Model* attachment, _attachments) { - attachment->render(alpha, mode); - } - if (_meshStates.isEmpty()) { - return false; - } - +void Model::renderSetup(RenderArgs* args) { // if we don't have valid mesh boxes, calculate them now, this only matters in cases // where our caller has passed RenderArgs which will include a view frustum we can cull // against. We cache the results of these calculations so long as the model hasn't been @@ -549,10 +540,40 @@ bool Model::render(float alpha, RenderMode mode, RenderArgs* args) { if (!_meshGroupsKnown) { segregateMeshGroups(); } +} + +bool Model::render(float alpha, RenderMode mode, RenderArgs* args) { + PROFILE_RANGE(__FUNCTION__); + + // render the attachments + foreach (Model* attachment, _attachments) { + attachment->render(alpha, mode); + } + if (_meshStates.isEmpty()) { + return false; + } + + renderSetup(args); + return renderCore(alpha, mode, args); +} + +bool Model::renderCore(float alpha, RenderMode mode, RenderArgs* args) { + PROFILE_RANGE(__FUNCTION__); // Let's introduce a gpu::Batch to capture all the calls to the graphics api - gpu::Batch batch; + _renderBatch.clear(); + gpu::Batch& batch = _renderBatch; + GLBATCH(glPushMatrix)(); + // Capture the view matrix once for the rendering of this model + if (_transforms.empty()) { + _transforms.push_back(gpu::TransformPointer(new gpu::Transform())); + } + (*_transforms[0]) = gpu::Transform((*Application::getInstance()->getViewTransform())); + // apply entity translation offset to the viewTransform in one go (it's a preTranslate because viewTransform goes from world to eye space) + _transforms[0]->preTranslate(-_translation); + + batch.setViewTransform(_transforms[0]); GLBATCH(glDisable)(GL_COLOR_MATERIAL); @@ -677,11 +698,12 @@ bool Model::render(float alpha, RenderMode mode, RenderArgs* args) { GLBATCH(glBindBuffer)(GL_ELEMENT_ARRAY_BUFFER, 0); GLBATCH(glBindTexture)(GL_TEXTURE_2D, 0); + GLBATCH(glPopMatrix)(); + // Render! { PROFILE_RANGE("render Batch"); ::gpu::GLBackend::renderBatch(batch); - batch.clear(); } // restore all the default material settings @@ -1459,6 +1481,279 @@ void Model::deleteGeometry() { _blendedBlendshapeCoefficients.clear(); } +// Scene rendering support +QVector Model::_modelsInScene; +gpu::Batch Model::_sceneRenderBatch; +void Model::startScene() { + _modelsInScene.clear(); +} + +void Model::setupBatchTransform(gpu::Batch& batch) { + GLBATCH(glPushMatrix)(); + + // Capture the view matrix once for the rendering of this model + if (_transforms.empty()) { + _transforms.push_back(gpu::TransformPointer(new gpu::Transform())); + } + (*_transforms[0]) = gpu::Transform((*Application::getInstance()->getViewTransform())); + _transforms[0]->preTranslate(-_translation); + batch.setViewTransform(_transforms[0]); +} + +void Model::endScene(RenderMode mode, RenderArgs* args) { + PROFILE_RANGE(__FUNCTION__); + + // Let's introduce a gpu::Batch to capture all the calls to the graphics api + _sceneRenderBatch.clear(); + gpu::Batch& batch = _sceneRenderBatch; + + GLBATCH(glDisable)(GL_COLOR_MATERIAL); + + if (mode == DIFFUSE_RENDER_MODE || mode == NORMAL_RENDER_MODE) { + GLBATCH(glDisable)(GL_CULL_FACE); + } else { + GLBATCH(glEnable)(GL_CULL_FACE); + if (mode == SHADOW_RENDER_MODE) { + GLBATCH(glCullFace)(GL_FRONT); + } + } + + // render opaque meshes with alpha testing + + GLBATCH(glDisable)(GL_BLEND); + GLBATCH(glEnable)(GL_ALPHA_TEST); + + if (mode == SHADOW_RENDER_MODE) { + GLBATCH(glAlphaFunc)(GL_EQUAL, 0.0f); + } + + + /*Application::getInstance()->getTextureCache()->setPrimaryDrawBuffers( + mode == DEFAULT_RENDER_MODE || mode == DIFFUSE_RENDER_MODE, + mode == DEFAULT_RENDER_MODE || mode == NORMAL_RENDER_MODE, + mode == DEFAULT_RENDER_MODE); + */ + { + GLenum buffers[3]; + int bufferCount = 0; + if (mode == DEFAULT_RENDER_MODE || mode == DIFFUSE_RENDER_MODE) { + buffers[bufferCount++] = GL_COLOR_ATTACHMENT0; + } + if (mode == DEFAULT_RENDER_MODE || mode == NORMAL_RENDER_MODE) { + buffers[bufferCount++] = GL_COLOR_ATTACHMENT1; + } + if (mode == DEFAULT_RENDER_MODE) { + buffers[bufferCount++] = GL_COLOR_ATTACHMENT2; + } + GLBATCH(glDrawBuffers)(bufferCount, buffers); + } + + const float DEFAULT_ALPHA_THRESHOLD = 0.5f; + + int opaqueMeshPartsRendered = 0; + + // now, for each model in the scene, render the mesh portions + foreach(Model* model, _modelsInScene) { + model->setupBatchTransform(batch); + opaqueMeshPartsRendered += model->renderMeshes(batch, mode, false, DEFAULT_ALPHA_THRESHOLD, false, false, false, args); + GLBATCH(glPopMatrix)(); + } + foreach(Model* model, _modelsInScene) { + model->setupBatchTransform(batch); + opaqueMeshPartsRendered += model->renderMeshes(batch, mode, false, DEFAULT_ALPHA_THRESHOLD, false, false, true, args); + GLBATCH(glPopMatrix)(); + } + foreach(Model* model, _modelsInScene) { + model->setupBatchTransform(batch); + opaqueMeshPartsRendered += model->renderMeshes(batch, mode, false, DEFAULT_ALPHA_THRESHOLD, false, true, false, args); + GLBATCH(glPopMatrix)(); + } + foreach(Model* model, _modelsInScene) { + model->setupBatchTransform(batch); + opaqueMeshPartsRendered += model->renderMeshes(batch, mode, false, DEFAULT_ALPHA_THRESHOLD, false, true, true, args); + GLBATCH(glPopMatrix)(); + } + foreach(Model* model, _modelsInScene) { + model->setupBatchTransform(batch); + opaqueMeshPartsRendered += model->renderMeshes(batch, mode, false, DEFAULT_ALPHA_THRESHOLD, true, false, false, args); + GLBATCH(glPopMatrix)(); + } + foreach(Model* model, _modelsInScene) { + model->setupBatchTransform(batch); + opaqueMeshPartsRendered += model->renderMeshes(batch, mode, false, DEFAULT_ALPHA_THRESHOLD, true, false, true, args); + GLBATCH(glPopMatrix)(); + } + foreach(Model* model, _modelsInScene) { + model->setupBatchTransform(batch); + opaqueMeshPartsRendered += model->renderMeshes(batch, mode, false, DEFAULT_ALPHA_THRESHOLD, true, true, false, args); + GLBATCH(glPopMatrix)(); + } + foreach(Model* model, _modelsInScene) { + model->setupBatchTransform(batch); + opaqueMeshPartsRendered += model->renderMeshes(batch, mode, false, DEFAULT_ALPHA_THRESHOLD, true, false, true, args); + GLBATCH(glPopMatrix)(); + } + + // render translucent meshes afterwards + //Application::getInstance()->getTextureCache()->setPrimaryDrawBuffers(false, true, true); + { + GLenum buffers[2]; + int bufferCount = 0; + buffers[bufferCount++] = GL_COLOR_ATTACHMENT1; + buffers[bufferCount++] = GL_COLOR_ATTACHMENT2; + GLBATCH(glDrawBuffers)(bufferCount, buffers); + } + + int translucentParts = 0; + const float MOSTLY_OPAQUE_THRESHOLD = 0.75f; + foreach(Model* model, _modelsInScene) { + model->setupBatchTransform(batch); + translucentParts += model->renderMeshes(batch, mode, true, MOSTLY_OPAQUE_THRESHOLD, false, false, false, args); + GLBATCH(glPopMatrix)(); + } + foreach(Model* model, _modelsInScene) { + model->setupBatchTransform(batch); + translucentParts += model->renderMeshes(batch, mode, true, MOSTLY_OPAQUE_THRESHOLD, false, false, true, args); + GLBATCH(glPopMatrix)(); + } + foreach(Model* model, _modelsInScene) { + model->setupBatchTransform(batch); + translucentParts += model->renderMeshes(batch, mode, true, MOSTLY_OPAQUE_THRESHOLD, false, true, false, args); + GLBATCH(glPopMatrix)(); + } + foreach(Model* model, _modelsInScene) { + model->setupBatchTransform(batch); + translucentParts += model->renderMeshes(batch, mode, true, MOSTLY_OPAQUE_THRESHOLD, false, true, true, args); + GLBATCH(glPopMatrix)(); + } + foreach(Model* model, _modelsInScene) { + model->setupBatchTransform(batch); + translucentParts += model->renderMeshes(batch, mode, true, MOSTLY_OPAQUE_THRESHOLD, true, false, false, args); + GLBATCH(glPopMatrix)(); + } + foreach(Model* model, _modelsInScene) { + model->setupBatchTransform(batch); + translucentParts += model->renderMeshes(batch, mode, true, MOSTLY_OPAQUE_THRESHOLD, true, false, true, args); + GLBATCH(glPopMatrix)(); + } + foreach(Model* model, _modelsInScene) { + model->setupBatchTransform(batch); + translucentParts += model->renderMeshes(batch, mode, true, MOSTLY_OPAQUE_THRESHOLD, true, true, false, args); + GLBATCH(glPopMatrix)(); + } + foreach(Model* model, _modelsInScene) { + model->setupBatchTransform(batch); + translucentParts += model->renderMeshes(batch, mode, true, MOSTLY_OPAQUE_THRESHOLD, true, false, true, args); + GLBATCH(glPopMatrix)(); + } + + GLBATCH(glDisable)(GL_ALPHA_TEST); + GLBATCH(glEnable)(GL_BLEND); + GLBATCH(glDepthMask)(false); + GLBATCH(glDepthFunc)(GL_LEQUAL); + + //Application::getInstance()->getTextureCache()->setPrimaryDrawBuffers(true); + { + GLenum buffers[1]; + int bufferCount = 0; + buffers[bufferCount++] = GL_COLOR_ATTACHMENT0; + GLBATCH(glDrawBuffers)(bufferCount, buffers); + } + + if (mode == DEFAULT_RENDER_MODE || mode == DIFFUSE_RENDER_MODE) { + const float MOSTLY_TRANSPARENT_THRESHOLD = 0.0f; + foreach(Model* model, _modelsInScene) { + model->setupBatchTransform(batch); + translucentParts += model->renderMeshes(batch, mode, true, MOSTLY_TRANSPARENT_THRESHOLD, false, false, false, args); + GLBATCH(glPopMatrix)(); + } + foreach(Model* model, _modelsInScene) { + model->setupBatchTransform(batch); + translucentParts += model->renderMeshes(batch, mode, true, MOSTLY_TRANSPARENT_THRESHOLD, false, false, true, args); + GLBATCH(glPopMatrix)(); + } + foreach(Model* model, _modelsInScene) { + model->setupBatchTransform(batch); + translucentParts += model->renderMeshes(batch, mode, true, MOSTLY_TRANSPARENT_THRESHOLD, false, true, false, args); + GLBATCH(glPopMatrix)(); + } + foreach(Model* model, _modelsInScene) { + model->setupBatchTransform(batch); + translucentParts += model->renderMeshes(batch, mode, true, MOSTLY_TRANSPARENT_THRESHOLD, false, true, true, args); + GLBATCH(glPopMatrix)(); + } + foreach(Model* model, _modelsInScene) { + model->setupBatchTransform(batch); + translucentParts += model->renderMeshes(batch, mode, true, MOSTLY_TRANSPARENT_THRESHOLD, true, false, false, args); + GLBATCH(glPopMatrix)(); + } + foreach(Model* model, _modelsInScene) { + model->setupBatchTransform(batch); + translucentParts += model->renderMeshes(batch, mode, true, MOSTLY_TRANSPARENT_THRESHOLD, true, false, true, args); + GLBATCH(glPopMatrix)(); + } + foreach(Model* model, _modelsInScene) { + model->setupBatchTransform(batch); + translucentParts += model->renderMeshes(batch, mode, true, MOSTLY_TRANSPARENT_THRESHOLD, true, true, false, args); + GLBATCH(glPopMatrix)(); + } + foreach(Model* model, _modelsInScene) { + model->setupBatchTransform(batch); + translucentParts += model->renderMeshes(batch, mode, true, MOSTLY_TRANSPARENT_THRESHOLD, true, false, true, args); + GLBATCH(glPopMatrix)(); + } + } + + GLBATCH(glDepthMask)(true); + GLBATCH(glDepthFunc)(GL_LESS); + GLBATCH(glDisable)(GL_CULL_FACE); + + if (mode == SHADOW_RENDER_MODE) { + GLBATCH(glCullFace)(GL_BACK); + } + + // deactivate vertex arrays after drawing + GLBATCH(glDisableClientState)(GL_NORMAL_ARRAY); + GLBATCH(glDisableClientState)(GL_VERTEX_ARRAY); + GLBATCH(glDisableClientState)(GL_TEXTURE_COORD_ARRAY); + GLBATCH(glDisableClientState)(GL_COLOR_ARRAY); + GLBATCH(glDisableVertexAttribArray)(gpu::Stream::TANGENT); + GLBATCH(glDisableVertexAttribArray)(gpu::Stream::SKIN_CLUSTER_INDEX); + GLBATCH(glDisableVertexAttribArray)(gpu::Stream::SKIN_CLUSTER_WEIGHT); + + // bind with 0 to switch back to normal operation + GLBATCH(glBindBuffer)(GL_ARRAY_BUFFER, 0); + GLBATCH(glBindBuffer)(GL_ELEMENT_ARRAY_BUFFER, 0); + GLBATCH(glBindTexture)(GL_TEXTURE_2D, 0); + + // Render! + { + PROFILE_RANGE("render Batch"); + ::gpu::GLBackend::renderBatch(batch); + } + + // restore all the default material settings + Application::getInstance()->setupWorldLight(); + + if (args) { + args->_translucentMeshPartsRendered = translucentParts; + args->_opaqueMeshPartsRendered = opaqueMeshPartsRendered; + } +} + +bool Model::renderInScene(float alpha, RenderArgs* args) { + // render the attachments + foreach (Model* attachment, _attachments) { + attachment->renderInScene(alpha); + } + if (_meshStates.isEmpty()) { + return false; + } + renderSetup(args); + _modelsInScene.push_back(this); + return true; +} + void Model::segregateMeshGroups() { _meshesTranslucentTangents.clear(); _meshesTranslucent.clear(); @@ -1849,20 +2144,17 @@ int Model::renderMeshes(gpu::Batch& batch, RenderMode mode, bool translucent, fl } GLBATCH(glPushMatrix)(); - //Application::getInstance()->loadTranslatedViewMatrix(_translation); - GLBATCH(glLoadMatrixf)((const GLfloat*)&Application::getInstance()->getUntranslatedViewMatrix()); - glm::vec3 viewMatTranslation = Application::getInstance()->getViewMatrixTranslation(); - GLBATCH(glTranslatef)(_translation.x + viewMatTranslation.x, _translation.y + viewMatTranslation.y, - _translation.z + viewMatTranslation.z); const MeshState& state = _meshStates.at(i); if (state.clusterMatrices.size() > 1) { GLBATCH(glUniformMatrix4fv)(skinLocations->clusterMatrices, state.clusterMatrices.size(), false, (const float*)state.clusterMatrices.constData()); + batch.setModelTransform(gpu::TransformPointer()); } else { - GLBATCH(glMultMatrixf)((const GLfloat*)&state.clusterMatrices[0]); + gpu::TransformPointer modelTransform(new gpu::Transform(state.clusterMatrices[0])); + batch.setModelTransform(modelTransform); } - + if (mesh.blendshapes.isEmpty()) { batch.setInputFormat(networkMesh._vertexFormat); batch.setInputStream(0, *networkMesh._vertexStream); @@ -1980,166 +2272,3 @@ int Model::renderMeshes(gpu::Batch& batch, RenderMode mode, bool translucent, fl return meshPartsRendered; } - -void AnimationHandle::setURL(const QUrl& url) { - if (_url != url) { - _animation = Application::getInstance()->getAnimationCache()->getAnimation(_url = url); - _jointMappings.clear(); - } -} - -static void insertSorted(QList& handles, const AnimationHandlePointer& handle) { - for (QList::iterator it = handles.begin(); it != handles.end(); it++) { - if (handle->getPriority() > (*it)->getPriority()) { - handles.insert(it, handle); - return; - } - } - handles.append(handle); -} - -void AnimationHandle::setPriority(float priority) { - if (_priority == priority) { - return; - } - if (_running) { - _model->_runningAnimations.removeOne(_self); - if (priority < _priority) { - replaceMatchingPriorities(priority); - } - _priority = priority; - insertSorted(_model->_runningAnimations, _self); - - } else { - _priority = priority; - } -} - -void AnimationHandle::setStartAutomatically(bool startAutomatically) { - if ((_startAutomatically = startAutomatically) && !_running) { - start(); - } -} - -void AnimationHandle::setMaskedJoints(const QStringList& maskedJoints) { - _maskedJoints = maskedJoints; - _jointMappings.clear(); -} - -void AnimationHandle::setRunning(bool running) { - if (_running == running) { - if (running) { - // move back to the beginning - _frameIndex = _firstFrame; - } - return; - } - if ((_running = running)) { - if (!_model->_runningAnimations.contains(_self)) { - insertSorted(_model->_runningAnimations, _self); - } - _frameIndex = _firstFrame; - - } else { - _model->_runningAnimations.removeOne(_self); - replaceMatchingPriorities(0.0f); - } - emit runningChanged(_running); -} - -AnimationHandle::AnimationHandle(Model* model) : - QObject(model), - _model(model), - _fps(30.0f), - _priority(1.0f), - _loop(false), - _hold(false), - _startAutomatically(false), - _firstFrame(0.0f), - _lastFrame(FLT_MAX), - _running(false) { -} - -AnimationDetails AnimationHandle::getAnimationDetails() const { - AnimationDetails details(_role, _url, _fps, _priority, _loop, _hold, - _startAutomatically, _firstFrame, _lastFrame, _running, _frameIndex); - return details; -} - - -void AnimationHandle::simulate(float deltaTime) { - _frameIndex += deltaTime * _fps; - - // update the joint mappings if necessary/possible - if (_jointMappings.isEmpty()) { - if (_model->isActive()) { - _jointMappings = _model->getGeometry()->getJointMappings(_animation); - } - if (_jointMappings.isEmpty()) { - return; - } - if (!_maskedJoints.isEmpty()) { - const FBXGeometry& geometry = _model->getGeometry()->getFBXGeometry(); - for (int i = 0; i < _jointMappings.size(); i++) { - int& mapping = _jointMappings[i]; - if (mapping != -1 && _maskedJoints.contains(geometry.joints.at(mapping).name)) { - mapping = -1; - } - } - } - } - - const FBXGeometry& animationGeometry = _animation->getGeometry(); - if (animationGeometry.animationFrames.isEmpty()) { - stop(); - return; - } - float endFrameIndex = qMin(_lastFrame, animationGeometry.animationFrames.size() - (_loop ? 0.0f : 1.0f)); - float startFrameIndex = qMin(_firstFrame, endFrameIndex); - if ((!_loop && (_frameIndex < startFrameIndex || _frameIndex > endFrameIndex)) || startFrameIndex == endFrameIndex) { - // passed the end; apply the last frame - applyFrame(glm::clamp(_frameIndex, startFrameIndex, endFrameIndex)); - if (!_hold) { - stop(); - } - return; - } - // wrap within the the desired range - if (_frameIndex < startFrameIndex) { - _frameIndex = endFrameIndex - glm::mod(endFrameIndex - _frameIndex, endFrameIndex - startFrameIndex); - - } else if (_frameIndex > endFrameIndex) { - _frameIndex = startFrameIndex + glm::mod(_frameIndex - startFrameIndex, endFrameIndex - startFrameIndex); - } - - // blend between the closest two frames - applyFrame(_frameIndex); -} - -void AnimationHandle::applyFrame(float frameIndex) { - const FBXGeometry& animationGeometry = _animation->getGeometry(); - int frameCount = animationGeometry.animationFrames.size(); - const FBXAnimationFrame& floorFrame = animationGeometry.animationFrames.at((int)glm::floor(frameIndex) % frameCount); - const FBXAnimationFrame& ceilFrame = animationGeometry.animationFrames.at((int)glm::ceil(frameIndex) % frameCount); - float frameFraction = glm::fract(frameIndex); - for (int i = 0; i < _jointMappings.size(); i++) { - int mapping = _jointMappings.at(i); - if (mapping != -1) { - JointState& state = _model->_jointStates[mapping]; - state.setRotationInConstrainedFrame(safeMix(floorFrame.rotations.at(i), ceilFrame.rotations.at(i), frameFraction), _priority); - } - } -} - -void AnimationHandle::replaceMatchingPriorities(float newPriority) { - for (int i = 0; i < _jointMappings.size(); i++) { - int mapping = _jointMappings.at(i); - if (mapping != -1) { - JointState& state = _model->_jointStates[mapping]; - if (_priority == state._animationPriority) { - state._animationPriority = newPriority; - } - } - } -} - diff --git a/interface/src/renderer/Model.h b/interface/src/renderer/Model.h index 34d6a34c06..8c74b1a222 100644 --- a/interface/src/renderer/Model.h +++ b/interface/src/renderer/Model.h @@ -16,10 +16,12 @@ #include #include +#include "Transform.h" #include #include #include +#include "AnimationHandle.h" #include "GeometryCache.h" #include "InterfaceConfig.h" #include "JointState.h" @@ -28,19 +30,13 @@ class QScriptEngine; -class AnimationHandle; class Shape; class RenderArgs; class ViewFrustum; -typedef QSharedPointer AnimationHandlePointer; -typedef QWeakPointer WeakAnimationHandlePointer; - -namespace gpu { - class Batch; -} #include "gpu/Stream.h" +#include "gpu/Batch.h" /// A generic 3D model displaying geometry loaded from a URL. class Model : public QObject, public PhysicsEntity { @@ -93,6 +89,11 @@ public: enum RenderMode { DEFAULT_RENDER_MODE, SHADOW_RENDER_MODE, DIFFUSE_RENDER_MODE, NORMAL_RENDER_MODE }; bool render(float alpha = 1.0f, RenderMode mode = DEFAULT_RENDER_MODE, RenderArgs* args = NULL); + + // Scene rendering support + static void startScene(); + bool renderInScene(float alpha = 1.0f, RenderArgs* args = NULL); + static void endScene(RenderMode mode = DEFAULT_RENDER_MODE, RenderArgs* args = NULL); /// Sets the URL of the model to render. /// \param fallback the URL of a fallback model to render if the requested model fails to load @@ -259,14 +260,13 @@ protected: /// Computes and returns the extended length of the limb terminating at the specified joint and starting at the joint's /// first free ancestor. float getLimbLength(int jointIndex) const; - + private: friend class AnimationHandle; void applyNextGeometry(); void deleteGeometry(); - int renderMeshes(gpu::Batch& batch, RenderMode mode, bool translucent, float alphaThreshold, bool hasTangents, bool hasSpecular, bool isSkinned, RenderArgs* args = NULL); QVector createJointStates(const FBXGeometry& geometry); void initJointTransforms(); @@ -283,7 +283,9 @@ private: QUrl _url; gpu::Buffers _blendedVertexBuffers; - + gpu::Transforms _transforms; + gpu::Batch _renderBatch; + QVector > > _dilatedTextures; QVector _attachments; @@ -394,91 +396,25 @@ private: QVector _meshesOpaqueTangentsSpecularSkinned; QVector _meshesOpaqueSpecularSkinned; + // Scene rendering support + static QVector _modelsInScene; + static gpu::Batch _sceneRenderBatch; + + static void endSceneSimple(RenderMode mode = DEFAULT_RENDER_MODE, RenderArgs* args = NULL); + static void endSceneSplitPass(RenderMode mode = DEFAULT_RENDER_MODE, RenderArgs* args = NULL); + + // helper functions used by render() or renderInScene() + void renderSetup(RenderArgs* args); + bool renderCore(float alpha, RenderMode mode, RenderArgs* args); + int renderMeshes(gpu::Batch& batch, RenderMode mode, bool translucent, float alphaThreshold, + bool hasTangents, bool hasSpecular, bool isSkinned, RenderArgs* args = NULL); + void setupBatchTransform(gpu::Batch& batch); + }; Q_DECLARE_METATYPE(QPointer) Q_DECLARE_METATYPE(QWeakPointer) Q_DECLARE_METATYPE(QVector) -/// Represents a handle to a model animation. -class AnimationHandle : public QObject { - Q_OBJECT - -public: - - void setRole(const QString& role) { _role = role; } - const QString& getRole() const { return _role; } - - void setURL(const QUrl& url); - const QUrl& getURL() const { return _url; } - - void setFPS(float fps) { _fps = fps; } - float getFPS() const { return _fps; } - - void setPriority(float priority); - float getPriority() const { return _priority; } - - void setLoop(bool loop) { _loop = loop; } - bool getLoop() const { return _loop; } - - void setHold(bool hold) { _hold = hold; } - bool getHold() const { return _hold; } - - void setStartAutomatically(bool startAutomatically); - bool getStartAutomatically() const { return _startAutomatically; } - - void setFirstFrame(float firstFrame) { _firstFrame = firstFrame; } - float getFirstFrame() const { return _firstFrame; } - - void setLastFrame(float lastFrame) { _lastFrame = lastFrame; } - float getLastFrame() const { return _lastFrame; } - - void setMaskedJoints(const QStringList& maskedJoints); - const QStringList& getMaskedJoints() const { return _maskedJoints; } - - void setRunning(bool running); - bool isRunning() const { return _running; } - - void setFrameIndex(float frameIndex) { _frameIndex = glm::clamp(_frameIndex, _firstFrame, _lastFrame); } - float getFrameIndex() const { return _frameIndex; } - - AnimationDetails getAnimationDetails() const; - -signals: - - void runningChanged(bool running); - -public slots: - - void start() { setRunning(true); } - void stop() { setRunning(false); } - -private: - - friend class Model; - - AnimationHandle(Model* model); - - void simulate(float deltaTime); - void applyFrame(float frameIndex); - void replaceMatchingPriorities(float newPriority); - - Model* _model; - WeakAnimationHandlePointer _self; - AnimationPointer _animation; - QString _role; - QUrl _url; - float _fps; - float _priority; - bool _loop; - bool _hold; - bool _startAutomatically; - float _firstFrame; - float _lastFrame; - QStringList _maskedJoints; - bool _running; - QVector _jointMappings; - float _frameIndex; -}; #endif // hifi_Model_h diff --git a/interface/src/ui/MetavoxelEditor.cpp b/interface/src/ui/MetavoxelEditor.cpp index e5a1f7f7a4..c0d8261427 100644 --- a/interface/src/ui/MetavoxelEditor.cpp +++ b/interface/src/ui/MetavoxelEditor.cpp @@ -919,7 +919,9 @@ void SetSpannerTool::applyEdit(const AttributePointer& attribute, const SharedOb Application::getInstance()->setupWorldLight(); Application::getInstance()->updateUntranslatedViewMatrix(); - + // TODO: assign an equivalent viewTransform object to the application to match the current path which uses glMatrixStack + // setViewTransform(viewTransform); + const glm::vec4 OPAQUE_WHITE(1.0f, 1.0f, 1.0f, 1.0f); spannerData->getRenderer()->render(OPAQUE_WHITE, SpannerRenderer::DIFFUSE_MODE, glm::vec3(), 0.0f); diff --git a/interface/src/ui/TextRenderer.cpp b/interface/src/ui/TextRenderer.cpp index fed041d6ea..65725ab4ec 100644 --- a/interface/src/ui/TextRenderer.cpp +++ b/interface/src/ui/TextRenderer.cpp @@ -69,14 +69,16 @@ int TextRenderer::calculateHeight(const char* str) { return maxHeight; } -int TextRenderer::draw(int x, int y, const char* str) { +int TextRenderer::draw(int x, int y, const char* str, float alpha) { // Grab the current color float currentColor[4]; glGetFloatv(GL_CURRENT_COLOR, currentColor); - int compactColor = ((int( currentColor[0] * 255.f) & 0xFF)) | - ((int( currentColor[1] * 255.f) & 0xFF) << 8) | - ((int( currentColor[2] * 255.f) & 0xFF) << 16) | - ((int( currentColor[3] * 255.f) & 0xFF) << 24); + alpha = std::max(0.f, std::min(alpha, 1.f)); + currentColor[3] *= alpha; + int compactColor = ((int(currentColor[0] * 255.f) & 0xFF)) | + ((int(currentColor[1] * 255.f) & 0xFF) << 8) | + ((int(currentColor[2] * 255.f) & 0xFF) << 16) | + ((int(currentColor[3] * 255.f) & 0xFF) << 24); // TODO: Remove that code once we test for performance improvments //glEnable(GL_TEXTURE_2D); diff --git a/interface/src/ui/TextRenderer.h b/interface/src/ui/TextRenderer.h index bcb0ce8890..325420cf2a 100644 --- a/interface/src/ui/TextRenderer.h +++ b/interface/src/ui/TextRenderer.h @@ -63,7 +63,7 @@ public: int calculateHeight(const char* str); // also returns the height of the tallest character - int draw(int x, int y, const char* str); + int draw(int x, int y, const char* str, float alpha = 1.f); int computeWidth(char ch); int computeWidth(const char* str); diff --git a/interface/src/ui/overlays/Text3DOverlay.cpp b/interface/src/ui/overlays/Text3DOverlay.cpp index d8febbf0eb..072b52768a 100644 --- a/interface/src/ui/overlays/Text3DOverlay.cpp +++ b/interface/src/ui/overlays/Text3DOverlay.cpp @@ -112,7 +112,7 @@ void Text3DOverlay::render(RenderArgs* args) { QStringList lines = _text.split("\n"); int lineOffset = maxHeight; foreach(QString thisLine, lines) { - textRenderer->draw(0, lineOffset, qPrintable(thisLine)); + textRenderer->draw(0, lineOffset, qPrintable(thisLine), alpha); lineOffset += maxHeight; } diff --git a/interface/src/ui/overlays/TextOverlay.cpp b/interface/src/ui/overlays/TextOverlay.cpp index 530b30a856..85ecca6d32 100644 --- a/interface/src/ui/overlays/TextOverlay.cpp +++ b/interface/src/ui/overlays/TextOverlay.cpp @@ -82,7 +82,7 @@ void TextOverlay::render(RenderArgs* args) { if (lineOffset == 0) { lineOffset = textRenderer->calculateHeight(qPrintable(thisLine)); } - lineOffset += textRenderer->draw(x, y + lineOffset, qPrintable(thisLine)); + lineOffset += textRenderer->draw(x, y + lineOffset, qPrintable(thisLine), alpha); const int lineGap = 2; lineOffset += lineGap; diff --git a/libraries/animation/src/AnimationCache.h b/libraries/animation/src/AnimationCache.h index 4af9f0a83f..8a9e371cb0 100644 --- a/libraries/animation/src/AnimationCache.h +++ b/libraries/animation/src/AnimationCache.h @@ -94,5 +94,4 @@ Q_DECLARE_METATYPE(AnimationDetails); QScriptValue animationDetailsToScriptValue(QScriptEngine* engine, const AnimationDetails& event); void animationDetailsFromScriptValue(const QScriptValue& object, AnimationDetails& event); - #endif // hifi_AnimationCache_h diff --git a/libraries/animation/src/AnimationLoop.cpp b/libraries/animation/src/AnimationLoop.cpp new file mode 100644 index 0000000000..f81904990f --- /dev/null +++ b/libraries/animation/src/AnimationLoop.cpp @@ -0,0 +1,95 @@ +// +// AnimationLoop.cpp +// libraries/animation +// +// Created by Brad Hefta-Gaub on 11/12/14. +// Copyright (c) 2014 High Fidelity, Inc. All rights reserved. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +#include "AnimationCache.h" +#include "AnimationLoop.h" + +AnimationLoop::AnimationLoop() : + _fps(30.0f), + _loop(false), + _hold(false), + _startAutomatically(false), + _firstFrame(0.0f), + _lastFrame(FLT_MAX), + _running(false), + _frameIndex(0.0f) +{ +} + +AnimationLoop::AnimationLoop(const AnimationDetails& animationDetails) : + _fps(animationDetails.fps), + _loop(animationDetails.loop), + _hold(animationDetails.hold), + _startAutomatically(animationDetails.startAutomatically), + _firstFrame(animationDetails.firstFrame), + _lastFrame(animationDetails.lastFrame), + _running(animationDetails.running), + _frameIndex(animationDetails.frameIndex) +{ +} + +AnimationLoop::AnimationLoop(float fps, bool loop, bool hold, bool startAutomatically, float firstFrame, + float lastFrame, bool running, float frameIndex) : + _fps(fps), + _loop(loop), + _hold(hold), + _startAutomatically(startAutomatically), + _firstFrame(firstFrame), + _lastFrame(lastFrame), + _running(running), + _frameIndex(frameIndex) +{ +} + +void AnimationLoop::simulate(float deltaTime) { + _frameIndex += deltaTime * _fps; + + + // If we knew the number of frames from the animation, we'd consider using it here + // animationGeometry.animationFrames.size() + float maxFrame = _lastFrame; + float endFrameIndex = qMin(_lastFrame, maxFrame - (_loop ? 0.0f : 1.0f)); + float startFrameIndex = qMin(_firstFrame, endFrameIndex); + if ((!_loop && (_frameIndex < startFrameIndex || _frameIndex > endFrameIndex)) || startFrameIndex == endFrameIndex) { + // passed the end; apply the last frame + _frameIndex = glm::clamp(_frameIndex, startFrameIndex, endFrameIndex); + if (!_hold) { + stop(); + } + } else { + // wrap within the the desired range + if (_frameIndex < startFrameIndex) { + _frameIndex = endFrameIndex - glm::mod(endFrameIndex - _frameIndex, endFrameIndex - startFrameIndex); + + } else if (_frameIndex > endFrameIndex) { + _frameIndex = startFrameIndex + glm::mod(_frameIndex - startFrameIndex, endFrameIndex - startFrameIndex); + } + } +} + +void AnimationLoop::setStartAutomatically(bool startAutomatically) { + if ((_startAutomatically = startAutomatically) && !isRunning()) { + start(); + } +} + +void AnimationLoop::setRunning(bool running) { + if (_running == running) { + if (running) { + // move back to the beginning + _frameIndex = _firstFrame; + } + return; + } + if ((_running = running)) { + _frameIndex = _firstFrame; + } +} diff --git a/libraries/animation/src/AnimationLoop.h b/libraries/animation/src/AnimationLoop.h new file mode 100644 index 0000000000..b56f68f23b --- /dev/null +++ b/libraries/animation/src/AnimationLoop.h @@ -0,0 +1,63 @@ +// +// AnimationLoop.h +// libraries/script-engine/src/ +// +// Created by Brad Hefta-Gaub on 11/12/14. +// Copyright (c) 2014 High Fidelity, Inc. All rights reserved. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +#ifndef hifi_AnimationLoop_h +#define hifi_AnimationLoop_h + +class AnimationDetails; + +class AnimationLoop { +public: + AnimationLoop(); + AnimationLoop(const AnimationDetails& animationDetails); + AnimationLoop(float fps, bool loop, bool hold, bool startAutomatically, float firstFrame, + float lastFrame, bool running, float frameIndex); + + void setFPS(float fps) { _fps = fps; } + float getFPS() const { return _fps; } + + void setLoop(bool loop) { _loop = loop; } + bool getLoop() const { return _loop; } + + void setHold(bool hold) { _hold = hold; } + bool getHold() const { return _hold; } + + void setStartAutomatically(bool startAutomatically); + bool getStartAutomatically() const { return _startAutomatically; } + + void setFirstFrame(float firstFrame) { _firstFrame = firstFrame; } + float getFirstFrame() const { return _firstFrame; } + + void setLastFrame(float lastFrame) { _lastFrame = lastFrame; } + float getLastFrame() const { return _lastFrame; } + + void setRunning(bool running); + bool isRunning() const { return _running; } + + void setFrameIndex(float frameIndex) { _frameIndex = glm::clamp(frameIndex, _firstFrame, _lastFrame); } + float getFrameIndex() const { return _frameIndex; } + + void start() { setRunning(true); } + void stop() { setRunning(false); } + void simulate(float deltaTime); + +private: + float _fps; + bool _loop; + bool _hold; + bool _startAutomatically; + float _firstFrame; + float _lastFrame; + bool _running; + float _frameIndex; +}; + +#endif // hifi_AnimationLoop_h diff --git a/libraries/audio/src/AbstractAudioInterface.h b/libraries/audio/src/AbstractAudioInterface.h index 1ee71ee32d..b5a7be9849 100644 --- a/libraries/audio/src/AbstractAudioInterface.h +++ b/libraries/audio/src/AbstractAudioInterface.h @@ -13,9 +13,13 @@ #define hifi_AbstractAudioInterface_h #include +#include #include "AudioInjectorOptions.h" +class AudioInjector; +class AudioInjectorLocalBuffer; + class AbstractAudioInterface : public QObject { Q_OBJECT public: @@ -24,7 +28,7 @@ public: virtual void startCollisionSound(float magnitude, float frequency, float noise, float duration, bool flashScreen) = 0; virtual void startDrumSound(float volume, float frequency, float duration, float decay) = 0; public slots: - virtual void handleAudioByteArray(const QByteArray& audioByteArray, const AudioInjectorOptions& options) = 0; + virtual bool outputLocalInjector(bool isStereo, qreal volume, AudioInjector* injector) = 0; }; Q_DECLARE_METATYPE(AbstractAudioInterface*) diff --git a/libraries/audio/src/AudioInjector.cpp b/libraries/audio/src/AudioInjector.cpp index 45e1eb57e1..1743504883 100644 --- a/libraries/audio/src/AudioInjector.cpp +++ b/libraries/audio/src/AudioInjector.cpp @@ -21,6 +21,14 @@ #include "AudioInjector.h" +QScriptValue injectorToScriptValue(QScriptEngine* engine, AudioInjector* const& in) { + return engine->newQObject(in); +} + +void injectorFromScriptValue(const QScriptValue& object, AudioInjector*& out) { + out = qobject_cast(object.toQObject()); +} + AudioInjector::AudioInjector(QObject* parent) : QObject(parent), _sound(NULL), @@ -28,7 +36,8 @@ AudioInjector::AudioInjector(QObject* parent) : _shouldStop(false), _loudness(0.0f), _isFinished(false), - _currentSendPosition(0) + _currentSendPosition(0), + _localBuffer(NULL) { } @@ -38,10 +47,17 @@ AudioInjector::AudioInjector(Sound* sound, const AudioInjectorOptions& injectorO _shouldStop(false), _loudness(0.0f), _isFinished(false), - _currentSendPosition(0) + _currentSendPosition(0), + _localBuffer(NULL) { } +AudioInjector::~AudioInjector() { + if (_localBuffer) { + _localBuffer->stop(); + } +} + void AudioInjector::setOptions(AudioInjectorOptions& options) { _options = options; } @@ -50,9 +66,54 @@ float AudioInjector::getLoudness() { return _loudness; } +void AudioInjector::injectAudio() { + if (_options.localOnly) { + injectLocally(); + } else { + injectToMixer(); + } +} + +void AudioInjector::injectLocally() { + bool success = false; + if (_localAudioInterface) { + const QByteArray& soundByteArray = _sound->getByteArray(); + + if (soundByteArray.size() > 0) { + _localBuffer = new AudioInjectorLocalBuffer(_sound->getByteArray(), this); + _localBuffer->open(QIODevice::ReadOnly); + _localBuffer->setShouldLoop(_options.loop); + + QMetaObject::invokeMethod(_localAudioInterface, "outputLocalInjector", + Qt::BlockingQueuedConnection, + Q_RETURN_ARG(bool, success), + Q_ARG(bool, _options.stereo), + Q_ARG(qreal, _options.volume), + Q_ARG(AudioInjector*, this)); + + + + if (!success) { + qDebug() << "AudioInjector::injectLocally could not output locally via _localAudioInterface"; + } + } else { + qDebug() << "AudioInjector::injectLocally called without any data in Sound QByteArray"; + } + + } else { + qDebug() << "AudioInjector::injectLocally cannot inject locally with no local audio interface present."; + } + + if (!success) { + // we never started so we are finished, call our stop method + stop(); + } + +} + const uchar MAX_INJECTOR_VOLUME = 0xFF; -void AudioInjector::injectAudio() { +void AudioInjector::injectToMixer() { QByteArray soundByteArray = _sound->getByteArray(); if (_currentSendPosition < 0 || @@ -75,7 +136,7 @@ void AudioInjector::injectAudio() { packetStream << QUuid::createUuid(); // pack the stereo/mono type of the stream - packetStream << _options.isStereo(); + packetStream << _options.stereo; // pack the flag for loopback uchar loopbackFlag = (uchar) true; @@ -83,13 +144,13 @@ void AudioInjector::injectAudio() { // pack the position for injected audio int positionOptionOffset = injectAudioPacket.size(); - packetStream.writeRawData(reinterpret_cast(&_options.getPosition()), - sizeof(_options.getPosition())); + packetStream.writeRawData(reinterpret_cast(&_options.position), + sizeof(_options.position)); // pack our orientation for injected audio int orientationOptionOffset = injectAudioPacket.size(); - packetStream.writeRawData(reinterpret_cast(&_options.getOrientation()), - sizeof(_options.getOrientation())); + packetStream.writeRawData(reinterpret_cast(&_options.orientation), + sizeof(_options.orientation)); // pack zero for radius float radius = 0; @@ -97,23 +158,23 @@ void AudioInjector::injectAudio() { // pack 255 for attenuation byte int volumeOptionOffset = injectAudioPacket.size(); - quint8 volume = MAX_INJECTOR_VOLUME * _options.getVolume(); + quint8 volume = MAX_INJECTOR_VOLUME * _options.volume; packetStream << volume; - packetStream << _options.ignorePenumbra(); + packetStream << _options.ignorePenumbra; QElapsedTimer timer; timer.start(); int nextFrame = 0; int numPreAudioDataBytes = injectAudioPacket.size(); - bool shouldLoop = _options.getLoop(); + bool shouldLoop = _options.loop; // loop to send off our audio in NETWORK_BUFFER_LENGTH_SAMPLES_PER_CHANNEL byte chunks quint16 outgoingInjectedAudioSequenceNumber = 0; while (_currentSendPosition < soundByteArray.size() && !_shouldStop) { - int bytesToCopy = std::min(((_options.isStereo()) ? 2 : 1) * NETWORK_BUFFER_LENGTH_BYTES_PER_CHANNEL, + int bytesToCopy = std::min(((_options.stereo) ? 2 : 1) * NETWORK_BUFFER_LENGTH_BYTES_PER_CHANNEL, soundByteArray.size() - _currentSendPosition); // Measure the loudness of this frame @@ -125,12 +186,12 @@ void AudioInjector::injectAudio() { _loudness /= (float)(bytesToCopy / sizeof(int16_t)); memcpy(injectAudioPacket.data() + positionOptionOffset, - &_options.getPosition(), - sizeof(_options.getPosition())); + &_options.position, + sizeof(_options.position)); memcpy(injectAudioPacket.data() + orientationOptionOffset, - &_options.getOrientation(), - sizeof(_options.getOrientation())); - volume = MAX_INJECTOR_VOLUME * _options.getVolume(); + &_options.orientation, + sizeof(_options.orientation)); + volume = MAX_INJECTOR_VOLUME * _options.volume; memcpy(injectAudioPacket.data() + volumeOptionOffset, &volume, sizeof(volume)); // resize the QByteArray to the right size @@ -175,3 +236,13 @@ void AudioInjector::injectAudio() { _isFinished = true; emit finished(); } + +void AudioInjector::stop() { + _shouldStop = true; + + if (_options.localOnly) { + // we're only a local injector, so we can say we are finished right away too + _isFinished = true; + emit finished(); + } +} diff --git a/libraries/audio/src/AudioInjector.h b/libraries/audio/src/AudioInjector.h index 68acd3b887..13188c5977 100644 --- a/libraries/audio/src/AudioInjector.h +++ b/libraries/audio/src/AudioInjector.h @@ -18,20 +18,29 @@ #include #include +#include "AudioInjectorLocalBuffer.h" #include "AudioInjectorOptions.h" #include "Sound.h" +class AbstractAudioInterface; + class AudioInjector : public QObject { Q_OBJECT public: AudioInjector(QObject* parent); AudioInjector(Sound* sound, const AudioInjectorOptions& injectorOptions); + ~AudioInjector(); bool isFinished() const { return _isFinished; } int getCurrentSendPosition() const { return _currentSendPosition; } + + AudioInjectorLocalBuffer* getLocalBuffer() const { return _localBuffer; } + bool isLocalOnly() const { return _options.localOnly; } + + void setLocalAudioInterface(AbstractAudioInterface* localAudioInterface) { _localAudioInterface = localAudioInterface; } public slots: void injectAudio(); - void stop() { _shouldStop = true; } + void stop(); void setOptions(AudioInjectorOptions& options); void setCurrentSendPosition(int currentSendPosition) { _currentSendPosition = currentSendPosition; } float getLoudness(); @@ -39,15 +48,22 @@ public slots: signals: void finished(); private: + void injectToMixer(); + void injectLocally(); + Sound* _sound; AudioInjectorOptions _options; bool _shouldStop; float _loudness; bool _isFinished; int _currentSendPosition; - + AbstractAudioInterface* _localAudioInterface; + AudioInjectorLocalBuffer* _localBuffer; }; Q_DECLARE_METATYPE(AudioInjector*) +QScriptValue injectorToScriptValue(QScriptEngine* engine, AudioInjector* const& in); +void injectorFromScriptValue(const QScriptValue& object, AudioInjector*& out); + #endif // hifi_AudioInjector_h diff --git a/libraries/audio/src/AudioInjectorLocalBuffer.cpp b/libraries/audio/src/AudioInjectorLocalBuffer.cpp new file mode 100644 index 0000000000..bdf084091d --- /dev/null +++ b/libraries/audio/src/AudioInjectorLocalBuffer.cpp @@ -0,0 +1,74 @@ +// +// AudioInjectorLocalBuffer.cpp +// libraries/audio/src +// +// Created by Stephen Birarda on 2014-11-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 "AudioInjectorLocalBuffer.h" + +AudioInjectorLocalBuffer::AudioInjectorLocalBuffer(const QByteArray& rawAudioArray, QObject* parent) : + QIODevice(parent), + _rawAudioArray(rawAudioArray), + _shouldLoop(false), + _isStopped(false), + _currentOffset(0) +{ + +} + +void AudioInjectorLocalBuffer::stop() { + _isStopped = true; + QIODevice::close(); +} + +qint64 AudioInjectorLocalBuffer::readData(char* data, qint64 maxSize) { + if (!_isStopped) { + + // first copy to the end of the raw audio + int bytesToEnd = _rawAudioArray.size() - _currentOffset; + + int bytesRead = maxSize; + + if (maxSize > bytesToEnd) { + bytesRead = bytesToEnd; + } + + memcpy(data, _rawAudioArray.data() + _currentOffset, bytesRead); + + // now check if we are supposed to loop and if we can copy more from the beginning + if (_shouldLoop && maxSize != bytesRead) { + bytesRead += recursiveReadFromFront(data + bytesRead, maxSize - bytesRead); + } else { + _currentOffset += bytesRead; + } + + return bytesRead; + } else { + return 0; + } +} + +qint64 AudioInjectorLocalBuffer::recursiveReadFromFront(char* data, qint64 maxSize) { + // see how much we can get in this pass + int bytesRead = maxSize; + + if (bytesRead > _rawAudioArray.size()) { + bytesRead = _rawAudioArray.size(); + } + + // copy that amount + memcpy(data, _rawAudioArray.data(), bytesRead); + + // check if we need to call ourselves again and pull from the front again + if (bytesRead < maxSize) { + return bytesRead + recursiveReadFromFront(data, maxSize); + } else { + _currentOffset = bytesRead; + return bytesRead; + } +} \ No newline at end of file diff --git a/libraries/audio/src/AudioInjectorLocalBuffer.h b/libraries/audio/src/AudioInjectorLocalBuffer.h new file mode 100644 index 0000000000..8b32c6fbc7 --- /dev/null +++ b/libraries/audio/src/AudioInjectorLocalBuffer.h @@ -0,0 +1,40 @@ +// +// AudioInjectorLocalBuffer.h +// libraries/audio/src +// +// Created by Stephen Birarda on 2014-11-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 +// + +#ifndef hifi_AudioInjectorLocalBuffer_h +#define hifi_AudioInjectorLocalBuffer_h + +#include + +class AudioInjectorLocalBuffer : public QIODevice { + Q_OBJECT +public: + AudioInjectorLocalBuffer(const QByteArray& rawAudioArray, QObject* parent); + + void stop(); + + qint64 readData(char* data, qint64 maxSize); + qint64 writeData(const char* data, qint64 maxSize) { return 0; } + + void setShouldLoop(bool shouldLoop) { _shouldLoop = shouldLoop; } + +private: + + qint64 recursiveReadFromFront(char* data, qint64 maxSize); + + QByteArray _rawAudioArray; + bool _shouldLoop; + bool _isStopped; + + int _currentOffset; +}; + +#endif // hifi_AudioInjectorLocalBuffer_h \ No newline at end of file diff --git a/libraries/audio/src/AudioInjectorOptions.cpp b/libraries/audio/src/AudioInjectorOptions.cpp index 670bea2fef..df435cf2cc 100644 --- a/libraries/audio/src/AudioInjectorOptions.cpp +++ b/libraries/audio/src/AudioInjectorOptions.cpp @@ -9,33 +9,60 @@ // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // +#include + #include "AudioInjectorOptions.h" -AudioInjectorOptions::AudioInjectorOptions(QObject* parent) : - QObject(parent), - _position(0.0f, 0.0f, 0.0f), - _volume(1.0f), - _loop(false), - _orientation(glm::vec3(0.0f, 0.0f, 0.0f)), - _isStereo(false), - _ignorePenumbra(false) +AudioInjectorOptions::AudioInjectorOptions() : + position(0.0f, 0.0f, 0.0f), + volume(1.0f), + loop(false), + orientation(glm::vec3(0.0f, 0.0f, 0.0f)), + stereo(false), + ignorePenumbra(false), + localOnly(false) { + } -AudioInjectorOptions::AudioInjectorOptions(const AudioInjectorOptions& other) { - _position = other._position; - _volume = other._volume; - _loop = other._loop; - _orientation = other._orientation; - _isStereo = other._isStereo; - _ignorePenumbra = other._ignorePenumbra; +QScriptValue injectorOptionsToScriptValue(QScriptEngine* engine, const AudioInjectorOptions& injectorOptions) { + QScriptValue obj = engine->newObject(); + obj.setProperty("position", vec3toScriptValue(engine, injectorOptions.position)); + obj.setProperty("volume", injectorOptions.volume); + obj.setProperty("loop", injectorOptions.loop); + obj.setProperty("orientation", quatToScriptValue(engine, injectorOptions.orientation)); + obj.setProperty("stereo", injectorOptions.stereo); + obj.setProperty("ignorePenumbra", injectorOptions.ignorePenumbra); + obj.setProperty("localOnly", injectorOptions.localOnly); + return obj; } -void AudioInjectorOptions::operator=(const AudioInjectorOptions& other) { - _position = other._position; - _volume = other._volume; - _loop = other._loop; - _orientation = other._orientation; - _isStereo = other._isStereo; - _ignorePenumbra = other._ignorePenumbra; -} \ No newline at end of file +void injectorOptionsFromScriptValue(const QScriptValue& object, AudioInjectorOptions& injectorOptions) { + if (object.property("position").isValid()) { + vec3FromScriptValue(object.property("position"), injectorOptions.position); + } + + if (object.property("volume").isValid()) { + injectorOptions.volume = object.property("volume").toNumber(); + } + + if (object.property("loop").isValid()) { + injectorOptions.loop = object.property("loop").toBool(); + } + + if (object.property("orientation").isValid()) { + quatFromScriptValue(object.property("orientation"), injectorOptions.orientation); + } + + if (object.property("stereo").isValid()) { + injectorOptions.stereo = object.property("stereo").toBool(); + } + + if (object.property("ignorePenumbra").isValid()) { + injectorOptions.ignorePenumbra = object.property("ignorePenumbra").toBool(); + } + + if (object.property("localOnly").isValid()) { + injectorOptions.localOnly = object.property("localOnly").toBool(); + } + } \ No newline at end of file diff --git a/libraries/audio/src/AudioInjectorOptions.h b/libraries/audio/src/AudioInjectorOptions.h index 1ccd85be7e..4fd3a0b7ae 100644 --- a/libraries/audio/src/AudioInjectorOptions.h +++ b/libraries/audio/src/AudioInjectorOptions.h @@ -12,54 +12,26 @@ #ifndef hifi_AudioInjectorOptions_h #define hifi_AudioInjectorOptions_h -#include +#include #include #include -#include - -class AudioInjectorOptions : public QObject { - Q_OBJECT - - Q_PROPERTY(glm::quat orientation READ getOrientation WRITE setOrientation) - Q_PROPERTY(glm::vec3 position READ getPosition WRITE setPosition) - Q_PROPERTY(float volume READ getVolume WRITE setVolume) - Q_PROPERTY(bool loop READ getLoop WRITE setLoop) - Q_PROPERTY(bool isStereo READ isStereo WRITE setIsStereo) - Q_PROPERTY(bool ignorePenumbra READ ignorePenumbra WRITE setIgnorePenumbra) +class AudioInjectorOptions { public: - AudioInjectorOptions(QObject* parent = 0); - AudioInjectorOptions(const AudioInjectorOptions& other); - void operator=(const AudioInjectorOptions& other); - - const glm::vec3& getPosition() const { return _position; } - void setPosition(const glm::vec3& position) { _position = position; } - - float getVolume() const { return _volume; } - void setVolume(float volume) { _volume = volume; } - - bool getLoop() const { return _loop; } - void setLoop(bool loop) { _loop = loop; } - - const glm::quat& getOrientation() const { return _orientation; } - void setOrientation(const glm::quat& orientation) { _orientation = orientation; } - - const bool isStereo() const { return _isStereo; } - void setIsStereo(const bool isStereo) { _isStereo = isStereo; } - - const bool ignorePenumbra() const {return _ignorePenumbra; } - void setIgnorePenumbra(bool ignorePenumbra) { _ignorePenumbra = ignorePenumbra; } - -private: - glm::vec3 _position; - float _volume; - bool _loop; - glm::quat _orientation; - bool _isStereo; - bool _ignorePenumbra; + AudioInjectorOptions(); + glm::vec3 position; + float volume; + bool loop; + glm::quat orientation; + bool stereo; + bool ignorePenumbra; + bool localOnly; }; -Q_DECLARE_METATYPE(AudioInjectorOptions) +Q_DECLARE_METATYPE(AudioInjectorOptions); + +QScriptValue injectorOptionsToScriptValue(QScriptEngine* engine, const AudioInjectorOptions& injectorOptions); +void injectorOptionsFromScriptValue(const QScriptValue& object, AudioInjectorOptions& injectorOptions); #endif // hifi_AudioInjectorOptions_h diff --git a/libraries/audio/src/AudioScriptingInterface.cpp b/libraries/audio/src/AudioScriptingInterface.cpp index 33afcdb095..cb010ef11d 100644 --- a/libraries/audio/src/AudioScriptingInterface.cpp +++ b/libraries/audio/src/AudioScriptingInterface.cpp @@ -11,15 +11,20 @@ #include "AudioScriptingInterface.h" +void registerAudioMetaTypes(QScriptEngine* engine) { + qScriptRegisterMetaType(engine, injectorOptionsToScriptValue, injectorOptionsFromScriptValue); + qScriptRegisterMetaType(engine, soundToScriptValue, soundFromScriptValue); +} + AudioScriptingInterface& AudioScriptingInterface::getInstance() { static AudioScriptingInterface staticInstance; return staticInstance; } AudioScriptingInterface::AudioScriptingInterface() : - _localLoopbackInterface(NULL) + _localAudioInterface(NULL) { - qRegisterMetaType("AudioInjectorOptions"); + } void AudioScriptingInterface::stopAllInjectors() { @@ -37,24 +42,10 @@ void AudioScriptingInterface::stopAllInjectors() { } } -void AudioScriptingInterface::playLocalSound(Sound* sound, const AudioInjectorOptions* injectorOptions) { - if (sound->isStereo()) { - const_cast(injectorOptions)->setIsStereo(true); - } +AudioInjector* AudioScriptingInterface::playSound(Sound* sound, const AudioInjectorOptions& injectorOptions) { - // assume that localAudioInterface could be on a separate thread, use Qt::AutoConnection to handle properly - QMetaObject::invokeMethod(_localLoopbackInterface, "handleAudioByteArray", - Qt::AutoConnection, - Q_ARG(QByteArray, sound->getByteArray()), - Q_ARG(const AudioInjectorOptions&, *injectorOptions)); -} - -AudioInjector* AudioScriptingInterface::playSound(Sound* sound, const AudioInjectorOptions* injectorOptions) { - - if (sound->isStereo()) { - const_cast(injectorOptions)->setIsStereo(true); - } - AudioInjector* injector = new AudioInjector(sound, *injectorOptions); + AudioInjector* injector = new AudioInjector(sound, injectorOptions); + injector->setLocalAudioInterface(_localAudioInterface); QThread* injectorThread = new QThread(); diff --git a/libraries/audio/src/AudioScriptingInterface.h b/libraries/audio/src/AudioScriptingInterface.h index 5cd0f9e99a..0017806b40 100644 --- a/libraries/audio/src/AudioScriptingInterface.h +++ b/libraries/audio/src/AudioScriptingInterface.h @@ -18,8 +18,6 @@ #include "AudioInjector.h" #include "Sound.h" -const AudioInjectorOptions DEFAULT_INJECTOR_OPTIONS; - class AudioScriptingInterface : public QObject { Q_OBJECT public: @@ -27,13 +25,12 @@ public: void stopAllInjectors(); - void setLocalLoopbackInterface(AbstractAudioInterface* audioInterface) { _localLoopbackInterface = audioInterface; } + void setLocalAudioInterface(AbstractAudioInterface* audioInterface) { _localAudioInterface = audioInterface; } public slots: static float getLoudness(AudioInjector* injector); - void playLocalSound(Sound *sound, const AudioInjectorOptions* injectorOptions = NULL); - AudioInjector* playSound(Sound* sound, const AudioInjectorOptions* injectorOptions = NULL); + AudioInjector* playSound(Sound* sound, const AudioInjectorOptions& injectorOptions = AudioInjectorOptions()); void stopInjector(AudioInjector* injector); bool isInjectorPlaying(AudioInjector* injector); @@ -43,6 +40,9 @@ public slots: private: AudioScriptingInterface(); QList< QPointer > _activeInjectors; - AbstractAudioInterface* _localLoopbackInterface; + AbstractAudioInterface* _localAudioInterface; }; + +void registerAudioMetaTypes(QScriptEngine* engine); + #endif // hifi_AudioScriptingInterface_h diff --git a/libraries/audio/src/Sound.cpp b/libraries/audio/src/Sound.cpp index 2266385425..4c520f27ce 100644 --- a/libraries/audio/src/Sound.cpp +++ b/libraries/audio/src/Sound.cpp @@ -29,6 +29,15 @@ #include "AudioEditBuffer.h" #include "Sound.h" + +QScriptValue soundToScriptValue(QScriptEngine* engine, Sound* const& in) { + return engine->newQObject(in); +} + +void soundFromScriptValue(const QScriptValue& object, Sound*& out) { + out = qobject_cast(object.toQObject()); +} + // procedural audio version of Sound Sound::Sound(float volume, float frequency, float duration, float decay, QObject* parent) : QObject(parent), diff --git a/libraries/audio/src/Sound.h b/libraries/audio/src/Sound.h index b8fdc6b458..f7b51891f0 100644 --- a/libraries/audio/src/Sound.h +++ b/libraries/audio/src/Sound.h @@ -14,6 +14,7 @@ #include #include +#include class Sound : public QObject { Q_OBJECT @@ -44,4 +45,9 @@ private slots: void replyError(QNetworkReply::NetworkError code); }; +Q_DECLARE_METATYPE(Sound*) + +QScriptValue soundToScriptValue(QScriptEngine* engine, Sound* const& in); +void soundFromScriptValue(const QScriptValue& object, Sound*& out); + #endif // hifi_Sound_h diff --git a/libraries/avatars/src/Player.cpp b/libraries/avatars/src/Player.cpp index 56f820ab97..63384371bd 100644 --- a/libraries/avatars/src/Player.cpp +++ b/libraries/avatars/src/Player.cpp @@ -166,8 +166,8 @@ void Player::pausePlayer() { void Player::setupAudioThread() { _audioThread = new QThread(); - _options.setPosition(_avatar->getPosition()); - _options.setOrientation(_avatar->getOrientation()); + _options.position = _avatar->getPosition(); + _options.orientation = _avatar->getOrientation(); _injector.reset(new AudioInjector(_recording->getAudio(), _options), &QObject::deleteLater); _injector->moveToThread(_audioThread); _audioThread->start(); @@ -292,8 +292,8 @@ void Player::play() { qDebug() << "WARNING: Player couldn't find head data."; } - _options.setPosition(_avatar->getPosition()); - _options.setOrientation(_avatar->getOrientation()); + _options.position = _avatar->getPosition(); + _options.orientation = _avatar->getOrientation(); _injector->setOptions(_options); } @@ -360,7 +360,7 @@ void Player::setCurrentTime(int currentTime) { } void Player::setVolume(float volume) { - _options.setVolume(volume); + _options.volume = volume; if (_injector) { _injector->setOptions(_options); } diff --git a/libraries/entities/src/EntityItemProperties.cpp b/libraries/entities/src/EntityItemProperties.cpp index cb6d9c7bcd..0c184d5e35 100644 --- a/libraries/entities/src/EntityItemProperties.cpp +++ b/libraries/entities/src/EntityItemProperties.cpp @@ -11,6 +11,7 @@ #include #include +#include #include #include @@ -66,6 +67,7 @@ EntityItemProperties::EntityItemProperties() : _animationIsPlaying(ModelEntityItem::DEFAULT_ANIMATION_IS_PLAYING), _animationFrameIndex(ModelEntityItem::DEFAULT_ANIMATION_FRAME_INDEX), _animationFPS(ModelEntityItem::DEFAULT_ANIMATION_FPS), + _animationSettings(""), _glowLevel(0.0f), _localRenderAlpha(1.0f), _isSpotlight(false), @@ -76,6 +78,8 @@ EntityItemProperties::EntityItemProperties() : _animationIsPlayingChanged(false), _animationFrameIndexChanged(false), _animationFPSChanged(false), + _animationSettingsChanged(false), + _glowLevelChanged(false), _localRenderAlphaChanged(false), _isSpotlightChanged(false), @@ -117,6 +121,58 @@ void EntityItemProperties::setSittingPoints(const QVector& sitting } } +void EntityItemProperties::setAnimationSettings(const QString& value) { + // the animations setting is a JSON string that may contain various animation settings. + // if it includes fps, frameIndex, or running, those values will be parsed out and + // will over ride the regular animation settings + + QJsonDocument settingsAsJson = QJsonDocument::fromJson(value.toUtf8()); + QJsonObject settingsAsJsonObject = settingsAsJson.object(); + QVariantMap settingsMap = settingsAsJsonObject.toVariantMap(); + if (settingsMap.contains("fps")) { + float fps = settingsMap["fps"].toFloat(); + setAnimationFPS(fps); + } + + if (settingsMap.contains("frameIndex")) { + float frameIndex = settingsMap["frameIndex"].toFloat(); + setAnimationFrameIndex(frameIndex); + } + + if (settingsMap.contains("running")) { + bool running = settingsMap["running"].toBool(); + setAnimationIsPlaying(running); + } + + _animationSettings = value; + _animationSettingsChanged = true; +} + +QString EntityItemProperties::getAnimationSettings() const { + // the animations setting is a JSON string that may contain various animation settings. + // if it includes fps, frameIndex, or running, those values will be parsed out and + // will over ride the regular animation settings + QString value = _animationSettings; + + QJsonDocument settingsAsJson = QJsonDocument::fromJson(value.toUtf8()); + QJsonObject settingsAsJsonObject = settingsAsJson.object(); + QVariantMap settingsMap = settingsAsJsonObject.toVariantMap(); + + QVariant fpsValue(getAnimationFPS()); + settingsMap["fps"] = fpsValue; + + QVariant frameIndexValue(getAnimationFrameIndex()); + settingsMap["frameIndex"] = frameIndexValue; + + QVariant runningValue(getAnimationIsPlaying()); + settingsMap["running"] = runningValue; + + settingsAsJsonObject = QJsonObject::fromVariantMap(settingsMap); + QJsonDocument newDocument(settingsAsJsonObject); + QByteArray jsonByteArray = newDocument.toJson(QJsonDocument::Compact); + QString jsonByteString(jsonByteArray); + return jsonByteString; +} void EntityItemProperties::debugDump() const { qDebug() << "EntityItemProperties..."; @@ -149,6 +205,7 @@ EntityPropertyFlags EntityItemProperties::getChangedProperties() const { CHECK_PROPERTY_CHANGE(PROP_ANIMATION_PLAYING, animationIsPlaying); CHECK_PROPERTY_CHANGE(PROP_ANIMATION_FRAME_INDEX, animationFrameIndex); CHECK_PROPERTY_CHANGE(PROP_ANIMATION_FPS, animationFPS); + CHECK_PROPERTY_CHANGE(PROP_ANIMATION_SETTINGS, animationSettings); CHECK_PROPERTY_CHANGE(PROP_VISIBLE, visible); CHECK_PROPERTY_CHANGE(PROP_REGISTRATION_POINT, registrationPoint); CHECK_PROPERTY_CHANGE(PROP_ANGULAR_VELOCITY, angularVelocity); @@ -201,8 +258,9 @@ QScriptValue EntityItemProperties::copyToScriptValue(QScriptEngine* engine) cons COPY_PROPERTY_TO_QSCRIPTVALUE(modelURL); COPY_PROPERTY_TO_QSCRIPTVALUE(animationURL); COPY_PROPERTY_TO_QSCRIPTVALUE(animationIsPlaying); - COPY_PROPERTY_TO_QSCRIPTVALUE(animationFrameIndex); COPY_PROPERTY_TO_QSCRIPTVALUE(animationFPS); + COPY_PROPERTY_TO_QSCRIPTVALUE(animationFrameIndex); + COPY_PROPERTY_TO_QSCRIPTVALUE_GETTER(animationSettings,getAnimationSettings()); COPY_PROPERTY_TO_QSCRIPTVALUE(glowLevel); COPY_PROPERTY_TO_QSCRIPTVALUE(localRenderAlpha); COPY_PROPERTY_TO_QSCRIPTVALUE(ignoreForCollisions); @@ -276,6 +334,7 @@ void EntityItemProperties::copyFromScriptValue(const QScriptValue& object) { COPY_PROPERTY_FROM_QSCRIPTVALUE_BOOL(animationIsPlaying, setAnimationIsPlaying); COPY_PROPERTY_FROM_QSCRIPTVALUE_FLOAT(animationFPS, setAnimationFPS); COPY_PROPERTY_FROM_QSCRIPTVALUE_FLOAT(animationFrameIndex, setAnimationFrameIndex); + COPY_PROPERTY_FROM_QSCRIPTVALUE_STRING(animationSettings, setAnimationSettings); COPY_PROPERTY_FROM_QSCRIPTVALUE_FLOAT(glowLevel, setGlowLevel); COPY_PROPERTY_FROM_QSCRIPTVALUE_FLOAT(localRenderAlpha, setLocalRenderAlpha); COPY_PROPERTY_FROM_QSCRIPTVALUE_BOOL(ignoreForCollisions, setIgnoreForCollisions); @@ -452,6 +511,7 @@ bool EntityItemProperties::encodeEntityEditPacket(PacketType command, EntityItem APPEND_ENTITY_PROPERTY(PROP_CUTOFF, appendValue, properties.getCutoff()); APPEND_ENTITY_PROPERTY(PROP_LOCKED, appendValue, properties.getLocked()); APPEND_ENTITY_PROPERTY(PROP_TEXTURES, appendValue, properties.getTextures()); + APPEND_ENTITY_PROPERTY(PROP_ANIMATION_SETTINGS, appendValue, properties.getAnimationSettings()); } if (propertyCount > 0) { int endOfEntityItemData = packetData->getUncompressedByteOffset(); @@ -661,7 +721,8 @@ bool EntityItemProperties::decodeEntityEditPacket(const unsigned char* data, int READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_CUTOFF, float, setCutoff); READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_LOCKED, bool, setLocked); READ_ENTITY_PROPERTY_STRING_TO_PROPERTIES(PROP_TEXTURES, setTextures); - + READ_ENTITY_PROPERTY_STRING_TO_PROPERTIES(PROP_ANIMATION_SETTINGS, setAnimationSettings); + return valid; } @@ -714,6 +775,7 @@ void EntityItemProperties::markAllChanged() { _animationIsPlayingChanged = true; _animationFrameIndexChanged = true; _animationFPSChanged = true; + _animationSettingsChanged = true; _glowLevelChanged = true; _localRenderAlphaChanged = true; _isSpotlightChanged = true; diff --git a/libraries/entities/src/EntityItemProperties.h b/libraries/entities/src/EntityItemProperties.h index 6b22e8cba9..d6b8181c28 100644 --- a/libraries/entities/src/EntityItemProperties.h +++ b/libraries/entities/src/EntityItemProperties.h @@ -80,8 +80,9 @@ enum EntityPropertyList { // used by Model entities PROP_TEXTURES, + PROP_ANIMATION_SETTINGS, - PROP_LAST_ITEM = PROP_CUTOFF + PROP_LAST_ITEM = PROP_ANIMATION_SETTINGS }; typedef PropertyFlags EntityPropertyFlags; @@ -178,6 +179,8 @@ public: float getAnimationFrameIndex() const { return _animationFrameIndex; } bool getAnimationIsPlaying() const { return _animationIsPlaying; } float getAnimationFPS() const { return _animationFPS; } + QString getAnimationSettings() const; + float getGlowLevel() const { return _glowLevel; } float getLocalRenderAlpha() const { return _localRenderAlpha; } const QString& getScript() const { return _script; } @@ -189,6 +192,8 @@ public: void setAnimationFrameIndex(float value) { _animationFrameIndex = value; _animationFrameIndexChanged = true; } void setAnimationIsPlaying(bool value) { _animationIsPlaying = value; _animationIsPlayingChanged = true; } void setAnimationFPS(float value) { _animationFPS = value; _animationFPSChanged = true; } + void setAnimationSettings(const QString& value); + void setGlowLevel(float value) { _glowLevel = value; _glowLevelChanged = true; } void setLocalRenderAlpha(float value) { _localRenderAlpha = value; _localRenderAlphaChanged = true; } void setScript(const QString& value) { _script = value; _scriptChanged = true; } @@ -342,6 +347,7 @@ private: bool _animationIsPlaying; float _animationFrameIndex; float _animationFPS; + QString _animationSettings; float _glowLevel; float _localRenderAlpha; bool _isSpotlight; @@ -352,6 +358,7 @@ private: bool _animationIsPlayingChanged; bool _animationFrameIndexChanged; bool _animationFPSChanged; + bool _animationSettingsChanged; bool _glowLevelChanged; bool _localRenderAlphaChanged; bool _isSpotlightChanged; diff --git a/libraries/entities/src/EntityItemPropertiesMacros.h b/libraries/entities/src/EntityItemPropertiesMacros.h index b5a489f88c..77782cb90f 100644 --- a/libraries/entities/src/EntityItemPropertiesMacros.h +++ b/libraries/entities/src/EntityItemPropertiesMacros.h @@ -40,6 +40,18 @@ } \ } +#define READ_ENTITY_PROPERTY_SETTER(P,T,M) \ + if (propertyFlags.getHasProperty(P)) { \ + T fromBuffer; \ + memcpy(&fromBuffer, dataAt, sizeof(fromBuffer)); \ + dataAt += sizeof(fromBuffer); \ + bytesRead += sizeof(fromBuffer); \ + if (overwriteLocalData) { \ + M(fromBuffer); \ + } \ + } + + #define READ_ENTITY_PROPERTY_QUAT(P,M) \ if (propertyFlags.getHasProperty(P)) { \ glm::quat fromBuffer; \ diff --git a/libraries/entities/src/ModelEntityItem.cpp b/libraries/entities/src/ModelEntityItem.cpp index 827f4f7e39..52b8f7e643 100644 --- a/libraries/entities/src/ModelEntityItem.cpp +++ b/libraries/entities/src/ModelEntityItem.cpp @@ -9,6 +9,8 @@ // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // +#include + #include #include @@ -33,7 +35,6 @@ ModelEntityItem::ModelEntityItem(const EntityItemID& entityItemID, const EntityI { _type = EntityTypes::Model; setProperties(properties, true); - _animationFrameIndex = 0.0f; _lastAnimated = usecTimestampNow(); _jointMappingCompleted = false; _color[0] = _color[1] = _color[2] = 0; @@ -50,6 +51,7 @@ EntityItemProperties ModelEntityItem::getProperties() const { COPY_ENTITY_PROPERTY_TO_PROPERTIES(animationFPS, getAnimationFPS); COPY_ENTITY_PROPERTY_TO_PROPERTIES(glowLevel, getGlowLevel); COPY_ENTITY_PROPERTY_TO_PROPERTIES(textures, getTextures); + COPY_ENTITY_PROPERTY_TO_PROPERTIES(animationSettings, getAnimationSettings); return properties; } @@ -64,6 +66,7 @@ bool ModelEntityItem::setProperties(const EntityItemProperties& properties, bool SET_ENTITY_PROPERTY_FROM_PROPERTIES(animationFrameIndex, setAnimationFrameIndex); SET_ENTITY_PROPERTY_FROM_PROPERTIES(animationFPS, setAnimationFPS); SET_ENTITY_PROPERTY_FROM_PROPERTIES(textures, setTextures); + SET_ENTITY_PROPERTY_FROM_PROPERTIES(animationSettings, setAnimationSettings); if (somethingChanged) { bool wantDebug = false; @@ -100,10 +103,29 @@ int ModelEntityItem::readEntitySubclassDataFromBuffer(const unsigned char* data, READ_ENTITY_PROPERTY_COLOR(PROP_COLOR, _color); READ_ENTITY_PROPERTY_STRING(PROP_MODEL_URL, setModelURL); READ_ENTITY_PROPERTY_STRING(PROP_ANIMATION_URL, setAnimationURL); - READ_ENTITY_PROPERTY(PROP_ANIMATION_FPS, float, _animationFPS); - READ_ENTITY_PROPERTY(PROP_ANIMATION_FRAME_INDEX, float, _animationFrameIndex); - READ_ENTITY_PROPERTY(PROP_ANIMATION_PLAYING, bool, _animationIsPlaying); + + // Because we're using AnimationLoop which will reset the frame index if you change it's running state + // we want to read these values in the order they appear in the buffer, but call our setters in an + // order that allows AnimationLoop to preserve the correct frame rate. + float animationFPS = getAnimationFPS(); + float animationFrameIndex = getAnimationFrameIndex(); + bool animationIsPlaying = getAnimationIsPlaying(); + READ_ENTITY_PROPERTY(PROP_ANIMATION_FPS, float, animationFPS); + READ_ENTITY_PROPERTY(PROP_ANIMATION_FRAME_INDEX, float, animationFrameIndex); + READ_ENTITY_PROPERTY(PROP_ANIMATION_PLAYING, bool, animationIsPlaying); + + if (propertyFlags.getHasProperty(PROP_ANIMATION_PLAYING)) { + setAnimationIsPlaying(animationIsPlaying); + } + if (propertyFlags.getHasProperty(PROP_ANIMATION_FPS)) { + setAnimationFPS(animationFPS); + } + if (propertyFlags.getHasProperty(PROP_ANIMATION_FRAME_INDEX)) { + setAnimationFrameIndex(animationFrameIndex); + } + READ_ENTITY_PROPERTY_STRING(PROP_TEXTURES, setTextures); + READ_ENTITY_PROPERTY_STRING(PROP_ANIMATION_SETTINGS, setAnimationSettings); return bytesRead; } @@ -199,19 +221,25 @@ int ModelEntityItem::oldVersionReadEntityDataFromBuffer(const unsigned char* dat bytesRead += animationURLLength; // animationIsPlaying - memcpy(&_animationIsPlaying, dataAt, sizeof(_animationIsPlaying)); - dataAt += sizeof(_animationIsPlaying); - bytesRead += sizeof(_animationIsPlaying); + bool animationIsPlaying; + memcpy(&animationIsPlaying, dataAt, sizeof(animationIsPlaying)); + dataAt += sizeof(animationIsPlaying); + bytesRead += sizeof(animationIsPlaying); + setAnimationIsPlaying(animationIsPlaying); // animationFrameIndex - memcpy(&_animationFrameIndex, dataAt, sizeof(_animationFrameIndex)); - dataAt += sizeof(_animationFrameIndex); - bytesRead += sizeof(_animationFrameIndex); + float animationFrameIndex; + memcpy(&animationFrameIndex, dataAt, sizeof(animationFrameIndex)); + dataAt += sizeof(animationFrameIndex); + bytesRead += sizeof(animationFrameIndex); + setAnimationFrameIndex(animationFrameIndex); // animationFPS - memcpy(&_animationFPS, dataAt, sizeof(_animationFPS)); - dataAt += sizeof(_animationFPS); - bytesRead += sizeof(_animationFPS); + float animationFPS; + memcpy(&animationFPS, dataAt, sizeof(animationFPS)); + dataAt += sizeof(animationFPS); + bytesRead += sizeof(animationFPS); + setAnimationFPS(animationFPS); } } return bytesRead; @@ -227,6 +255,7 @@ EntityPropertyFlags ModelEntityItem::getEntityProperties(EncodeBitstreamParams& requestedProperties += PROP_ANIMATION_FPS; requestedProperties += PROP_ANIMATION_FRAME_INDEX; requestedProperties += PROP_ANIMATION_PLAYING; + requestedProperties += PROP_ANIMATION_SETTINGS; requestedProperties += PROP_TEXTURES; return requestedProperties; @@ -249,6 +278,7 @@ void ModelEntityItem::appendSubclassData(OctreePacketData* packetData, EncodeBit APPEND_ENTITY_PROPERTY(PROP_ANIMATION_FRAME_INDEX, appendValue, getAnimationFrameIndex()); APPEND_ENTITY_PROPERTY(PROP_ANIMATION_PLAYING, appendValue, getAnimationIsPlaying()); APPEND_ENTITY_PROPERTY(PROP_TEXTURES, appendValue, getTextures()); + APPEND_ENTITY_PROPERTY(PROP_ANIMATION_SETTINGS, appendValue, getAnimationSettings()); } @@ -314,7 +344,7 @@ QVector ModelEntityItem::getAnimationFrame() { int frameCount = frames.size(); if (frameCount > 0) { - int animationFrameIndex = (int)(glm::floor(_animationFrameIndex)) % frameCount; + int animationFrameIndex = (int)(glm::floor(getAnimationFrameIndex())) % frameCount; if (animationFrameIndex < 0 || animationFrameIndex > frameCount) { animationFrameIndex = 0; @@ -363,7 +393,7 @@ void ModelEntityItem::update(const quint64& updateTime) { if (getAnimationIsPlaying()) { float deltaTime = (float)(now - _lastAnimated) / (float)USECS_PER_SECOND; _lastAnimated = now; - _animationFrameIndex += deltaTime * _animationFPS; + _animationLoop.simulate(deltaTime); } else { _lastAnimated = now; } @@ -377,3 +407,94 @@ void ModelEntityItem::debugDump() const { qDebug() << " model URL:" << getModelURL(); } +void ModelEntityItem::setAnimationSettings(const QString& value) { + // the animations setting is a JSON string that may contain various animation settings. + // if it includes fps, frameIndex, or running, those values will be parsed out and + // will over ride the regular animation settings + + QJsonDocument settingsAsJson = QJsonDocument::fromJson(value.toUtf8()); + QJsonObject settingsAsJsonObject = settingsAsJson.object(); + QVariantMap settingsMap = settingsAsJsonObject.toVariantMap(); + if (settingsMap.contains("fps")) { + float fps = settingsMap["fps"].toFloat(); + setAnimationFPS(fps); + } + + if (settingsMap.contains("frameIndex")) { + float frameIndex = settingsMap["frameIndex"].toFloat(); + setAnimationFrameIndex(frameIndex); + } + + if (settingsMap.contains("running")) { + bool running = settingsMap["running"].toBool(); + setAnimationIsPlaying(running); + } + + if (settingsMap.contains("firstFrame")) { + float firstFrame = settingsMap["firstFrame"].toFloat(); + setAnimationFirstFrame(firstFrame); + } + + if (settingsMap.contains("lastFrame")) { + float lastFrame = settingsMap["lastFrame"].toFloat(); + setAnimationLastFrame(lastFrame); + } + + if (settingsMap.contains("loop")) { + bool loop = settingsMap["loop"].toBool(); + setAnimationLoop(loop); + } + + if (settingsMap.contains("hold")) { + bool hold = settingsMap["hold"].toBool(); + setAnimationHold(hold); + } + + if (settingsMap.contains("startAutomatically")) { + bool startAutomatically = settingsMap["startAutomatically"].toBool(); + setAnimationStartAutomatically(startAutomatically); + } + + _animationSettings = value; +} + +QString ModelEntityItem::getAnimationSettings() const { + // the animations setting is a JSON string that may contain various animation settings. + // if it includes fps, frameIndex, or running, those values will be parsed out and + // will over ride the regular animation settings + QString value = _animationSettings; + + QJsonDocument settingsAsJson = QJsonDocument::fromJson(value.toUtf8()); + QJsonObject settingsAsJsonObject = settingsAsJson.object(); + QVariantMap settingsMap = settingsAsJsonObject.toVariantMap(); + + QVariant fpsValue(getAnimationFPS()); + settingsMap["fps"] = fpsValue; + + QVariant frameIndexValue(getAnimationFrameIndex()); + settingsMap["frameIndex"] = frameIndexValue; + + QVariant runningValue(getAnimationIsPlaying()); + settingsMap["running"] = runningValue; + + QVariant firstFrameValue(getAnimationFirstFrame()); + settingsMap["firstFrame"] = firstFrameValue; + + QVariant lastFrameValue(getAnimationLastFrame()); + settingsMap["lastFrame"] = lastFrameValue; + + QVariant loopValue(getAnimationLoop()); + settingsMap["loop"] = loopValue; + + QVariant holdValue(getAnimationHold()); + settingsMap["hold"] = holdValue; + + QVariant startAutomaticallyValue(getAnimationStartAutomatically()); + settingsMap["startAutomatically"] = startAutomaticallyValue; + + settingsAsJsonObject = QJsonObject::fromVariantMap(settingsMap); + QJsonDocument newDocument(settingsAsJsonObject); + QByteArray jsonByteArray = newDocument.toJson(QJsonDocument::Compact); + QString jsonByteString(jsonByteArray); + return jsonByteString; +} diff --git a/libraries/entities/src/ModelEntityItem.h b/libraries/entities/src/ModelEntityItem.h index 4c79a46c5e..97ffed4076 100644 --- a/libraries/entities/src/ModelEntityItem.h +++ b/libraries/entities/src/ModelEntityItem.h @@ -12,6 +12,8 @@ #ifndef hifi_ModelEntityItem_h #define hifi_ModelEntityItem_h +#include + #include "EntityItem.h" class ModelEntityItem : public EntityItem { @@ -73,21 +75,38 @@ public: void setModelURL(const QString& url) { _modelURL = url; } void setAnimationURL(const QString& url) { _animationURL = url; } static const float DEFAULT_ANIMATION_FRAME_INDEX; - void setAnimationFrameIndex(float value) { _animationFrameIndex = value; } + void setAnimationFrameIndex(float value) { _animationLoop.setFrameIndex(value); } + void setAnimationSettings(const QString& value); static const bool DEFAULT_ANIMATION_IS_PLAYING; - void setAnimationIsPlaying(bool value) { _animationIsPlaying = value; } + void setAnimationIsPlaying(bool value) { _animationLoop.setRunning(value); } static const float DEFAULT_ANIMATION_FPS; - void setAnimationFPS(float value) { _animationFPS = value; } + void setAnimationFPS(float value) { _animationLoop.setFPS(value); } + + void setAnimationLoop(bool loop) { _animationLoop.setLoop(loop); } + bool getAnimationLoop() const { return _animationLoop.getLoop(); } + + void setAnimationHold(bool hold) { _animationLoop.setHold(hold); } + bool getAnimationHold() const { return _animationLoop.getHold(); } + + void setAnimationStartAutomatically(bool startAutomatically) { _animationLoop.setStartAutomatically(startAutomatically); } + bool getAnimationStartAutomatically() const { return _animationLoop.getStartAutomatically(); } + + void setAnimationFirstFrame(float firstFrame) { _animationLoop.setFirstFrame(firstFrame); } + float getAnimationFirstFrame() const { return _animationLoop.getFirstFrame(); } + + void setAnimationLastFrame(float lastFrame) { _animationLoop.setLastFrame(lastFrame); } + float getAnimationLastFrame() const { return _animationLoop.getLastFrame(); } void mapJoints(const QStringList& modelJointNames); QVector getAnimationFrame(); bool jointsMapped() const { return _jointMappingCompleted; } - bool getAnimationIsPlaying() const { return _animationIsPlaying; } - float getAnimationFrameIndex() const { return _animationFrameIndex; } - float getAnimationFPS() const { return _animationFPS; } + bool getAnimationIsPlaying() const { return _animationLoop.isRunning(); } + float getAnimationFrameIndex() const { return _animationLoop.getFrameIndex(); } + float getAnimationFPS() const { return _animationLoop.getFPS(); } + QString getAnimationSettings() const; static const QString DEFAULT_TEXTURES; const QString& getTextures() const { return _textures; } @@ -106,9 +125,8 @@ protected: quint64 _lastAnimated; QString _animationURL; - float _animationFrameIndex; // we keep this as a float and round to int only when we need the exact index - bool _animationIsPlaying; - float _animationFPS; + AnimationLoop _animationLoop; + QString _animationSettings; QString _textures; // used on client side diff --git a/libraries/networking/src/PacketHeaders.cpp b/libraries/networking/src/PacketHeaders.cpp index b5cf84ee28..a5fdd86e3d 100644 --- a/libraries/networking/src/PacketHeaders.cpp +++ b/libraries/networking/src/PacketHeaders.cpp @@ -75,7 +75,7 @@ PacketVersion versionForPacketType(PacketType type) { return 1; case PacketTypeEntityAddOrEdit: case PacketTypeEntityData: - return VERSION_ENTITIES_SUPPORT_DIMENSIONS; + return VERSION_ENTITIES_MODELS_HAVE_ANIMATION_SETTINGS; case PacketTypeEntityErase: return 2; case PacketTypeAudioStreamStats: diff --git a/libraries/networking/src/PacketHeaders.h b/libraries/networking/src/PacketHeaders.h index 2e9ce697f0..466aebd36b 100644 --- a/libraries/networking/src/PacketHeaders.h +++ b/libraries/networking/src/PacketHeaders.h @@ -124,6 +124,7 @@ const PacketVersion VERSION_ROOT_ELEMENT_HAS_DATA = 2; const PacketVersion VERSION_ENTITIES_SUPPORT_SPLIT_MTU = 3; const PacketVersion VERSION_ENTITIES_HAS_FILE_BREAKS = VERSION_ENTITIES_SUPPORT_SPLIT_MTU; const PacketVersion VERSION_ENTITIES_SUPPORT_DIMENSIONS = 4; +const PacketVersion VERSION_ENTITIES_MODELS_HAVE_ANIMATION_SETTINGS = 5; const PacketVersion VERSION_VOXELS_HAS_FILE_BREAKS = 1; #endif // hifi_PacketHeaders_h diff --git a/libraries/octree/src/Octree.cpp b/libraries/octree/src/Octree.cpp index 596dd7536c..4450689949 100644 --- a/libraries/octree/src/Octree.cpp +++ b/libraries/octree/src/Octree.cpp @@ -23,6 +23,7 @@ #include #include +#include #include #include #include @@ -71,6 +72,10 @@ void Octree::recurseTreeWithPostOperation(RecurseOctreeOperation operation, void void Octree::recurseElementWithOperation(OctreeElement* element, RecurseOctreeOperation operation, void* extraData, int recursionCount) { if (recursionCount > DANGEROUSLY_DEEP_RECURSION) { + static QString repeatedMessage + = LogHandler::getInstance().addRepeatedMessageRegex( + "Octree::recurseElementWithOperation() reached DANGEROUSLY_DEEP_RECURSION, bailing!"); + qDebug() << "Octree::recurseElementWithOperation() reached DANGEROUSLY_DEEP_RECURSION, bailing!"; return; } @@ -89,7 +94,11 @@ void Octree::recurseElementWithOperation(OctreeElement* element, RecurseOctreeOp void Octree::recurseElementWithPostOperation(OctreeElement* element, RecurseOctreeOperation operation, void* extraData, int recursionCount) { if (recursionCount > DANGEROUSLY_DEEP_RECURSION) { - qDebug() << "Octree::recurseElementWithOperation() reached DANGEROUSLY_DEEP_RECURSION, bailing!\n"; + static QString repeatedMessage + = LogHandler::getInstance().addRepeatedMessageRegex( + "Octree::recurseElementWithPostOperation() reached DANGEROUSLY_DEEP_RECURSION, bailing!"); + + qDebug() << "Octree::recurseElementWithPostOperation() reached DANGEROUSLY_DEEP_RECURSION, bailing!"; return; } @@ -115,6 +124,10 @@ void Octree::recurseElementWithOperationDistanceSorted(OctreeElement* element, R const glm::vec3& point, void* extraData, int recursionCount) { if (recursionCount > DANGEROUSLY_DEEP_RECURSION) { + static QString repeatedMessage + = LogHandler::getInstance().addRepeatedMessageRegex( + "Octree::recurseElementWithOperationDistanceSorted() reached DANGEROUSLY_DEEP_RECURSION, bailing!"); + qDebug() << "Octree::recurseElementWithOperationDistanceSorted() reached DANGEROUSLY_DEEP_RECURSION, bailing!"; return; } @@ -152,7 +165,11 @@ void Octree::recurseTreeWithOperator(RecurseOctreeOperator* operatorObject) { bool Octree::recurseElementWithOperator(OctreeElement* element, RecurseOctreeOperator* operatorObject, int recursionCount) { if (recursionCount > DANGEROUSLY_DEEP_RECURSION) { - qDebug() << "Octree::recurseElementWithOperation() reached DANGEROUSLY_DEEP_RECURSION, bailing!"; + static QString repeatedMessage + = LogHandler::getInstance().addRepeatedMessageRegex( + "Octree::recurseElementWithOperator() reached DANGEROUSLY_DEEP_RECURSION, bailing!"); + + qDebug() << "Octree::recurseElementWithOperator() reached DANGEROUSLY_DEEP_RECURSION, bailing!"; return false; } diff --git a/libraries/octree/src/OctreeSceneStats.cpp b/libraries/octree/src/OctreeSceneStats.cpp index 82a874a680..4b21890a22 100644 --- a/libraries/octree/src/OctreeSceneStats.cpp +++ b/libraries/octree/src/OctreeSceneStats.cpp @@ -13,6 +13,7 @@ #include #include +#include #include #include @@ -854,8 +855,12 @@ void OctreeSceneStats::trackIncomingOctreePacket(const QByteArray& packet, const int MAX_RESONABLE_FLIGHT_TIME = 200 * USECS_PER_SECOND; // 200 seconds is more than enough time for a packet to arrive const int MIN_RESONABLE_FLIGHT_TIME = 0; if (flightTime > MAX_RESONABLE_FLIGHT_TIME || flightTime < MIN_RESONABLE_FLIGHT_TIME) { + static QString repeatedMessage + = LogHandler::getInstance().addRepeatedMessageRegex( + "ignoring unreasonable packet... flightTime: -?\\d+ nodeClockSkewUsec: -?\\d+ usecs"); + qDebug() << "ignoring unreasonable packet... flightTime:" << flightTime - << " nodeClockSkewUsec:" << nodeClockSkewUsec << " usecs";; + << "nodeClockSkewUsec:" << nodeClockSkewUsec << "usecs";; return; // ignore any packets that are unreasonable } diff --git a/libraries/script-engine/src/ScriptEngine.cpp b/libraries/script-engine/src/ScriptEngine.cpp index 40602645f1..2691b10273 100644 --- a/libraries/script-engine/src/ScriptEngine.cpp +++ b/libraries/script-engine/src/ScriptEngine.cpp @@ -74,14 +74,6 @@ void avatarDataFromScriptValue(const QScriptValue &object, AvatarData* &out) { out = qobject_cast(object.toQObject()); } -QScriptValue injectorToScriptValue(QScriptEngine* engine, AudioInjector* const &in) { - return engine->newQObject(in); -} - -void injectorFromScriptValue(const QScriptValue &object, AudioInjector* &out) { - out = qobject_cast(object.toQObject()); -} - QScriptValue inputControllerToScriptValue(QScriptEngine *engine, AbstractInputController* const &in) { return engine->newQObject(in); } @@ -234,7 +226,6 @@ bool ScriptEngine::setScriptContents(const QString& scriptContents, const QStrin return true; } -Q_SCRIPT_DECLARE_QMETAOBJECT(AudioInjectorOptions, QObject*) Q_SCRIPT_DECLARE_QMETAOBJECT(LocalVoxels, QString) void ScriptEngine::init() { @@ -254,6 +245,7 @@ void ScriptEngine::init() { registerMenuItemProperties(this); registerAnimationTypes(this); registerAvatarTypes(this); + registerAudioMetaTypes(this); Bitstream::registerTypes(this); qScriptRegisterMetaType(this, EntityItemPropertiesToScriptValue, EntityItemPropertiesFromScriptValue); @@ -275,9 +267,6 @@ void ScriptEngine::init() { QScriptValue soundMetaObject = newQMetaObject(&Sound::staticMetaObject, soundConstructorValue); globalObject().setProperty("Sound", soundMetaObject); - QScriptValue injectionOptionValue = scriptValueFromQMetaObject(); - globalObject().setProperty("AudioInjectionOptions", injectionOptionValue); - QScriptValue localVoxelsValue = scriptValueFromQMetaObject(); globalObject().setProperty("LocalVoxels", localVoxelsValue); diff --git a/libraries/shared/src/SharedUtil.h b/libraries/shared/src/SharedUtil.h index ac1b73e50b..69dfb4db35 100644 --- a/libraries/shared/src/SharedUtil.h +++ b/libraries/shared/src/SharedUtil.h @@ -34,6 +34,15 @@ struct xColor { unsigned char blue; }; +inline QDebug& operator<<(QDebug& dbg, const xColor& c) { + dbg.nospace() << "{type='xColor'" + ", red=" << c.red << + ", green=" << c.green << + ", blue=" << c.blue << + "}"; + return dbg; +} + static const float ZERO = 0.0f; static const float ONE = 1.0f; static const float ONE_HALF = 0.5f; diff --git a/libraries/shared/src/Transform.cpp b/libraries/shared/src/Transform.cpp new file mode 100644 index 0000000000..9945091ed0 --- /dev/null +++ b/libraries/shared/src/Transform.cpp @@ -0,0 +1,71 @@ +// +// Transform.cpp +// shared/src/gpu +// +// Created by Sam Gateau on 11/4/2014. +// Copyright 2014 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +#include "Transform.h" + + +void Transform::evalRotationScale(Quat& rotation, Vec3& scale, const Mat3& rotationScaleMatrix) { + const float ACCURACY_THREASHOLD = 0.00001f; + + // Following technique taken from: + // http://callumhay.blogspot.com/2010/10/decomposing-affine-transforms.html + // Extract the rotation component - this is done using polar decompostion, where + // we successively average the matrix with its inverse transpose until there is + // no/a very small difference between successive averages + float norm; + int count = 0; + Mat3 rotationMat = rotationScaleMatrix; + do { + Mat3 currInvTranspose = glm::inverse(glm::transpose(rotationMat)); + + Mat3 nextRotation = 0.5f * (rotationMat + currInvTranspose); + + norm = 0.0; + for (int i = 0; i < 3; i++) { + float n = static_cast( + fabs(rotationMat[0][i] - nextRotation[0][i]) + + fabs(rotationMat[1][i] - nextRotation[1][i]) + + fabs(rotationMat[2][i] - nextRotation[2][i])); + norm = (norm > n ? norm : n); + } + rotationMat = nextRotation; + } while (count < 100 && norm > ACCURACY_THREASHOLD); + + + // extract scale of the matrix as the length of each axis + Mat3 scaleMat = glm::inverse(rotationMat) * rotationScaleMatrix; + + scale = glm::max(Vec3(ACCURACY_THREASHOLD), Vec3(scaleMat[0][0], scaleMat[1][1], scaleMat[2][2])); + + // Let's work on a local matrix containing rotation only + Mat3 matRot( + rotationScaleMatrix[0] / scale.x, + rotationScaleMatrix[1] / scale.y, + rotationScaleMatrix[2] / scale.z); + + // Beware!!! needs to detect for the case there is a negative scale + // Based on the determinant sign we just can flip the scale sign of one component: we choose X axis + float determinant = glm::determinant(matRot); + if (determinant < 0.f) { + scale.x = -scale.x; + matRot[0] *= -1.f; + } + + // Beware: even though the matRot is supposed to be normalized at that point, + // glm::quat_cast doesn't always return a normalized quaternion... + // rotation = glm::normalize(glm::quat_cast(matRot)); + rotation = (glm::quat_cast(matRot)); +} + + + + + diff --git a/libraries/shared/src/Transform.h b/libraries/shared/src/Transform.h new file mode 100644 index 0000000000..6ad106063f --- /dev/null +++ b/libraries/shared/src/Transform.h @@ -0,0 +1,397 @@ +// +// Transform.h +// shared/src/gpu +// +// Created by Sam Gateau on 11/4/2014. +// Copyright 2014 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// +#ifndef hifi_gpu_Transform_h +#define hifi_gpu_Transform_h + +#include + +#include +#include +#include +#include + +#include + +class Transform { +public: + typedef glm::mat4 Mat4; + typedef glm::mat3 Mat3; + typedef glm::vec4 Vec4; + typedef glm::vec3 Vec3; + typedef glm::vec2 Vec2; + typedef glm::quat Quat; + + Transform() : + _translation(0), + _rotation(1.0f, 0, 0, 0), + _scale(1.0f), + _flags(FLAG_CACHE_INVALID_BITSET) // invalid cache + { + } + Transform(const Transform& transform) : + _translation(transform._translation), + _rotation(transform._rotation), + _scale(transform._scale), + _flags(transform._flags) + { + invalidCache(); + } + Transform(const Mat4& raw) { + evalFromRawMatrix(raw); + } + ~Transform() {} + + void setIdentity(); + + const Vec3& getTranslation() const; + void setTranslation(const Vec3& translation); + void preTranslate(const Vec3& translation); + void postTranslate(const Vec3& translation); + + const Quat& getRotation() const; + void setRotation(const Quat& rotation); + void preRotate(const Quat& rotation); + void postRotate(const Quat& rotation); + + const Vec3& getScale() const; + void setScale(float scale); + void setScale(const Vec3& scale); + void postScale(float scale); + void postScale(const Vec3& scale); + + bool isIdentity() const { return (_flags & ~Flags(FLAG_CACHE_INVALID_BITSET)).none(); } + bool isTranslating() const { return _flags[FLAG_TRANSLATION]; } + bool isRotating() const { return _flags[FLAG_ROTATION]; } + bool isScaling() const { return _flags[FLAG_SCALING]; } + bool isUniform() const { return !isNonUniform(); } + bool isNonUniform() const { return _flags[FLAG_NON_UNIFORM]; } + + void evalFromRawMatrix(const Mat4& matrix); + void evalFromRawMatrix(const Mat3& rotationScalematrix); + + Mat4& getMatrix(Mat4& result) const; + Mat4& getInverseMatrix(Mat4& result) const; + + Transform& evalInverse(Transform& result) const; + + static void evalRotationScale(Quat& rotation, Vec3& scale, const Mat3& rotationScaleMatrix); + + static Transform& mult(Transform& result, const Transform& left, const Transform& right); + + // Left will be inversed before the multiplication + static Transform& inverseMult(Transform& result, const Transform& left, const Transform& right); + + +protected: + + enum Flag { + FLAG_CACHE_INVALID = 0, + + FLAG_TRANSLATION, + FLAG_ROTATION, + FLAG_SCALING, + FLAG_NON_UNIFORM, + FLAG_ZERO_SCALE, + + FLAG_PROJECTION, + + NUM_FLAGS, + + FLAG_CACHE_INVALID_BITSET = 1, + }; + typedef std::bitset Flags; + + + // TRS + Vec3 _translation; + Quat _rotation; + Vec3 _scale; + + mutable Flags _flags; + + // Cached transform + mutable Mat4 _matrix; + + bool isCacheInvalid() const { return _flags[FLAG_CACHE_INVALID]; } + void validCache() const { _flags.set(FLAG_CACHE_INVALID, false); } + void invalidCache() const { _flags.set(FLAG_CACHE_INVALID, true); } + + void flagTranslation() { _flags.set(FLAG_TRANSLATION, true); } + void flagRotation() { _flags.set(FLAG_ROTATION, true); } + + void flagScaling() { _flags.set(FLAG_SCALING, true); } + void unflagScaling() { _flags.set(FLAG_SCALING, false); } + + + void flagUniform() { _flags.set(FLAG_NON_UNIFORM, false); } + void flagNonUniform() { _flags.set(FLAG_NON_UNIFORM, true); } + + void updateCache() const; +}; + +inline void Transform::setIdentity() { + _translation = Vec3(0); + _rotation = Quat(1.0f, 0, 0, 0); + _scale = Vec3(1.0f); + _flags = Flags(FLAG_CACHE_INVALID_BITSET); +} + +inline const Transform::Vec3& Transform::getTranslation() const { + return _translation; +} + +inline void Transform::setTranslation(const Vec3& translation) { + invalidCache(); + flagTranslation(); + _translation = translation; +} + +inline void Transform::preTranslate(const Vec3& translation) { + invalidCache(); + flagTranslation(); + _translation += translation; +} + +inline void Transform::postTranslate(const Vec3& translation) { + invalidCache(); + flagTranslation(); + + Vec3 scaledT = translation; + if (isScaling()) scaledT *= _scale; + + if (isRotating()) { + _translation += glm::rotate(_rotation, scaledT); + } else { + _translation += scaledT; + } +} + +inline const Transform::Quat& Transform::getRotation() const { + return _rotation; +} + +inline void Transform::setRotation(const Quat& rotation) { + invalidCache(); + flagRotation(); + _rotation = rotation; +} + +inline void Transform::preRotate(const Quat& rotation) { + invalidCache(); + if (isRotating()) { + _rotation = rotation * _rotation; + } else { + _rotation = rotation; + } + flagRotation(); + _translation = glm::rotate(rotation, _translation); +} + +inline void Transform::postRotate(const Quat& rotation) { + invalidCache(); + + if (isNonUniform()) { + Quat newRot; + Vec3 newScale; + Mat3 scaleRot(glm::mat3_cast(rotation)); + scaleRot[0] *= _scale; + scaleRot[1] *= _scale; + scaleRot[2] *= _scale; + evalRotationScale(newRot, newScale, scaleRot); + + if (isRotating()) { + _rotation *= newRot; + } else { + _rotation = newRot; + } + setScale(newScale); + } else { + if (isRotating()) { + _rotation *= rotation; + } else { + _rotation = rotation; + } + } + flagRotation(); +} + +inline const Transform::Vec3& Transform::getScale() const { + return _scale; +} + +inline void Transform::setScale(float scale) { + invalidCache(); + flagUniform(); + if (scale == 1.0f) { + unflagScaling(); + } else { + flagScaling(); + } + _scale = Vec3(scale); +} + +inline void Transform::setScale(const Vec3& scale) { + if ((scale.x == scale.y) && (scale.x == scale.z)) { + setScale(scale.x); + } else { + invalidCache(); + flagScaling(); + flagNonUniform(); + _scale = scale; + } +} + +inline void Transform::postScale(float scale) { + if (scale == 1.0f) return; + if (isScaling()) { + // if already scaling, just invalid cache and aply uniform scale + invalidCache(); + _scale *= scale; + } else { + setScale(scale); + } +} + +inline void Transform::postScale(const Vec3& scale) { + invalidCache(); + if (isScaling()) { + _scale *= scale; + } else { + _scale = scale; + } + flagScaling(); +} + +inline Transform::Mat4& Transform::getMatrix(Transform::Mat4& result) const { + updateCache(); + result = _matrix; + return result; +} + +inline Transform::Mat4& Transform::getInverseMatrix(Transform::Mat4& result) const { + Transform inverse; + evalInverse(inverse); + return inverse.getMatrix(result); +} + +inline void Transform::evalFromRawMatrix(const Mat4& matrix) { + // for now works only in the case of TRS transformation + if ((matrix[0][3] == 0) && (matrix[1][3] == 0) && (matrix[2][3] == 0) && (matrix[3][3] == 1.f)) { + setTranslation(Vec3(matrix[3])); + evalFromRawMatrix(Mat3(matrix)); + } +} + +inline void Transform::evalFromRawMatrix(const Mat3& rotationScaleMatrix) { + Quat rotation; + Vec3 scale; + evalRotationScale(rotation, scale, rotationScaleMatrix); + setRotation(rotation); + setScale(scale); +} + +inline Transform& Transform::evalInverse(Transform& inverse) const { + inverse.setIdentity(); + if (isScaling()) { + // TODO: At some point we will face the case when scale is 0 and so 1/0 will blow up... + // WHat should we do for this one? + assert(_scale.x != 0); + assert(_scale.y != 0); + assert(_scale.z != 0); + if (isNonUniform()) { + inverse.setScale(Vec3(1.0f/_scale.x, 1.0f/_scale.y, 1.0f/_scale.z)); + } else { + inverse.setScale(1.0f/_scale.x); + } + } + if (isRotating()) { + inverse.postRotate(glm::conjugate(_rotation)); + } + if (isTranslating()) { + inverse.postTranslate(-_translation); + } + return inverse; +} + +inline Transform& Transform::mult( Transform& result, const Transform& left, const Transform& right) { + result = left; + if (right.isTranslating()) { + result.postTranslate(right.getTranslation()); + } + if (right.isRotating()) { + result.postRotate(right.getRotation()); + } + if (right.isScaling()) { + result.postScale(right.getScale()); + } + + // HACK: In case of an issue in the Transform multiplication results, to make sure this code is + // working properly uncomment the next 2 lines and compare the results, they should be the same... + // Transform::Mat4 mv = left.getMatrix() * right.getMatrix(); + // Transform::Mat4 mv2 = result.getMatrix(); + + return result; +} + +inline Transform& Transform::inverseMult( Transform& result, const Transform& left, const Transform& right) { + result.setIdentity(); + + if (left.isScaling()) { + const Vec3& s = left.getScale(); + result.setScale(Vec3(1.0f / s.x, 1.0f / s.y, 1.0f / s.z)); + } + if (left.isRotating()) { + result.postRotate(glm::conjugate(left.getRotation())); + } + if (left.isTranslating() || right.isTranslating()) { + result.postTranslate(right.getTranslation() - left.getTranslation()); + } + if (right.isRotating()) { + result.postRotate(right.getRotation()); + } + if (right.isScaling()) { + result.postScale(right.getScale()); + } + + // HACK: In case of an issue in the Transform multiplication results, to make sure this code is + // working properly uncomment the next 2 lines and compare the results, they should be the same... + // Transform::Mat4 mv = left.getMatrix() * right.getMatrix(); + // Transform::Mat4 mv2 = result.getMatrix(); + + return result; +} + +inline void Transform::updateCache() const { + if (isCacheInvalid()) { + if (isRotating()) { + Mat3 rot = glm::mat3_cast(_rotation); + + if (isScaling()) { + rot[0] *= _scale.x; + rot[1] *= _scale.y; + rot[2] *= _scale.z; + } + + _matrix[0] = Vec4(rot[0], 0.f); + _matrix[1] = Vec4(rot[1], 0.f); + _matrix[2] = Vec4(rot[2], 0.f); + } else { + _matrix[0] = Vec4(_scale.x, 0.f, 0.f, 0.f); + _matrix[1] = Vec4(0.f, _scale.y, 0.f, 0.f); + _matrix[2] = Vec4(0.f, 0.f, _scale.z, 0.f); + } + + _matrix[3] = Vec4(_translation, 1.0f); + validCache(); + } +} + +#endif