From e7a8255af2d17266a842ebdd4abff849231df411 Mon Sep 17 00:00:00 2001 From: Ryan Huffman Date: Fri, 1 May 2015 13:02:28 -0700 Subject: [PATCH 01/15] Replace zoneOverlayManager with internal drawZoneBoundaries --- examples/edit.js | 7 +- examples/libraries/zoneOverlayManager.js | 146 ----------------------- 2 files changed, 2 insertions(+), 151 deletions(-) delete mode 100644 examples/libraries/zoneOverlayManager.js diff --git a/examples/edit.js b/examples/edit.js index e6dd03de96..adc3374eb0 100644 --- a/examples/edit.js +++ b/examples/edit.js @@ -28,7 +28,6 @@ Script.include([ "libraries/gridTool.js", "libraries/entityList.js", "libraries/lightOverlayManager.js", - "libraries/zoneOverlayManager.js", ]); var selectionDisplay = SelectionDisplay; @@ -36,7 +35,6 @@ var selectionManager = SelectionManager; var entityPropertyDialogBox = EntityPropertyDialogBox; var lightOverlayManager = new LightOverlayManager(); -var zoneOverlayManager = new ZoneOverlayManager(); var cameraManager = new CameraManager(); @@ -49,7 +47,6 @@ var entityListTool = EntityListTool(); selectionManager.addEventListener(function() { selectionDisplay.updateHandles(); lightOverlayManager.updatePositions(); - zoneOverlayManager.updatePositions(); }); var windowDimensions = Controller.getViewportDimensions(); @@ -246,7 +243,7 @@ var toolBar = (function () { } toolBar.selectTool(activeButton, isActive); lightOverlayManager.setVisible(isActive && Menu.isOptionChecked(MENU_SHOW_LIGHTS_IN_EDIT_MODE)); - zoneOverlayManager.setVisible(isActive && Menu.isOptionChecked(MENU_SHOW_ZONES_IN_EDIT_MODE)); + Entities.drawZoneBoundaries(isActive && Menu.isOptionChecked(MENU_SHOW_ZONES_IN_EDIT_MODE)); }; // Sets visibility of tool buttons, excluding the power button @@ -1000,7 +997,7 @@ function handeMenuEvent(menuItem) { } else if (menuItem == MENU_SHOW_LIGHTS_IN_EDIT_MODE) { lightOverlayManager.setVisible(isActive && Menu.isOptionChecked(MENU_SHOW_LIGHTS_IN_EDIT_MODE)); } else if (menuItem == MENU_SHOW_ZONES_IN_EDIT_MODE) { - zoneOverlayManager.setVisible(isActive && Menu.isOptionChecked(MENU_SHOW_ZONES_IN_EDIT_MODE)); + Entities.drawZoneBoundaries(isActive && Menu.isOptionChecked(MENU_SHOW_ZONES_IN_EDIT_MODE)); } tooltip.show(false); } diff --git a/examples/libraries/zoneOverlayManager.js b/examples/libraries/zoneOverlayManager.js deleted file mode 100644 index aac3af119b..0000000000 --- a/examples/libraries/zoneOverlayManager.js +++ /dev/null @@ -1,146 +0,0 @@ -ZoneOverlayManager = function(isEntityFunc, entityAddedFunc, entityRemovedFunc, entityMovedFunc) { - var self = this; - - var visible = false; - - // List of all created overlays - var allOverlays = []; - - // List of overlays not currently being used - var unusedOverlays = []; - - // Map from EntityItemID.id to overlay id - var entityOverlays = {}; - - // Map from EntityItemID.id to EntityItemID object - var entityIDs = {}; - - this.updatePositions = function(ids) { - for (var id in entityIDs) { - var entityID = entityIDs[id]; - var properties = Entities.getEntityProperties(entityID); - Overlays.editOverlay(entityOverlays[entityID.id].solid, { - position: properties.position, - rotation: properties.rotation, - dimensions: properties.dimensions, - }); - Overlays.editOverlay(entityOverlays[entityID.id].outline, { - position: properties.position, - rotation: properties.rotation, - dimensions: properties.dimensions, - }); - } - }; - - this.setVisible = function(isVisible) { - if (visible != isVisible) { - visible = isVisible; - for (var id in entityOverlays) { - Overlays.editOverlay(entityOverlays[id].solid, { visible: visible }); - Overlays.editOverlay(entityOverlays[id].outline, { visible: visible }); - } - } - }; - - // Allocate or get an unused overlay - function getOverlay() { - if (unusedOverlays.length == 0) { - var overlay = Overlays.addOverlay("cube", { - }); - allOverlays.push(overlay); - } else { - var overlay = unusedOverlays.pop(); - }; - return overlay; - } - - function releaseOverlay(overlay) { - unusedOverlays.push(overlay); - Overlays.editOverlay(overlay, { visible: false }); - } - - function addEntity(entityID) { - var properties = Entities.getEntityProperties(entityID); - if (properties.type == "Zone" && !(entityID.id in entityOverlays)) { - var overlaySolid = getOverlay(); - var overlayOutline = getOverlay(); - - entityOverlays[entityID.id] = { - solid: overlaySolid, - outline: overlayOutline, - } - entityIDs[entityID.id] = entityID; - - var color = { - red: Math.round(Math.random() * 255), - green: Math.round(Math.random() * 255), - blue: Math.round(Math.random() * 255) - }; - Overlays.editOverlay(overlaySolid, { - position: properties.position, - rotation: properties.rotation, - dimensions: properties.dimensions, - visible: visible, - solid: true, - alpha: 0.1, - color: color, - ignoreRayIntersection: true, - }); - Overlays.editOverlay(overlayOutline, { - position: properties.position, - rotation: properties.rotation, - dimensions: properties.dimensions, - visible: visible, - solid: false, - dashed: false, - lineWidth: 2.0, - alpha: 1.0, - color: color, - ignoreRayIntersection: true, - }); - } - } - - function deleteEntity(entityID) { - if (entityID.id in entityOverlays) { - releaseOverlay(entityOverlays[entityID.id].outline); - releaseOverlay(entityOverlays[entityID.id].solid); - delete entityIDs[entityID.id]; - delete entityOverlays[entityID.id]; - } - } - - function changeEntityID(oldEntityID, newEntityID) { - entityOverlays[newEntityID.id] = entityOverlays[oldEntityID.id]; - entityIDs[newEntityID.id] = newEntityID; - - delete entityOverlays[oldEntityID.id]; - delete entityIDs[oldEntityID.id]; - } - - function clearEntities() { - for (var id in entityOverlays) { - releaseOverlay(entityOverlays[id].outline); - releaseOverlay(entityOverlays[id].solid); - } - entityOverlays = {}; - entityIDs = {}; - } - - Entities.addingEntity.connect(addEntity); - Entities.changingEntityID.connect(changeEntityID); - Entities.deletingEntity.connect(deleteEntity); - Entities.clearingEntities.connect(clearEntities); - - // Add existing entities - var ids = Entities.findEntities(MyAvatar.position, 64000); - for (var i = 0; i < ids.length; i++) { - addEntity(ids[i]); - } - - Script.scriptEnding.connect(function() { - for (var i = 0; i < allOverlays.length; i++) { - Overlays.deleteOverlay(allOverlays[i]); - } - }); -}; From ede42285b1ba1c6d1d2045c0be42ab699e684ab9 Mon Sep 17 00:00:00 2001 From: "Anthony J. Thibault" Date: Sat, 9 May 2015 07:57:29 -0700 Subject: [PATCH 02/15] Improvements to particle entity. * Changed particle geometry to billboarded quads * Added texture support * Added ajt-test.js particle test script. * GeometryCache support for batched quads with texCoords. * Bug fix for infinite loop if _lifetime was very large. * Don't reset the simulation on animation loop. * stop emitting particles on animation stop, but keep simulating until there are no more living particles. * Removed some trailing whitespace --- examples/ajt-test.js | 83 ++++++++++ .../RenderableParticleEffectEntityItem.cpp | 148 ++++++++++++++---- .../src/RenderableParticleEffectEntityItem.h | 9 +- .../entities/src/ParticleEffectEntityItem.cpp | 86 +++++----- .../entities/src/ParticleEffectEntityItem.h | 39 ++--- libraries/render-utils/src/GeometryCache.cpp | 70 +++++++++ libraries/render-utils/src/GeometryCache.h | 1 + 7 files changed, 341 insertions(+), 95 deletions(-) create mode 100644 examples/ajt-test.js diff --git a/examples/ajt-test.js b/examples/ajt-test.js new file mode 100644 index 0000000000..a908978a02 --- /dev/null +++ b/examples/ajt-test.js @@ -0,0 +1,83 @@ + +(function () { + var spawnPoint = Vec3.sum(MyAvatar.position, Vec3.multiply(3, Quat.getFront(Camera.getOrientation()))); + + // constructor + function TestBox() { + this.entity = Entities.addEntity({ type: "Box", + position: spawnPoint, + dimentions: { x: 1, y: 1, z: 1 }, + color: { red: 100, green: 100, blue: 255 }, + gravity: { x: 0, y: 0, z: 0 }, + visible: true, + locked: false, + lifetime: 6000 }); + var self = this; + this.timer = Script.setInterval(function () { + var colorProp = { color: { red: Math.random() * 255, + green: Math.random() * 255, + blue: Math.random() * 255 } }; + Entities.editEntity(self.entity, colorProp); + }, 1000); + } + + TestBox.prototype.Destroy = function () { + Script.clearInterval(this.timer); + Entities.editEntity(this.entity, { locked: false }); + Entities.deleteEntity(this.entity); + } + + // constructor + function TestFx() { + var animationSettings = JSON.stringify({ fps: 30, + frameIndex: 0, + running: true, + firstFrame: 0, + lastFrame: 30, + loop: false }); + + this.entity = Entities.addEntity({ type: "ParticleEffect", + animationSettings: animationSettings, + position: spawnPoint, + textures: "http://www.hyperlogic.org/images/particle.png", + emitRate: 30, + emitStrength: 3, + color: { red: 128, green: 255, blue: 255 }, + visible: true, + locked: false }); + + var self = this; + this.timer = Script.setInterval(function () { + print("AJT: setting animation props"); + var animProp = { animationFrameIndex: 0, + animationIsPlaying: true }; + Entities.editEntity(self.entity, animProp); + }, 2000); + + } + + TestFx.prototype.Destroy = function () { + Script.clearInterval(this.timer); + Entities.editEntity(this.entity, { locked: false }); + Entities.deleteEntity(this.entity); + } + + var objs = []; + function Init() { + objs.push(new TestBox()); + objs.push(new TestFx()); + } + + function ShutDown() { + var i, len = entities.length; + for (i = 0; i < len; i++) { + objs[i].Destroy(); + } + objs = []; + } + + Init(); + Script.scriptEnding.connect(ShutDown); +})(); + + diff --git a/libraries/entities-renderer/src/RenderableParticleEffectEntityItem.cpp b/libraries/entities-renderer/src/RenderableParticleEffectEntityItem.cpp index b4dd5b4c64..32fd06c837 100644 --- a/libraries/entities-renderer/src/RenderableParticleEffectEntityItem.cpp +++ b/libraries/entities-renderer/src/RenderableParticleEffectEntityItem.cpp @@ -16,6 +16,7 @@ #include #include #include +#include "EntitiesRendererLogging.h" #include "RenderableParticleEffectEntityItem.h" @@ -29,62 +30,155 @@ RenderableParticleEffectEntityItem::RenderableParticleEffectEntityItem(const Ent } void RenderableParticleEffectEntityItem::render(RenderArgs* args) { - PerformanceTimer perfTimer("RenderableParticleEffectEntityItem::render"); + assert(getType() == EntityTypes::ParticleEffect); + PerformanceTimer perfTimer("RenderableParticleEffectEntityItem::render"); + + if (_texturesChangedFlag) + { + if (_textures.isEmpty()) { + _texture.clear(); + } else { + // for now use the textures string directly. + // Eventually we'll want multiple textures in a map or array. + _texture = DependencyManager::get()->getTexture(_textures); + } + _texturesChangedFlag = false; + } + + if (!_texture) { + renderUntexturedQuads(args); + } else if (_texture && !_texture->isLoaded()) { + renderUntexturedQuads(args); + } else { + renderTexturedQuads(args); + } +}; + +void RenderableParticleEffectEntityItem::renderUntexturedQuads(RenderArgs* args) { + float pa_rad = getParticleRadius(); const float MAX_COLOR = 255.0f; glm::vec4 paColor(getColor()[RED_INDEX] / MAX_COLOR, getColor()[GREEN_INDEX] / MAX_COLOR, getColor()[BLUE_INDEX] / MAX_COLOR, getLocalRenderAlpha()); - // Right now we're just iterating over particles and rendering as a cross of four quads. - // This is pretty dumb, it was quick enough to code up. Really, there should be many - // rendering modes, including the all-important textured billboards. + glm::vec3 up_offset = args->_viewFrustum->getUp() * pa_rad; + glm::vec3 right_offset = args->_viewFrustum->getRight() * pa_rad; QVector* pointVec = new QVector(_paCount * VERTS_PER_PARTICLE); + quint32 paCount = 0; quint32 paIter = _paHead; - while (_paLife[paIter] > 0.0f) { + while (_paLife[paIter] > 0.0f && paCount < _maxParticles) { int j = paIter * XYZ_STRIDE; - pointVec->append(glm::vec3(_paPosition[j] - pa_rad, _paPosition[j + 1] + pa_rad, _paPosition[j + 2])); - pointVec->append(glm::vec3(_paPosition[j] + pa_rad, _paPosition[j + 1] + pa_rad, _paPosition[j + 2])); - pointVec->append(glm::vec3(_paPosition[j] + pa_rad, _paPosition[j + 1] - pa_rad, _paPosition[j + 2])); - pointVec->append(glm::vec3(_paPosition[j] - pa_rad, _paPosition[j + 1] - pa_rad, _paPosition[j + 2])); + glm::vec3 pos(_paPosition[j], _paPosition[j + 1], _paPosition[j + 2]); - pointVec->append(glm::vec3(_paPosition[j] + pa_rad, _paPosition[j + 1] + pa_rad, _paPosition[j + 2])); - pointVec->append(glm::vec3(_paPosition[j] - pa_rad, _paPosition[j + 1] + pa_rad, _paPosition[j + 2])); - pointVec->append(glm::vec3(_paPosition[j] - pa_rad, _paPosition[j + 1] - pa_rad, _paPosition[j + 2])); - pointVec->append(glm::vec3(_paPosition[j] + pa_rad, _paPosition[j + 1] - pa_rad, _paPosition[j + 2])); - - pointVec->append(glm::vec3(_paPosition[j], _paPosition[j + 1] + pa_rad, _paPosition[j + 2] - pa_rad)); - pointVec->append(glm::vec3(_paPosition[j], _paPosition[j + 1] + pa_rad, _paPosition[j + 2] + pa_rad)); - pointVec->append(glm::vec3(_paPosition[j], _paPosition[j + 1] - pa_rad, _paPosition[j + 2] + pa_rad)); - pointVec->append(glm::vec3(_paPosition[j], _paPosition[j + 1] - pa_rad, _paPosition[j + 2] - pa_rad)); - - pointVec->append(glm::vec3(_paPosition[j], _paPosition[j + 1] + pa_rad, _paPosition[j + 2] + pa_rad)); - pointVec->append(glm::vec3(_paPosition[j], _paPosition[j + 1] + pa_rad, _paPosition[j + 2] - pa_rad)); - pointVec->append(glm::vec3(_paPosition[j], _paPosition[j + 1] - pa_rad, _paPosition[j + 2] - pa_rad)); - pointVec->append(glm::vec3(_paPosition[j], _paPosition[j + 1] - pa_rad, _paPosition[j + 2] + pa_rad)); + pointVec->append(pos - right_offset + up_offset); + pointVec->append(pos + right_offset + up_offset); + pointVec->append(pos + right_offset - up_offset); + pointVec->append(pos - right_offset - up_offset); paIter = (paIter + 1) % _maxParticles; + paCount++; } DependencyManager::get()->updateVertices(_cacheID, *pointVec, paColor); - + glPushMatrix(); glm::vec3 position = getPosition(); glTranslatef(position.x, position.y, position.z); glm::quat rotation = getRotation(); glm::vec3 axis = glm::axis(rotation); glRotatef(glm::degrees(glm::angle(rotation)), axis.x, axis.y, axis.z); - + glPushMatrix(); glm::vec3 positionToCenter = getCenter() - position; glTranslatef(positionToCenter.x, positionToCenter.y, positionToCenter.z); - + DependencyManager::get()->renderVertices(gpu::QUADS, _cacheID); glPopMatrix(); glPopMatrix(); delete pointVec; -}; +} + +static glm::vec3 s_dir; +static bool zSort(const glm::vec3& rhs, const glm::vec3& lhs) { + return glm::dot(rhs, s_dir) > glm::dot(lhs, s_dir); +} + +void RenderableParticleEffectEntityItem::renderTexturedQuads(RenderArgs* args) { + + float pa_rad = getParticleRadius(); + + const float MAX_COLOR = 255.0f; + glm::vec4 paColor(getColor()[RED_INDEX] / MAX_COLOR, getColor()[GREEN_INDEX] / MAX_COLOR, + getColor()[BLUE_INDEX] / MAX_COLOR, getLocalRenderAlpha()); + + QVector* posVec = new QVector(_paCount); + quint32 paCount = 0; + quint32 paIter = _paHead; + while (_paLife[paIter] > 0.0f && paCount < _maxParticles) { + int j = paIter * XYZ_STRIDE; + posVec->append(glm::vec3(_paPosition[j], _paPosition[j + 1], _paPosition[j + 2])); + + paIter = (paIter + 1) % _maxParticles; + paCount++; + } + + // sort particles back to front + qSort(posVec->begin(), posVec->end(), zSort); + + QVector* vertVec = new QVector(_paCount * VERTS_PER_PARTICLE); + QVector* texCoordVec = new QVector(_paCount * VERTS_PER_PARTICLE); + + s_dir = args->_viewFrustum->getDirection(); + glm::vec3 up_offset = args->_viewFrustum->getUp() * pa_rad; + glm::vec3 right_offset = args->_viewFrustum->getRight() * pa_rad; + + for (int i = 0; i < posVec->size(); i++) { + glm::vec3 pos = posVec->at(i); + + vertVec->append(pos - right_offset + up_offset); + vertVec->append(pos + right_offset + up_offset); + vertVec->append(pos + right_offset - up_offset); + vertVec->append(pos - right_offset - up_offset); + + texCoordVec->append(glm::vec2(0, 1)); + texCoordVec->append(glm::vec2(1, 1)); + texCoordVec->append(glm::vec2(1, 0)); + texCoordVec->append(glm::vec2(0, 0)); + } + + DependencyManager::get()->updateVertices(_cacheID, *vertVec, *texCoordVec, paColor); + + glEnable(GL_TEXTURE_2D); + glBindTexture(GL_TEXTURE_2D, _texture->getID()); + + glPushMatrix(); + + glm::vec3 position = getPosition(); + glTranslatef(position.x, position.y, position.z); + glm::quat rotation = getRotation(); + glm::vec3 axis = glm::axis(rotation); + glRotatef(glm::degrees(glm::angle(rotation)), axis.x, axis.y, axis.z); + + glPushMatrix(); + + glm::vec3 positionToCenter = getCenter() - position; + glTranslatef(positionToCenter.x, positionToCenter.y, positionToCenter.z); + + DependencyManager::get()->renderVertices(gpu::QUADS, _cacheID); + + glPopMatrix(); + glPopMatrix(); + + glDisable(GL_TEXTURE_2D); + glBindTexture(GL_TEXTURE_2D, 0); + + delete posVec; + delete vertVec; + delete texCoordVec; +} + diff --git a/libraries/entities-renderer/src/RenderableParticleEffectEntityItem.h b/libraries/entities-renderer/src/RenderableParticleEffectEntityItem.h index 74b29574c3..25bbe5c147 100644 --- a/libraries/entities-renderer/src/RenderableParticleEffectEntityItem.h +++ b/libraries/entities-renderer/src/RenderableParticleEffectEntityItem.h @@ -12,6 +12,7 @@ #define hifi_RenderableParticleEffectEntityItem_h #include +#include class RenderableParticleEffectEntityItem : public ParticleEffectEntityItem { public: @@ -19,9 +20,15 @@ public: RenderableParticleEffectEntityItem(const EntityItemID& entityItemID, const EntityItemProperties& properties); virtual void render(RenderArgs* args); + void renderUntexturedQuads(RenderArgs* args); + void renderTexturedQuads(RenderArgs* args); + protected: + int _cacheID; - const int VERTS_PER_PARTICLE = 16; + const int VERTS_PER_PARTICLE = 4; + + NetworkTexturePointer _texture; }; diff --git a/libraries/entities/src/ParticleEffectEntityItem.cpp b/libraries/entities/src/ParticleEffectEntityItem.cpp index 45312f465b..fc4927fdcd 100644 --- a/libraries/entities/src/ParticleEffectEntityItem.cpp +++ b/libraries/entities/src/ParticleEffectEntityItem.cpp @@ -18,12 +18,8 @@ // - Add controls for spread (which is currently hard-coded) and varying emission strength (not currently implemented). // - Add drag. // - Add some kind of support for collisions. -// - For simplicity, I'm currently just rendering each particle as a cross of four axis-aligned quads. Really, we'd -// want multiple render modes, including (the most important) textured billboards (always facing camera). Also, these -// should support animated textures. // - There's no synchronization of the simulation across clients at all. In fact, it's using rand() under the hood, so // there's no gaurantee that different clients will see simulations that look anything like the other. -// - MORE? // // Created by Jason Rickwald on 3/2/15. // @@ -55,7 +51,7 @@ const glm::vec3 ParticleEffectEntityItem::DEFAULT_EMIT_DIRECTION(0.0f, 1.0f, 0.0 const float ParticleEffectEntityItem::DEFAULT_EMIT_STRENGTH = 25.0f; const float ParticleEffectEntityItem::DEFAULT_LOCAL_GRAVITY = -9.8f; const float ParticleEffectEntityItem::DEFAULT_PARTICLE_RADIUS = 0.025f; - +const QString ParticleEffectEntityItem::DEFAULT_TEXTURES = ""; EntityItem* ParticleEffectEntityItem::factory(const EntityItemID& entityID, const EntityItemProperties& properties) { return new ParticleEffectEntityItem(entityID, properties); @@ -72,6 +68,7 @@ ParticleEffectEntityItem::ParticleEffectEntityItem(const EntityItemID& entityIte _emitStrength = DEFAULT_EMIT_STRENGTH; _localGravity = DEFAULT_LOCAL_GRAVITY; _particleRadius = DEFAULT_PARTICLE_RADIUS; + _textures = DEFAULT_TEXTURES; setProperties(properties); // this is a pretty dumb thing to do, and it should probably be changed to use a more dynamic // data structure in the future. I'm just trying to get some code out the door for now (and it's @@ -96,7 +93,7 @@ ParticleEffectEntityItem::~ParticleEffectEntityItem() { EntityItemProperties ParticleEffectEntityItem::getProperties() const { EntityItemProperties properties = EntityItem::getProperties(); // get the properties from our base class - + COPY_ENTITY_PROPERTY_TO_PROPERTIES(color, getXColor); COPY_ENTITY_PROPERTY_TO_PROPERTIES(animationIsPlaying, getAnimationIsPlaying); COPY_ENTITY_PROPERTY_TO_PROPERTIES(animationFrameIndex, getAnimationFrameIndex); @@ -111,6 +108,7 @@ EntityItemProperties ParticleEffectEntityItem::getProperties() const { COPY_ENTITY_PROPERTY_TO_PROPERTIES(emitStrength, getEmitStrength); COPY_ENTITY_PROPERTY_TO_PROPERTIES(localGravity, getLocalGravity); COPY_ENTITY_PROPERTY_TO_PROPERTIES(particleRadius, getParticleRadius); + COPY_ENTITY_PROPERTY_TO_PROPERTIES(textures, getTextures); return properties; } @@ -132,6 +130,7 @@ bool ParticleEffectEntityItem::setProperties(const EntityItemProperties& propert SET_ENTITY_PROPERTY_FROM_PROPERTIES(emitStrength, setEmitStrength); SET_ENTITY_PROPERTY_FROM_PROPERTIES(localGravity, setLocalGravity); SET_ENTITY_PROPERTY_FROM_PROPERTIES(particleRadius, setParticleRadius); + SET_ENTITY_PROPERTY_FROM_PROPERTIES(textures, setTextures); if (somethingChanged) { bool wantDebug = false; @@ -186,6 +185,7 @@ int ParticleEffectEntityItem::readEntitySubclassDataFromBuffer(const unsigned ch READ_ENTITY_PROPERTY(PROP_EMIT_STRENGTH, float, _emitStrength); READ_ENTITY_PROPERTY(PROP_LOCAL_GRAVITY, float, _localGravity); READ_ENTITY_PROPERTY(PROP_PARTICLE_RADIUS, float, _particleRadius); + READ_ENTITY_PROPERTY(PROP_TEXTURES, QString, _textures); return bytesRead; } @@ -194,7 +194,7 @@ int ParticleEffectEntityItem::readEntitySubclassDataFromBuffer(const unsigned ch // TODO: eventually only include properties changed since the params.lastViewFrustumSent time EntityPropertyFlags ParticleEffectEntityItem::getEntityProperties(EncodeBitstreamParams& params) const { EntityPropertyFlags requestedProperties = EntityItem::getEntityProperties(params); - + requestedProperties += PROP_COLOR; requestedProperties += PROP_ANIMATION_FPS; requestedProperties += PROP_ANIMATION_FRAME_INDEX; @@ -208,6 +208,7 @@ EntityPropertyFlags ParticleEffectEntityItem::getEntityProperties(EncodeBitstrea requestedProperties += PROP_EMIT_STRENGTH; requestedProperties += PROP_LOCAL_GRAVITY; requestedProperties += PROP_PARTICLE_RADIUS; + requestedProperties += PROP_TEXTURES; return requestedProperties; } @@ -234,11 +235,12 @@ void ParticleEffectEntityItem::appendSubclassData(OctreePacketData* packetData, APPEND_ENTITY_PROPERTY(PROP_EMIT_STRENGTH, appendValue, getEmitStrength()); APPEND_ENTITY_PROPERTY(PROP_LOCAL_GRAVITY, appendValue, getLocalGravity()); APPEND_ENTITY_PROPERTY(PROP_PARTICLE_RADIUS, appendValue, getParticleRadius()); + APPEND_ENTITY_PROPERTY(PROP_TEXTURES, appendValue, getTextures()); } bool ParticleEffectEntityItem::isAnimatingSomething() const { - return getAnimationIsPlaying() && - getAnimationFPS() != 0.0f; + // keep animating if there are particles still alive. + return (getAnimationIsPlaying() || _paCount > 0) && getAnimationFPS() != 0.0f; } bool ParticleEffectEntityItem::needsToCallUpdate() const { @@ -246,33 +248,25 @@ bool ParticleEffectEntityItem::needsToCallUpdate() const { } void ParticleEffectEntityItem::update(const quint64& now) { + + float deltaTime = (float)(now - _lastAnimated) / (float)USECS_PER_SECOND; + _lastAnimated = now; + // only advance the frame index if we're playing if (getAnimationIsPlaying()) { - float deltaTime = (float)(now - _lastAnimated) / (float)USECS_PER_SECOND; - _lastAnimated = now; - float lastFrame = _animationLoop.getFrameIndex(); _animationLoop.simulate(deltaTime); - float curFrame = _animationLoop.getFrameIndex(); - if (curFrame > lastFrame) { - stepSimulation(deltaTime); - } - else if (curFrame < lastFrame) { - // we looped around, so restart the sim and only sim up to the point - // since the beginning of the frame range. - resetSimulation(); - stepSimulation((curFrame - _animationLoop.getFirstFrame()) / _animationLoop.getFPS()); - } - } - else { - _lastAnimated = now; } - // update the dimensions - glm::vec3 dims; - dims.x = glm::max(glm::abs(_paXmin), glm::abs(_paXmax)) * 2.0f; - dims.y = glm::max(glm::abs(_paYmin), glm::abs(_paYmax)) * 2.0f; - dims.z = glm::max(glm::abs(_paZmin), glm::abs(_paZmax)) * 2.0f; - setDimensions(dims); + if (isAnimatingSomething()) { + stepSimulation(deltaTime); + + // update the dimensions + glm::vec3 dims; + dims.x = glm::max(glm::abs(_paXmin), glm::abs(_paXmax)) * 2.0f; + dims.y = glm::max(glm::abs(_paYmin), glm::abs(_paYmax)) * 2.0f; + dims.z = glm::max(glm::abs(_paZmin), glm::abs(_paZmax)) * 2.0f; + setDimensions(dims); + } EntityItem::update(now); // let our base class handle it's updates... } @@ -422,9 +416,12 @@ void ParticleEffectEntityItem::stepSimulation(float deltaTime) { _paXmin = _paYmin = _paZmin = -1.0f; _paXmax = _paYmax = _paZmax = 1.0f; + float oneHalfATSquared = 0.5f * _localGravity * deltaTime * deltaTime; + // update particles + quint32 updateCount = 0; quint32 updateIter = _paHead; - while (_paLife[updateIter] > 0.0f) { + while (_paLife[updateIter] > 0.0f && updateCount < _maxParticles) { _paLife[updateIter] -= deltaTime; if (_paLife[updateIter] <= 0.0f) { _paLife[updateIter] = -1.0f; @@ -432,10 +429,10 @@ void ParticleEffectEntityItem::stepSimulation(float deltaTime) { _paCount--; } else { - // DUMB FORWARD EULER just to get it done int j = updateIter * XYZ_STRIDE; + _paPosition[j] += _paVelocity[j] * deltaTime; - _paPosition[j+1] += _paVelocity[j+1] * deltaTime; + _paPosition[j+1] += _paVelocity[j+1] * deltaTime + oneHalfATSquared; _paPosition[j+2] += _paVelocity[j+2] * deltaTime; _paXmin = glm::min(_paXmin, _paPosition[j]); @@ -449,29 +446,37 @@ void ParticleEffectEntityItem::stepSimulation(float deltaTime) { _paVelocity[j + 1] += deltaTime * _localGravity; } updateIter = (updateIter + 1) % _maxParticles; + updateCount++; } - // emit new particles quint32 emitIdx = updateIter; - _partialEmit += ((float)_emitRate) * deltaTime; - quint32 birthed = (quint32)_partialEmit; - _partialEmit -= (float)birthed; + quint32 birthed = 0; glm::vec3 randOffset; + // emit new particles, but only if animaiton is playing + if (getAnimationIsPlaying()) { + _partialEmit += ((float)_emitRate) * deltaTime; + birthed = (quint32)_partialEmit; + _partialEmit -= (float)birthed; + } else { + _partialEmit = 0; + } + for (quint32 i = 0; i < birthed; i++) { if (_paLife[emitIdx] < 0.0f) { int j = emitIdx * XYZ_STRIDE; _paLife[emitIdx] = _lifespan; + randOffset.x = (((double)rand() / (double)RAND_MAX) - 0.5) * 0.25 * _emitStrength; randOffset.y = (((double)rand() / (double)RAND_MAX) - 0.5) * 0.25 * _emitStrength; randOffset.z = (((double)rand() / (double)RAND_MAX) - 0.5) * 0.25 * _emitStrength; + _paVelocity[j] = (_emitDirection.x * _emitStrength) + randOffset.x; _paVelocity[j + 1] = (_emitDirection.y * _emitStrength) + randOffset.y; _paVelocity[j + 2] = (_emitDirection.z * _emitStrength) + randOffset.z; - // DUMB FORWARD EULER just to get it done _paPosition[j] += _paVelocity[j] * deltaTime; - _paPosition[j + 1] += _paVelocity[j + 1] * deltaTime; + _paPosition[j + 1] += _paVelocity[j + 1] * deltaTime + oneHalfATSquared; _paPosition[j + 2] += _paVelocity[j + 2] * deltaTime; _paXmin = glm::min(_paXmin, _paPosition[j]); @@ -510,4 +515,3 @@ void ParticleEffectEntityItem::resetSimulation() { srand(_randSeed); } - diff --git a/libraries/entities/src/ParticleEffectEntityItem.h b/libraries/entities/src/ParticleEffectEntityItem.h index b00eb94685..3533ce84e7 100644 --- a/libraries/entities/src/ParticleEffectEntityItem.h +++ b/libraries/entities/src/ParticleEffectEntityItem.h @@ -2,29 +2,6 @@ // ParticleEffectEntityItem.h // libraries/entities/src // -// Some starter code for a particle simulation entity, which could ideally be used for a variety of effects. -// This is some really early and rough stuff here. It was enough for now to just get it up and running in the interface. -// -// Todo's and other notes: -// - The simulation should restart when the AnimationLoop's max frame is reached (or passed), but there doesn't seem -// to be a good way to set that max frame to something reasonable right now. -// - There seems to be a bug whereby entities on the edge of screen will just pop off or on. This is probably due -// to my lack of understanding of how entities in the octree are picked for rendering. I am updating the entity -// dimensions based on the bounds of the sim, but maybe I need to update a dirty flag or something. -// - This should support some kind of pre-roll of the simulation. -// - Just to get this out the door, I just did forward Euler integration. There are better ways. -// - Gravity always points along the Y axis. Support an actual gravity vector. -// - Add the ability to add arbitrary forces to the simulation. -// - Add controls for spread (which is currently hard-coded) and varying emission strength (not currently implemented). -// - Add drag. -// - Add some kind of support for collisions. -// - For simplicity, I'm currently just rendering each particle as a cross of four axis-aligned quads. Really, we'd -// want multiple render modes, including (the most important) textured billboards (always facing camera). Also, these -// should support animated textures. -// - There's no synchronization of the simulation across clients at all. In fact, it's using rand() under the hood, so -// there's no gaurantee that different clients will see simulations that look anything like the other. -// - MORE? -// // Created by Jason Rickwald on 3/2/15. // // Distributed under the Apache License, Version 2.0. @@ -39,14 +16,14 @@ class ParticleEffectEntityItem : public EntityItem { public: - + static EntityItem* factory(const EntityItemID& entityID, const EntityItemProperties& properties); ParticleEffectEntityItem(const EntityItemID& entityItemID, const EntityItemProperties& properties); virtual ~ParticleEffectEntityItem(); ALLOW_INSTANTIATION // This class can be instantiated - + // methods for getting/setting all properties of this entity virtual EntityItemProperties getProperties() const; virtual bool setProperties(const EntityItemProperties& properties); @@ -141,6 +118,15 @@ public: float getAnimationFPS() const { return _animationLoop.getFPS(); } QString getAnimationSettings() const; + static const QString DEFAULT_TEXTURES; + const QString& getTextures() const { return _textures; } + void setTextures(const QString& textures) { + if (_textures != textures) { + _textures = textures; + _texturesChangedFlag = true; + } + } + protected: bool isAnimatingSomething() const; @@ -159,6 +145,8 @@ protected: quint64 _lastAnimated; AnimationLoop _animationLoop; QString _animationSettings; + QString _textures; + bool _texturesChangedFlag; ShapeType _shapeType = SHAPE_TYPE_NONE; // all the internals of running the particle sim @@ -180,4 +168,3 @@ protected: }; #endif // hifi_ParticleEffectEntityItem_h - diff --git a/libraries/render-utils/src/GeometryCache.cpp b/libraries/render-utils/src/GeometryCache.cpp index 61d92ccc17..770717edbe 100644 --- a/libraries/render-utils/src/GeometryCache.cpp +++ b/libraries/render-utils/src/GeometryCache.cpp @@ -721,6 +721,76 @@ void GeometryCache::updateVertices(int id, const QVector& points, con #endif } +void GeometryCache::updateVertices(int id, const QVector& points, const QVector& texCoords, const glm::vec4& color) { + BatchItemDetails& details = _registeredVertices[id]; + + if (details.isCreated) { + details.clear(); + #ifdef WANT_DEBUG + qCDebug(renderutils) << "updateVertices()... RELEASING REGISTERED"; + #endif // def WANT_DEBUG + } + + const int FLOATS_PER_VERTEX = 5; + details.isCreated = true; + details.vertices = points.size(); + details.vertexSize = FLOATS_PER_VERTEX; + + gpu::BufferPointer verticesBuffer(new gpu::Buffer()); + gpu::BufferPointer colorBuffer(new gpu::Buffer()); + gpu::Stream::FormatPointer streamFormat(new gpu::Stream::Format()); + gpu::BufferStreamPointer stream(new gpu::BufferStream()); + + details.verticesBuffer = verticesBuffer; + details.colorBuffer = colorBuffer; + details.streamFormat = streamFormat; + details.stream = stream; + + details.streamFormat->setAttribute(gpu::Stream::POSITION, 0, gpu::Element(gpu::VEC3, gpu::FLOAT, gpu::XYZ), 0); + details.streamFormat->setAttribute(gpu::Stream::TEXCOORD, 0, gpu::Element(gpu::VEC2, gpu::FLOAT, gpu::UV), 3 * sizeof(float)); + details.streamFormat->setAttribute(gpu::Stream::COLOR, 1, gpu::Element(gpu::VEC4, gpu::UINT8, gpu::RGBA)); + + details.stream->addBuffer(details.verticesBuffer, 0, details.streamFormat->getChannels().at(0)._stride); + details.stream->addBuffer(details.colorBuffer, 0, details.streamFormat->getChannels().at(1)._stride); + + assert(points.size() == texCoords.size()); + + details.vertices = points.size(); + details.vertexSize = FLOATS_PER_VERTEX; + + int compactColor = ((int(color.x * 255.0f) & 0xFF)) | + ((int(color.y * 255.0f) & 0xFF) << 8) | + ((int(color.z * 255.0f) & 0xFF) << 16) | + ((int(color.w * 255.0f) & 0xFF) << 24); + + GLfloat* vertexData = new GLfloat[details.vertices * FLOATS_PER_VERTEX]; + GLfloat* vertex = vertexData; + + int* colorData = new int[details.vertices]; + int* colorDataAt = colorData; + + for (int i = 0; i < points.size(); i++) { + glm::vec3 point = points[i]; + glm::vec2 texCoord = texCoords[i]; + *(vertex++) = point.x; + *(vertex++) = point.y; + *(vertex++) = point.z; + *(vertex++) = texCoord.x; + *(vertex++) = texCoord.y; + + *(colorDataAt++) = compactColor; + } + + details.verticesBuffer->append(sizeof(GLfloat) * FLOATS_PER_VERTEX * details.vertices, (gpu::Byte*) vertexData); + details.colorBuffer->append(sizeof(int) * details.vertices, (gpu::Byte*) colorData); + delete[] vertexData; + delete[] colorData; + + #ifdef WANT_DEBUG + qCDebug(renderutils) << "new registered linestrip buffer made -- _registeredVertices.size():" << _registeredVertices.size(); + #endif +} + void GeometryCache::renderVertices(gpu::Primitive primitiveType, int id) { BatchItemDetails& details = _registeredVertices[id]; if (details.isCreated) { diff --git a/libraries/render-utils/src/GeometryCache.h b/libraries/render-utils/src/GeometryCache.h index 8ccbb65dbb..672e4ddb78 100644 --- a/libraries/render-utils/src/GeometryCache.h +++ b/libraries/render-utils/src/GeometryCache.h @@ -198,6 +198,7 @@ public: void updateVertices(int id, const QVector& points, const glm::vec4& color); void updateVertices(int id, const QVector& points, const glm::vec4& color); + void updateVertices(int id, const QVector& points, const QVector& texCoords, const glm::vec4& color); void renderVertices(gpu::Primitive primitiveType, int id); /// Loads geometry from the specified URL. From 28bacb4d82672666677cc8cce76c5722ace0d150 Mon Sep 17 00:00:00 2001 From: ZappoMan Date: Mon, 11 May 2015 13:26:52 -0700 Subject: [PATCH 03/15] add center in zone button to edit.js --- examples/edit.js | 19 +++++++++++++++++++ examples/html/entityProperties.html | 11 +++++++++++ 2 files changed, 30 insertions(+) diff --git a/examples/edit.js b/examples/edit.js index 64050d92aa..9c7db22eca 100644 --- a/examples/edit.js +++ b/examples/edit.js @@ -1295,6 +1295,25 @@ PropertiesTool = function(opts) { pushCommandForSelections(); selectionManager._update(); } + } else if (data.action == "centerAtmosphereToZone") { + if (selectionManager.hasSelection()) { + selectionManager.saveProperties(); + for (var i = 0; i < selectionManager.selections.length; i++) { + var properties = selectionManager.savedProperties[selectionManager.selections[i].id]; + if (properties.type == "Zone") { + var centerOfZone = properties.boundingBox.center; + var atmosphereCenter = { x: centerOfZone.x, + y: centerOfZone.y - properties.atmosphere.innerRadius, + z: centerOfZone.z }; + + Entities.editEntity(selectionManager.selections[i], { + atmosphere: { center: atmosphereCenter }, + }); + } + } + pushCommandForSelections(); + selectionManager._update(); + } } } }); diff --git a/examples/html/entityProperties.html b/examples/html/entityProperties.html index 56fc84ef96..4247a5e6cb 100644 --- a/examples/html/entityProperties.html +++ b/examples/html/entityProperties.html @@ -300,6 +300,8 @@ var elZoneAtmosphereCenterX = document.getElementById("property-zone-atmosphere-center-x"); var elZoneAtmosphereCenterY = document.getElementById("property-zone-atmosphere-center-y"); var elZoneAtmosphereCenterZ = document.getElementById("property-zone-atmosphere-center-z"); + var elCenterAtmosphereToZone = document.getElementById("center-atmosphere-in-zone"); + var elZoneAtmosphereInnerRadius = document.getElementById("property-zone-atmosphere-inner-radius"); var elZoneAtmosphereOuterRadius = document.getElementById("property-zone-atmosphere-outer-radius"); var elZoneAtmosphereMieScattering = document.getElementById("property-zone-atmosphere-mie-scattering"); @@ -705,6 +707,12 @@ percentage: parseInt(elRescaleDimensionsPct.value), })); }); + elCenterAtmosphereToZone.addEventListener("click", function() { + EventBridge.emitWebEvent(JSON.stringify({ + type: "action", + action: "centerAtmosphereToZone", + })); + }); window.onblur = function() { // Fake a change event @@ -1156,6 +1164,9 @@
X
Y
Z
+
+ +
From e58976fe5a1c4c311643c9d7c3af72dd4a935750 Mon Sep 17 00:00:00 2001 From: Brad Davis Date: Mon, 11 May 2015 13:52:36 -0700 Subject: [PATCH 04/15] Fix magnifier location --- interface/src/ui/ApplicationOverlay.cpp | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/interface/src/ui/ApplicationOverlay.cpp b/interface/src/ui/ApplicationOverlay.cpp index 6d9701ded7..b65e75923e 100644 --- a/interface/src/ui/ApplicationOverlay.cpp +++ b/interface/src/ui/ApplicationOverlay.cpp @@ -1226,19 +1226,17 @@ glm::vec2 ApplicationOverlay::sphericalToOverlay(const glm::vec2& sphericalPos) result /= _textureFov; result.x /= _textureAspectRatio; result += 0.5f; - result.x = (-sphericalPos.x / (_textureFov * _textureAspectRatio) + 0.5f); - result.y = (sphericalPos.y / _textureFov + 0.5f); result *= qApp->getCanvasSize(); return result; } glm::vec2 ApplicationOverlay::overlayToSpherical(const glm::vec2& overlayPos) const { glm::vec2 result = overlayPos; - result.x *= -1.0; result /= qApp->getCanvasSize(); result -= 0.5f; result *= _textureFov; result.x *= _textureAspectRatio; + result.x *= -1.0f; return result; } From 73428ec12d15bb2af8182d620338471cf3de6807 Mon Sep 17 00:00:00 2001 From: ZappoMan Date: Mon, 11 May 2015 14:21:58 -0700 Subject: [PATCH 05/15] add support to not displace very large clipboards on import --- examples/edit.js | 7 +++++-- .../scripting/ClipboardScriptingInterface.cpp | 4 ++++ .../scripting/ClipboardScriptingInterface.h | 1 + libraries/entities/src/EntityTree.cpp | 21 +++++++++++++++++++ libraries/entities/src/EntityTree.h | 2 ++ libraries/entities/src/EntityTreeElement.cpp | 10 +++++++++ libraries/entities/src/EntityTreeElement.h | 2 ++ libraries/shared/src/Extents.cpp | 6 ++++++ libraries/shared/src/Extents.h | 6 ++++++ 9 files changed, 57 insertions(+), 2 deletions(-) diff --git a/examples/edit.js b/examples/edit.js index 9c7db22eca..1bc4a34393 100644 --- a/examples/edit.js +++ b/examples/edit.js @@ -1030,8 +1030,11 @@ function importSVO(importURL) { var success = Clipboard.importEntities(importURL); if (success) { - var position = getPositionToCreateEntity(); - + var VERY_LARGE = 10000; + var position = { x: 0, y: 0, z: 0}; + if (Clipboard.getClipboardContentsLargestDimension() < VERY_LARGE) { + position = getPositionToCreateEntity(); + } var pastedEntityIDs = Clipboard.pasteEntities(position); if (isActive) { diff --git a/interface/src/scripting/ClipboardScriptingInterface.cpp b/interface/src/scripting/ClipboardScriptingInterface.cpp index 616fc5f141..f833ad43cc 100644 --- a/interface/src/scripting/ClipboardScriptingInterface.cpp +++ b/interface/src/scripting/ClipboardScriptingInterface.cpp @@ -14,6 +14,10 @@ ClipboardScriptingInterface::ClipboardScriptingInterface() { } +float ClipboardScriptingInterface::getClipboardContentsLargestDimension() { + return Application::getInstance()->getEntityClipboard()->getContentsLargestDimension(); +} + bool ClipboardScriptingInterface::exportEntities(const QString& filename, const QVector& entityIDs) { return Application::getInstance()->exportEntities(filename, entityIDs); } diff --git a/interface/src/scripting/ClipboardScriptingInterface.h b/interface/src/scripting/ClipboardScriptingInterface.h index 92204af60d..73bebd4836 100644 --- a/interface/src/scripting/ClipboardScriptingInterface.h +++ b/interface/src/scripting/ClipboardScriptingInterface.h @@ -22,6 +22,7 @@ signals: void readyToImport(); public slots: + float getClipboardContentsLargestDimension(); /// returns the largest dimension of everything on the clipboard bool importEntities(const QString& filename); bool exportEntities(const QString& filename, const QVector& entityIDs); bool exportEntities(const QString& filename, float x, float y, float z, float s); diff --git a/libraries/entities/src/EntityTree.cpp b/libraries/entities/src/EntityTree.cpp index 1b59d3bd8e..ef49aca87a 100644 --- a/libraries/entities/src/EntityTree.cpp +++ b/libraries/entities/src/EntityTree.cpp @@ -1068,6 +1068,27 @@ void EntityTree::debugDumpMap() { qCDebug(entities) << "-----------------------------------------------------"; } +class ContentsDimensionOperator : public RecurseOctreeOperator { +public: + virtual bool preRecursion(OctreeElement* element); + virtual bool postRecursion(OctreeElement* element) { return true; } + float getLargestDimension() const { return _contentExtents.largestDimension(); } +private: + Extents _contentExtents; +}; + +bool ContentsDimensionOperator::preRecursion(OctreeElement* element) { + EntityTreeElement* entityTreeElement = static_cast(element); + entityTreeElement->expandExtentsToContents(_contentExtents); + return true; +} + +float EntityTree::getContentsLargestDimension() { + ContentsDimensionOperator theOperator; + recurseTreeWithOperator(&theOperator); + return theOperator.getLargestDimension(); +} + class DebugOperator : public RecurseOctreeOperator { public: virtual bool preRecursion(OctreeElement* element); diff --git a/libraries/entities/src/EntityTree.h b/libraries/entities/src/EntityTree.h index f99160f4ed..76d648cf46 100644 --- a/libraries/entities/src/EntityTree.h +++ b/libraries/entities/src/EntityTree.h @@ -166,6 +166,8 @@ public: bool writeToMap(QVariantMap& entityDescription, OctreeElement* element, bool skipDefaultValues); bool readFromMap(QVariantMap& entityDescription); + + float getContentsLargestDimension(); signals: void deletingEntity(const EntityItemID& entityID); diff --git a/libraries/entities/src/EntityTreeElement.cpp b/libraries/entities/src/EntityTreeElement.cpp index 349bd51372..3acc5fbcef 100644 --- a/libraries/entities/src/EntityTreeElement.cpp +++ b/libraries/entities/src/EntityTreeElement.cpp @@ -819,6 +819,16 @@ bool EntityTreeElement::pruneChildren() { return somethingPruned; } +void EntityTreeElement::expandExtentsToContents(Extents& extents) { + if (_entityItems->size()) { + for (uint16_t i = 0; i < _entityItems->size(); i++) { + EntityItem* entity = (*_entityItems)[i]; + extents.add(entity->getAABox()); + } + } +} + + void EntityTreeElement::debugDump() { qCDebug(entities) << "EntityTreeElement..."; diff --git a/libraries/entities/src/EntityTreeElement.h b/libraries/entities/src/EntityTreeElement.h index 0b28dd30d0..658dfeab92 100644 --- a/libraries/entities/src/EntityTreeElement.h +++ b/libraries/entities/src/EntityTreeElement.h @@ -196,6 +196,8 @@ public: bool pruneChildren(); + void expandExtentsToContents(Extents& extents); + protected: virtual void init(unsigned char * octalCode); EntityTree* _myTree; diff --git a/libraries/shared/src/Extents.cpp b/libraries/shared/src/Extents.cpp index 02e09bfa3c..f48ba3c99f 100644 --- a/libraries/shared/src/Extents.cpp +++ b/libraries/shared/src/Extents.cpp @@ -14,6 +14,7 @@ #include #include +#include "AABox.h" #include "Extents.h" void Extents::reset() { @@ -37,6 +38,11 @@ void Extents::addPoint(const glm::vec3& point) { maximum = glm::max(maximum, point); } +void Extents::add(const AABox& box) { + minimum = glm::min(minimum, box.getMinimumPoint()); + maximum = glm::max(maximum, box.getMaximumPoint()); +} + void Extents::rotate(const glm::quat& rotation) { glm::vec3 bottomLeftNear(minimum.x, minimum.y, minimum.z); glm::vec3 bottomRightNear(maximum.x, minimum.y, minimum.z); diff --git a/libraries/shared/src/Extents.h b/libraries/shared/src/Extents.h index 024d72013a..489d48b1c1 100644 --- a/libraries/shared/src/Extents.h +++ b/libraries/shared/src/Extents.h @@ -19,6 +19,8 @@ #include #include "StreamUtils.h" +class AABox; + class Extents { public: /// set minimum and maximum to FLT_MAX and -FLT_MAX respectively @@ -28,6 +30,10 @@ public: /// expand current limits to contain other extents void addExtents(const Extents& extents); + /// \param aabox another intance of extents + /// expand current limits to contain other aabox + void add(const AABox& box); + /// \param point new point to compare against existing limits /// compare point to current limits and expand them if necessary to contain point void addPoint(const glm::vec3& point); From 24739d24e168e711581fe980affb308df4e5491e Mon Sep 17 00:00:00 2001 From: Ryan Huffman Date: Mon, 11 May 2015 15:23:39 -0700 Subject: [PATCH 06/15] Fix calls to set draw zone boundaries --- examples/edit.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/examples/edit.js b/examples/edit.js index 208abf1e20..1545358448 100644 --- a/examples/edit.js +++ b/examples/edit.js @@ -243,7 +243,7 @@ var toolBar = (function () { } toolBar.selectTool(activeButton, isActive); lightOverlayManager.setVisible(isActive && Menu.isOptionChecked(MENU_SHOW_LIGHTS_IN_EDIT_MODE)); - Entities.drawZoneBoundaries(isActive && Menu.isOptionChecked(MENU_SHOW_ZONES_IN_EDIT_MODE)); + Entities.setDrawZoneBoundaries(isActive && Menu.isOptionChecked(MENU_SHOW_ZONES_IN_EDIT_MODE)); }; // Sets visibility of tool buttons, excluding the power button @@ -997,7 +997,7 @@ function handeMenuEvent(menuItem) { } else if (menuItem == MENU_SHOW_LIGHTS_IN_EDIT_MODE) { lightOverlayManager.setVisible(isActive && Menu.isOptionChecked(MENU_SHOW_LIGHTS_IN_EDIT_MODE)); } else if (menuItem == MENU_SHOW_ZONES_IN_EDIT_MODE) { - Entities.drawZoneBoundaries(isActive && Menu.isOptionChecked(MENU_SHOW_ZONES_IN_EDIT_MODE)); + Entities.setDrawZoneBoundaries(isActive && Menu.isOptionChecked(MENU_SHOW_ZONES_IN_EDIT_MODE)); } tooltip.show(false); } From a2a1bf4e8cab30984cf10716f02407b3242720f5 Mon Sep 17 00:00:00 2001 From: ZappoMan Date: Mon, 11 May 2015 16:32:27 -0700 Subject: [PATCH 07/15] make stars fade in as sun drops below horizon --- interface/src/Application.cpp | 24 +++++++++++++++++++++++- 1 file changed, 23 insertions(+), 1 deletion(-) diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index 088b2ddf96..dc38f64f58 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -3220,18 +3220,40 @@ void Application::displaySide(Camera& theCamera, bool selfAvatarOnly, RenderArgs // compute starfield alpha based on distance from atmosphere float alpha = 1.0f; bool hasStars = true; + if (Menu::getInstance()->isOptionChecked(MenuOption::Atmosphere)) { // TODO: handle this correctly for zones const EnvironmentData& closestData = _environment.getClosestData(theCamera.getPosition()); - + if (closestData.getHasStars()) { + const float APPROXIMATE_DISTANCE_FROM_HORIZON = 0.1f; + const float DOUBLE_APPROXIMATE_DISTANCE_FROM_HORIZON = 0.2f; + + glm::vec3 sunDirection = (getAvatarPosition() - closestData.getSunLocation()) + / closestData.getAtmosphereOuterRadius(); float height = glm::distance(theCamera.getPosition(), closestData.getAtmosphereCenter()); if (height < closestData.getAtmosphereInnerRadius()) { + // If we're inside the atmosphere, then determine if our keyLight is below the horizon alpha = 0.0f; + if (sunDirection.y > -APPROXIMATE_DISTANCE_FROM_HORIZON) { + float directionY = glm::clamp(sunDirection.y, + -APPROXIMATE_DISTANCE_FROM_HORIZON, APPROXIMATE_DISTANCE_FROM_HORIZON) + + APPROXIMATE_DISTANCE_FROM_HORIZON; + alpha = (directionY / DOUBLE_APPROXIMATE_DISTANCE_FROM_HORIZON); + } + + } else if (height < closestData.getAtmosphereOuterRadius()) { alpha = (height - closestData.getAtmosphereInnerRadius()) / (closestData.getAtmosphereOuterRadius() - closestData.getAtmosphereInnerRadius()); + + if (sunDirection.y > -APPROXIMATE_DISTANCE_FROM_HORIZON) { + float directionY = glm::clamp(sunDirection.y, + -APPROXIMATE_DISTANCE_FROM_HORIZON, APPROXIMATE_DISTANCE_FROM_HORIZON) + + APPROXIMATE_DISTANCE_FROM_HORIZON; + alpha = (directionY / DOUBLE_APPROXIMATE_DISTANCE_FROM_HORIZON); + } } } else { hasStars = false; From b3af5152240c20583f93855906b0beac66d50c35 Mon Sep 17 00:00:00 2001 From: Anthony Thibault Date: Mon, 11 May 2015 19:21:33 -0700 Subject: [PATCH 08/15] Particle entity improvements based on code review. * Updated variable naming to match coding standards. * Changed particle raw float arrays to vectors. * Bug fix: changing maxParticles will no longer lead to memory corruption. * Made particle ring buffer more explicit, added _particleTailIndex. * Added getLivingParticleCount() private method. * Moved integration and bounds code into private methods. * Bug fix: high emit rates now properly integrate particles forward for the remaing frame time, not the entire frame. * Bug fix: new particles were not initiaized to origin. * Added more particles to ajt-test.js. * Bug fix: ajt-test.js script was not shutting down properly. * Removed random seed, unless we have a psudo random number generator per particle system instance, it's unlikely that this will preserve sync. * Bumped packet version number. --- examples/ajt-test.js | 34 ++- .../RenderableParticleEffectEntityItem.cpp | 143 +++++------ .../entities/src/ParticleEffectEntityItem.cpp | 232 +++++++++--------- .../entities/src/ParticleEffectEntityItem.h | 51 ++-- libraries/networking/src/PacketHeaders.cpp | 2 +- libraries/networking/src/PacketHeaders.h | 1 + 6 files changed, 238 insertions(+), 225 deletions(-) diff --git a/examples/ajt-test.js b/examples/ajt-test.js index a908978a02..c26458a9af 100644 --- a/examples/ajt-test.js +++ b/examples/ajt-test.js @@ -28,32 +28,34 @@ } // constructor - function TestFx() { + function TestFx(color, emitDirection, emitRate, emitStrength, blinkRate) { var animationSettings = JSON.stringify({ fps: 30, frameIndex: 0, running: true, firstFrame: 0, lastFrame: 30, - loop: false }); + loop: true }); this.entity = Entities.addEntity({ type: "ParticleEffect", animationSettings: animationSettings, position: spawnPoint, textures: "http://www.hyperlogic.org/images/particle.png", - emitRate: 30, - emitStrength: 3, - color: { red: 128, green: 255, blue: 255 }, + emitRate: emitRate, + emitStrength: emitStrength, + emitDirection: emitDirection, + color: color, visible: true, locked: false }); + this.isPlaying = true; + var self = this; this.timer = Script.setInterval(function () { - print("AJT: setting animation props"); - var animProp = { animationFrameIndex: 0, - animationIsPlaying: true }; + // flip is playing state + self.isPlaying = !self.isPlaying; + var animProp = { animationIsPlaying: self.isPlaying }; Entities.editEntity(self.entity, animProp); - }, 2000); - + }, (1 / blinkRate) * 1000); } TestFx.prototype.Destroy = function () { @@ -65,11 +67,19 @@ var objs = []; function Init() { objs.push(new TestBox()); - objs.push(new TestFx()); + objs.push(new TestFx({ red: 255, blue: 0, green: 0 }, + { x: 0.5, y: 1.0, z: 0.0 }, + 100, 3, 1)); + objs.push(new TestFx({ red: 0, blue: 255, green: 0 }, + { x: 0, y: 1, z: 0 }, + 1000, 5, 0.5)); + objs.push(new TestFx({ red: 0, blue: 0, green: 255 }, + { x: -0.5, y: 1, z: 0 }, + 100, 3, 1)); } function ShutDown() { - var i, len = entities.length; + var i, len = objs.length; for (i = 0; i < len; i++) { objs[i].Destroy(); } diff --git a/libraries/entities-renderer/src/RenderableParticleEffectEntityItem.cpp b/libraries/entities-renderer/src/RenderableParticleEffectEntityItem.cpp index 32fd06c837..009d26481a 100644 --- a/libraries/entities-renderer/src/RenderableParticleEffectEntityItem.cpp +++ b/libraries/entities-renderer/src/RenderableParticleEffectEntityItem.cpp @@ -34,8 +34,7 @@ void RenderableParticleEffectEntityItem::render(RenderArgs* args) { assert(getType() == EntityTypes::ParticleEffect); PerformanceTimer perfTimer("RenderableParticleEffectEntityItem::render"); - if (_texturesChangedFlag) - { + if (_texturesChangedFlag) { if (_textures.isEmpty()) { _texture.clear(); } else { @@ -57,35 +56,38 @@ void RenderableParticleEffectEntityItem::render(RenderArgs* args) { void RenderableParticleEffectEntityItem::renderUntexturedQuads(RenderArgs* args) { - float pa_rad = getParticleRadius(); + float particleRadius = getParticleRadius(); const float MAX_COLOR = 255.0f; - glm::vec4 paColor(getColor()[RED_INDEX] / MAX_COLOR, getColor()[GREEN_INDEX] / MAX_COLOR, - getColor()[BLUE_INDEX] / MAX_COLOR, getLocalRenderAlpha()); + glm::vec4 particleColor(getColor()[RED_INDEX] / MAX_COLOR, + getColor()[GREEN_INDEX] / MAX_COLOR, + getColor()[BLUE_INDEX] / MAX_COLOR, + getLocalRenderAlpha()); - glm::vec3 up_offset = args->_viewFrustum->getUp() * pa_rad; - glm::vec3 right_offset = args->_viewFrustum->getRight() * pa_rad; + glm::vec3 upOffset = args->_viewFrustum->getUp() * particleRadius; + glm::vec3 rightOffset = args->_viewFrustum->getRight() * particleRadius; - QVector* pointVec = new QVector(_paCount * VERTS_PER_PARTICLE); - quint32 paCount = 0; - quint32 paIter = _paHead; - while (_paLife[paIter] > 0.0f && paCount < _maxParticles) { - int j = paIter * XYZ_STRIDE; + QVector vertices(getLivingParticleCount() * VERTS_PER_PARTICLE); + quint32 count = 0; + for (quint32 i = _particleHeadIndex; i != _particleTailIndex; i = (i + 1) % _maxParticles) { + glm::vec3 pos = _particlePositions[i]; - glm::vec3 pos(_paPosition[j], _paPosition[j + 1], _paPosition[j + 2]); - - pointVec->append(pos - right_offset + up_offset); - pointVec->append(pos + right_offset + up_offset); - pointVec->append(pos + right_offset - up_offset); - pointVec->append(pos - right_offset - up_offset); - - paIter = (paIter + 1) % _maxParticles; - paCount++; + // generate corners of quad, aligned to face the camera + vertices.append(pos - rightOffset + upOffset); + vertices.append(pos + rightOffset + upOffset); + vertices.append(pos + rightOffset - upOffset); + vertices.append(pos - rightOffset - upOffset); + count++; } - DependencyManager::get()->updateVertices(_cacheID, *pointVec, paColor); + // just double checking, cause if this invarient is false, we might have memory corruption bugs. + assert(count == getLivingParticleCount()); + + // update geometry cache with all the verts in model coordinates. + DependencyManager::get()->updateVertices(_cacheID, vertices, particleColor); glPushMatrix(); + glm::vec3 position = getPosition(); glTranslatef(position.x, position.y, position.z); glm::quat rotation = getRotation(); @@ -93,92 +95,93 @@ void RenderableParticleEffectEntityItem::renderUntexturedQuads(RenderArgs* args) glRotatef(glm::degrees(glm::angle(rotation)), axis.x, axis.y, axis.z); glPushMatrix(); + glm::vec3 positionToCenter = getCenter() - position; glTranslatef(positionToCenter.x, positionToCenter.y, positionToCenter.z); DependencyManager::get()->renderVertices(gpu::QUADS, _cacheID); - glPopMatrix(); - glPopMatrix(); - delete pointVec; + glPopMatrix(); + + glPopMatrix(); } -static glm::vec3 s_dir; +static glm::vec3 zSortAxis; static bool zSort(const glm::vec3& rhs, const glm::vec3& lhs) { - return glm::dot(rhs, s_dir) > glm::dot(lhs, s_dir); + return glm::dot(rhs, ::zSortAxis) > glm::dot(lhs, ::zSortAxis); } void RenderableParticleEffectEntityItem::renderTexturedQuads(RenderArgs* args) { - float pa_rad = getParticleRadius(); + float particleRadius = getParticleRadius(); const float MAX_COLOR = 255.0f; - glm::vec4 paColor(getColor()[RED_INDEX] / MAX_COLOR, getColor()[GREEN_INDEX] / MAX_COLOR, - getColor()[BLUE_INDEX] / MAX_COLOR, getLocalRenderAlpha()); + glm::vec4 particleColor(getColor()[RED_INDEX] / MAX_COLOR, + getColor()[GREEN_INDEX] / MAX_COLOR, + getColor()[BLUE_INDEX] / MAX_COLOR, + getLocalRenderAlpha()); - QVector* posVec = new QVector(_paCount); - quint32 paCount = 0; - quint32 paIter = _paHead; - while (_paLife[paIter] > 0.0f && paCount < _maxParticles) { - int j = paIter * XYZ_STRIDE; - posVec->append(glm::vec3(_paPosition[j], _paPosition[j + 1], _paPosition[j + 2])); - - paIter = (paIter + 1) % _maxParticles; - paCount++; + QVector positions(getLivingParticleCount()); + quint32 count = 0; + for (quint32 i = _particleHeadIndex; i != _particleTailIndex; i = (i + 1) % _maxParticles) { + positions.append(_particlePositions[i]); + count++; } + // just double checking, cause if this invarient is false, we might have memory corruption bugs. + assert(count == getLivingParticleCount()); + // sort particles back to front - qSort(posVec->begin(), posVec->end(), zSort); + ::zSortAxis = args->_viewFrustum->getDirection(); + qSort(positions.begin(), positions.end(), zSort); - QVector* vertVec = new QVector(_paCount * VERTS_PER_PARTICLE); - QVector* texCoordVec = new QVector(_paCount * VERTS_PER_PARTICLE); + QVector vertices(getLivingParticleCount() * VERTS_PER_PARTICLE); + QVector textureCoords(getLivingParticleCount() * VERTS_PER_PARTICLE); - s_dir = args->_viewFrustum->getDirection(); - glm::vec3 up_offset = args->_viewFrustum->getUp() * pa_rad; - glm::vec3 right_offset = args->_viewFrustum->getRight() * pa_rad; + glm::vec3 upOffset = args->_viewFrustum->getUp() * particleRadius; + glm::vec3 rightOffset = args->_viewFrustum->getRight() * particleRadius; - for (int i = 0; i < posVec->size(); i++) { - glm::vec3 pos = posVec->at(i); + for (int i = 0; i < positions.size(); i++) { + glm::vec3 pos = positions[i]; - vertVec->append(pos - right_offset + up_offset); - vertVec->append(pos + right_offset + up_offset); - vertVec->append(pos + right_offset - up_offset); - vertVec->append(pos - right_offset - up_offset); + // generate corners of quad aligned to face the camera. + vertices.append(pos - rightOffset + upOffset); + vertices.append(pos + rightOffset + upOffset); + vertices.append(pos + rightOffset - upOffset); + vertices.append(pos - rightOffset - upOffset); - texCoordVec->append(glm::vec2(0, 1)); - texCoordVec->append(glm::vec2(1, 1)); - texCoordVec->append(glm::vec2(1, 0)); - texCoordVec->append(glm::vec2(0, 0)); + textureCoords.append(glm::vec2(0, 1)); + textureCoords.append(glm::vec2(1, 1)); + textureCoords.append(glm::vec2(1, 0)); + textureCoords.append(glm::vec2(0, 0)); } - DependencyManager::get()->updateVertices(_cacheID, *vertVec, *texCoordVec, paColor); + // update geometry cache with all the verts in model coordinates. + DependencyManager::get()->updateVertices(_cacheID, vertices, textureCoords, particleColor); glEnable(GL_TEXTURE_2D); glBindTexture(GL_TEXTURE_2D, _texture->getID()); glPushMatrix(); - glm::vec3 position = getPosition(); - glTranslatef(position.x, position.y, position.z); - glm::quat rotation = getRotation(); - glm::vec3 axis = glm::axis(rotation); - glRotatef(glm::degrees(glm::angle(rotation)), axis.x, axis.y, axis.z); + glm::vec3 position = getPosition(); + glTranslatef(position.x, position.y, position.z); + glm::quat rotation = getRotation(); + glm::vec3 axis = glm::axis(rotation); + glRotatef(glm::degrees(glm::angle(rotation)), axis.x, axis.y, axis.z); - glPushMatrix(); + glPushMatrix(); - glm::vec3 positionToCenter = getCenter() - position; - glTranslatef(positionToCenter.x, positionToCenter.y, positionToCenter.z); + glm::vec3 positionToCenter = getCenter() - position; + glTranslatef(positionToCenter.x, positionToCenter.y, positionToCenter.z); - DependencyManager::get()->renderVertices(gpu::QUADS, _cacheID); + DependencyManager::get()->renderVertices(gpu::QUADS, _cacheID); + + glPopMatrix(); - glPopMatrix(); glPopMatrix(); glDisable(GL_TEXTURE_2D); glBindTexture(GL_TEXTURE_2D, 0); - - delete posVec; - delete vertVec; - delete texCoordVec; } diff --git a/libraries/entities/src/ParticleEffectEntityItem.cpp b/libraries/entities/src/ParticleEffectEntityItem.cpp index fc4927fdcd..d5492cb485 100644 --- a/libraries/entities/src/ParticleEffectEntityItem.cpp +++ b/libraries/entities/src/ParticleEffectEntityItem.cpp @@ -41,6 +41,7 @@ #include "EntitiesLogging.h" #include "ParticleEffectEntityItem.h" +const xColor ParticleEffectEntityItem::DEFAULT_COLOR = { 255, 255, 255 }; const float ParticleEffectEntityItem::DEFAULT_ANIMATION_FRAME_INDEX = 0.0f; const bool ParticleEffectEntityItem::DEFAULT_ANIMATION_IS_PLAYING = false; const float ParticleEffectEntityItem::DEFAULT_ANIMATION_FPS = 30.0f; @@ -53,42 +54,42 @@ const float ParticleEffectEntityItem::DEFAULT_LOCAL_GRAVITY = -9.8f; const float ParticleEffectEntityItem::DEFAULT_PARTICLE_RADIUS = 0.025f; const QString ParticleEffectEntityItem::DEFAULT_TEXTURES = ""; + EntityItem* ParticleEffectEntityItem::factory(const EntityItemID& entityID, const EntityItemProperties& properties) { return new ParticleEffectEntityItem(entityID, properties); } // our non-pure virtual subclass for now... ParticleEffectEntityItem::ParticleEffectEntityItem(const EntityItemID& entityItemID, const EntityItemProperties& properties) : - EntityItem(entityItemID, properties) { + EntityItem(entityItemID, properties), + _maxParticles(DEFAULT_MAX_PARTICLES), + _lifespan(DEFAULT_LIFESPAN), + _emitRate(DEFAULT_EMIT_RATE), + _emitDirection(DEFAULT_EMIT_DIRECTION), + _emitStrength(DEFAULT_EMIT_STRENGTH), + _localGravity(DEFAULT_LOCAL_GRAVITY), + _particleRadius(DEFAULT_PARTICLE_RADIUS), + _lastAnimated(usecTimestampNow()), + _animationLoop(), + _animationSettings(), + _textures(DEFAULT_TEXTURES), + _texturesChangedFlag(false), + _shapeType(SHAPE_TYPE_NONE), + _particleLifetimes(DEFAULT_MAX_PARTICLES, 0.0f), + _particlePositions(DEFAULT_MAX_PARTICLES, glm::vec3(0.0f, 0.0f, 0.0f)), + _particleVelocities(DEFAULT_MAX_PARTICLES, glm::vec3(0.0f, 0.0f, 0.0f)), + _timeUntilNextEmit(0.0f), + _particleHeadIndex(0), + _particleTailIndex(0), + _particleMaxBound(glm::vec3(1.0f, 1.0f, 1.0f)), + _particleMinBound(glm::vec3(-1.0f, -1.0f, -1.0f)) { + _type = EntityTypes::ParticleEffect; - _maxParticles = DEFAULT_MAX_PARTICLES; - _lifespan = DEFAULT_LIFESPAN; - _emitRate = DEFAULT_EMIT_RATE; - _emitDirection = DEFAULT_EMIT_DIRECTION; - _emitStrength = DEFAULT_EMIT_STRENGTH; - _localGravity = DEFAULT_LOCAL_GRAVITY; - _particleRadius = DEFAULT_PARTICLE_RADIUS; - _textures = DEFAULT_TEXTURES; + setColor(DEFAULT_COLOR); setProperties(properties); - // this is a pretty dumb thing to do, and it should probably be changed to use a more dynamic - // data structure in the future. I'm just trying to get some code out the door for now (and it's - // at least time efficient (though not space efficient). - // Also, this being a real-time application, it's doubtful we'll ever have millions of particles - // to keep track of, so this really isn't all that bad. - _paLife = new float[_maxParticles]; - _paPosition = new float[_maxParticles * XYZ_STRIDE]; // x,y,z - _paVelocity = new float[_maxParticles * XYZ_STRIDE]; // x,y,z - _paXmax = _paYmax = _paZmax = 1.0f; - _paXmin = _paYmin = _paZmin = -1.0f; - _randSeed = (unsigned int) glm::abs(_lifespan + _emitRate + _localGravity + getPosition().x + getPosition().y + getPosition().z); - resetSimulation(); - _lastAnimated = usecTimestampNow(); } ParticleEffectEntityItem::~ParticleEffectEntityItem() { - delete [] _paLife; - delete [] _paPosition; - delete [] _paVelocity; } EntityItemProperties ParticleEffectEntityItem::getProperties() const { @@ -146,8 +147,8 @@ bool ParticleEffectEntityItem::setProperties(const EntityItemProperties& propert } int ParticleEffectEntityItem::readEntitySubclassDataFromBuffer(const unsigned char* data, int bytesLeftToRead, - ReadBitstreamToTreeParams& args, - EntityPropertyFlags& propertyFlags, bool overwriteLocalData) { + ReadBitstreamToTreeParams& args, + EntityPropertyFlags& propertyFlags, bool overwriteLocalData) { int bytesRead = 0; const unsigned char* dataAt = data; @@ -214,12 +215,12 @@ EntityPropertyFlags ParticleEffectEntityItem::getEntityProperties(EncodeBitstrea } void ParticleEffectEntityItem::appendSubclassData(OctreePacketData* packetData, EncodeBitstreamParams& params, - EntityTreeElementExtraEncodeData* modelTreeElementExtraEncodeData, - EntityPropertyFlags& requestedProperties, - EntityPropertyFlags& propertyFlags, - EntityPropertyFlags& propertiesDidntFit, - int& propertyCount, - OctreeElement::AppendState& appendState) const { + EntityTreeElementExtraEncodeData* modelTreeElementExtraEncodeData, + EntityPropertyFlags& requestedProperties, + EntityPropertyFlags& propertyFlags, + EntityPropertyFlags& propertiesDidntFit, + int& propertyCount, + OctreeElement::AppendState& appendState) const { bool successPropertyFits = true; APPEND_ENTITY_PROPERTY(PROP_COLOR, appendColor, getColor()); @@ -240,7 +241,7 @@ void ParticleEffectEntityItem::appendSubclassData(OctreePacketData* packetData, bool ParticleEffectEntityItem::isAnimatingSomething() const { // keep animating if there are particles still alive. - return (getAnimationIsPlaying() || _paCount > 0) && getAnimationFPS() != 0.0f; + return (getAnimationIsPlaying() || getLivingParticleCount() > 0) && getAnimationFPS() != 0.0f; } bool ParticleEffectEntityItem::needsToCallUpdate() const { @@ -262,9 +263,9 @@ void ParticleEffectEntityItem::update(const quint64& now) { // update the dimensions glm::vec3 dims; - dims.x = glm::max(glm::abs(_paXmin), glm::abs(_paXmax)) * 2.0f; - dims.y = glm::max(glm::abs(_paYmin), glm::abs(_paYmax)) * 2.0f; - dims.z = glm::max(glm::abs(_paZmin), glm::abs(_paZmax)) * 2.0f; + dims.x = glm::max(glm::abs(_particleMinBound.x), glm::abs(_particleMaxBound.x)) * 2.0f; + dims.y = glm::max(glm::abs(_particleMinBound.y), glm::abs(_particleMaxBound.y)) * 2.0f; + dims.z = glm::max(glm::abs(_particleMinBound.z), glm::abs(_particleMaxBound.z)) * 2.0f; setDimensions(dims); } @@ -412,106 +413,103 @@ QString ParticleEffectEntityItem::getAnimationSettings() const { return jsonByteString; } +void ParticleEffectEntityItem::extendBounds(const glm::vec3& point) { + _particleMinBound.x = glm::min(_particleMinBound.x, point.x); + _particleMinBound.y = glm::min(_particleMinBound.y, point.y); + _particleMinBound.z = glm::min(_particleMinBound.z, point.z); + _particleMaxBound.x = glm::max(_particleMaxBound.x, point.x); + _particleMaxBound.y = glm::max(_particleMaxBound.y, point.y); + _particleMaxBound.z = glm::max(_particleMaxBound.z, point.z); +} + +void ParticleEffectEntityItem::integrateParticle(quint32 index, float deltaTime) { + glm::vec3 atSquared(0.0f, 0.5f * _localGravity * deltaTime * deltaTime, 0.0f); + glm::vec3 at(0.0f, _localGravity * deltaTime, 0.0f); + _particlePositions[index] += _particleVelocities[index] * deltaTime + atSquared; + _particleVelocities[index] += at; +} + void ParticleEffectEntityItem::stepSimulation(float deltaTime) { - _paXmin = _paYmin = _paZmin = -1.0f; - _paXmax = _paYmax = _paZmax = 1.0f; - float oneHalfATSquared = 0.5f * _localGravity * deltaTime * deltaTime; + _particleMinBound = glm::vec3(-1.0f, -1.0f, -1.0f); + _particleMaxBound = glm::vec3(1.0f, 1.0f, 1.0f); - // update particles - quint32 updateCount = 0; - quint32 updateIter = _paHead; - while (_paLife[updateIter] > 0.0f && updateCount < _maxParticles) { - _paLife[updateIter] -= deltaTime; - if (_paLife[updateIter] <= 0.0f) { - _paLife[updateIter] = -1.0f; - _paHead = (_paHead + 1) % _maxParticles; - _paCount--; + // update particles between head and tail + for (quint32 i = _particleHeadIndex; i != _particleTailIndex; i = (i + 1) % _maxParticles) { + _particleLifetimes[i] -= deltaTime; + + // if particle has died. + if (_particleLifetimes[i] <= 0.0f) { + // move head forward + _particleHeadIndex = (_particleHeadIndex + 1) % _maxParticles; } else { - int j = updateIter * XYZ_STRIDE; - - _paPosition[j] += _paVelocity[j] * deltaTime; - _paPosition[j+1] += _paVelocity[j+1] * deltaTime + oneHalfATSquared; - _paPosition[j+2] += _paVelocity[j+2] * deltaTime; - - _paXmin = glm::min(_paXmin, _paPosition[j]); - _paYmin = glm::min(_paYmin, _paPosition[j+1]); - _paZmin = glm::min(_paZmin, _paPosition[j+2]); - _paXmax = glm::max(_paXmax, _paPosition[j]); - _paYmax = glm::max(_paYmax, _paPosition[j + 1]); - _paZmax = glm::max(_paZmax, _paPosition[j + 2]); - - // massless particles - _paVelocity[j + 1] += deltaTime * _localGravity; + integrateParticle(i, deltaTime); + extendBounds(_particlePositions[i]); } - updateIter = (updateIter + 1) % _maxParticles; - updateCount++; } - quint32 emitIdx = updateIter; - quint32 birthed = 0; - glm::vec3 randOffset; - // emit new particles, but only if animaiton is playing if (getAnimationIsPlaying()) { - _partialEmit += ((float)_emitRate) * deltaTime; - birthed = (quint32)_partialEmit; - _partialEmit -= (float)birthed; - } else { - _partialEmit = 0; - } - for (quint32 i = 0; i < birthed; i++) { - if (_paLife[emitIdx] < 0.0f) { - int j = emitIdx * XYZ_STRIDE; - _paLife[emitIdx] = _lifespan; + float timeLeftInFrame = deltaTime; + while (_timeUntilNextEmit < timeLeftInFrame) { - randOffset.x = (((double)rand() / (double)RAND_MAX) - 0.5) * 0.25 * _emitStrength; - randOffset.y = (((double)rand() / (double)RAND_MAX) - 0.5) * 0.25 * _emitStrength; - randOffset.z = (((double)rand() / (double)RAND_MAX) - 0.5) * 0.25 * _emitStrength; + timeLeftInFrame -= _timeUntilNextEmit; + _timeUntilNextEmit = 1.0f / _emitRate; - _paVelocity[j] = (_emitDirection.x * _emitStrength) + randOffset.x; - _paVelocity[j + 1] = (_emitDirection.y * _emitStrength) + randOffset.y; - _paVelocity[j + 2] = (_emitDirection.z * _emitStrength) + randOffset.z; + // emit a new particle at tail index. + quint32 i = _particleTailIndex; + _particleLifetimes[i] = _lifespan; - _paPosition[j] += _paVelocity[j] * deltaTime; - _paPosition[j + 1] += _paVelocity[j + 1] * deltaTime + oneHalfATSquared; - _paPosition[j + 2] += _paVelocity[j + 2] * deltaTime; + // jitter the _emitDirection by a random offset + glm::vec3 randOffset; + randOffset.x = (randFloat() - 0.5f) * 0.25f * _emitStrength; + randOffset.y = (randFloat() - 0.5f) * 0.25f * _emitStrength; + randOffset.z = (randFloat() - 0.5f) * 0.25f * _emitStrength; - _paXmin = glm::min(_paXmin, _paPosition[j]); - _paYmin = glm::min(_paYmin, _paPosition[j + 1]); - _paZmin = glm::min(_paZmin, _paPosition[j + 2]); - _paXmax = glm::max(_paXmax, _paPosition[j]); - _paYmax = glm::max(_paYmax, _paPosition[j + 1]); - _paZmax = glm::max(_paZmax, _paPosition[j + 2]); + // set initial conditions + _particlePositions[i] = glm::vec3(0.0f, 0.0f, 0.0f); + _particleVelocities[i] = _emitDirection * _emitStrength + randOffset; - // massless particles - // and simple gravity down - _paVelocity[j + 1] += deltaTime * _localGravity; + integrateParticle(i, timeLeftInFrame); + extendBounds(_particlePositions[i]); - emitIdx = (emitIdx + 1) % _maxParticles; - _paCount++; + _particleTailIndex = (_particleTailIndex + 1) % _maxParticles; + + // overflow! move head forward by one. + // because the case of head == tail indicates an empty array, not a full one. + // This can drop an existing older particle, but this is by design, newer particles are a higher priority. + if (_particleTailIndex == _particleHeadIndex) { + _particleHeadIndex = (_particleHeadIndex + 1) % _maxParticles; + } } - else - break; + + _timeUntilNextEmit -= timeLeftInFrame; } } -void ParticleEffectEntityItem::resetSimulation() { - for (quint32 i = 0; i < _maxParticles; i++) { - quint32 j = i * XYZ_STRIDE; - _paLife[i] = -1.0f; - _paPosition[j] = 0.0f; - _paPosition[j+1] = 0.0f; - _paPosition[j+2] = 0.0f; - _paVelocity[j] = 0.0f; - _paVelocity[j+1] = 0.0f; - _paVelocity[j+2] = 0.0f; - } - _paCount = 0; - _paHead = 0; - _partialEmit = 0.0f; +void ParticleEffectEntityItem::setMaxParticles(quint32 maxParticles) { + _maxParticles = maxParticles; - srand(_randSeed); + // TODO: try to do something smart here and preserve the state of existing particles. + + // resize vectors + _particleLifetimes.resize(_maxParticles); + _particlePositions.resize(_maxParticles); + _particleVelocities.resize(_maxParticles); + + // effectivly clear all particles and start emitting new ones from scratch. + _particleHeadIndex = 0; + _particleTailIndex = 0; + _timeUntilNextEmit = 0.0f; +} + +// because particles are in a ring buffer, this isn't trivial +quint32 ParticleEffectEntityItem::getLivingParticleCount() const { + if (_particleTailIndex >= _particleHeadIndex) { + return _particleTailIndex - _particleHeadIndex; + } else { + return (_maxParticles - _particleHeadIndex) + _particleTailIndex; + } } diff --git a/libraries/entities/src/ParticleEffectEntityItem.h b/libraries/entities/src/ParticleEffectEntityItem.h index 3533ce84e7..6d1ef601f6 100644 --- a/libraries/entities/src/ParticleEffectEntityItem.h +++ b/libraries/entities/src/ParticleEffectEntityItem.h @@ -31,16 +31,16 @@ public: virtual EntityPropertyFlags getEntityProperties(EncodeBitstreamParams& params) const; virtual void appendSubclassData(OctreePacketData* packetData, EncodeBitstreamParams& params, - EntityTreeElementExtraEncodeData* modelTreeElementExtraEncodeData, - EntityPropertyFlags& requestedProperties, - EntityPropertyFlags& propertyFlags, - EntityPropertyFlags& propertiesDidntFit, - int& propertyCount, - OctreeElement::AppendState& appendState) const; + EntityTreeElementExtraEncodeData* modelTreeElementExtraEncodeData, + EntityPropertyFlags& requestedProperties, + EntityPropertyFlags& propertyFlags, + EntityPropertyFlags& propertiesDidntFit, + int& propertyCount, + OctreeElement::AppendState& appendState) const; virtual int readEntitySubclassDataFromBuffer(const unsigned char* data, int bytesLeftToRead, - ReadBitstreamToTreeParams& args, - EntityPropertyFlags& propertyFlags, bool overwriteLocalData); + ReadBitstreamToTreeParams& args, + EntityPropertyFlags& propertyFlags, bool overwriteLocalData); virtual void update(const quint64& now); virtual bool needsToCallUpdate() const; @@ -48,6 +48,7 @@ public: const rgbColor& getColor() const { return _color; } xColor getXColor() const { xColor color = { _color[RED_INDEX], _color[GREEN_INDEX], _color[BLUE_INDEX] }; return color; } + static const xColor DEFAULT_COLOR; void setColor(const rgbColor& value) { memcpy(_color, value, sizeof(_color)); } void setColor(const xColor& value) { _color[RED_INDEX] = value.red; @@ -86,7 +87,7 @@ public: float getAnimationLastFrame() const { return _animationLoop.getLastFrame(); } static const quint32 DEFAULT_MAX_PARTICLES; - void setMaxParticles(quint32 maxParticles) { _maxParticles = maxParticles; } + void setMaxParticles(quint32 maxParticles); quint32 getMaxParticles() const { return _maxParticles; } static const float DEFAULT_LIFESPAN; @@ -98,7 +99,7 @@ public: float getEmitRate() const { return _emitRate; } static const glm::vec3 DEFAULT_EMIT_DIRECTION; - void setEmitDirection(glm::vec3 emitDirection) { _emitDirection = emitDirection; } + void setEmitDirection(glm::vec3 emitDirection) { _emitDirection = glm::normalize(emitDirection); } const glm::vec3& getEmitDirection() const { return _emitDirection; } static const float DEFAULT_EMIT_STRENGTH; @@ -131,7 +132,9 @@ protected: bool isAnimatingSomething() const; void stepSimulation(float deltaTime); - void resetSimulation(); + void extendBounds(const glm::vec3& point); + void integrateParticle(quint32 index, float deltaTime); + quint32 getLivingParticleCount() const; // the properties of this entity rgbColor _color; @@ -150,21 +153,19 @@ protected: ShapeType _shapeType = SHAPE_TYPE_NONE; // all the internals of running the particle sim - const quint32 XYZ_STRIDE = 3; - float* _paLife; - float* _paPosition; - float* _paVelocity; - float _partialEmit; - quint32 _paCount; - quint32 _paHead; - float _paXmin; - float _paXmax; - float _paYmin; - float _paYmax; - float _paZmin; - float _paZmax; - unsigned int _randSeed; + QVector _particleLifetimes; + QVector _particlePositions; + QVector _particleVelocities; + float _timeUntilNextEmit; + // particle arrays are a ring buffer, use these indicies + // to keep track of the living particles. + quint32 _particleHeadIndex; + quint32 _particleTailIndex; + + // bounding volume + glm::vec3 _particleMaxBound; + glm::vec3 _particleMinBound; }; #endif // hifi_ParticleEffectEntityItem_h diff --git a/libraries/networking/src/PacketHeaders.cpp b/libraries/networking/src/PacketHeaders.cpp index ec83e18c3d..87851ed80e 100644 --- a/libraries/networking/src/PacketHeaders.cpp +++ b/libraries/networking/src/PacketHeaders.cpp @@ -74,7 +74,7 @@ PacketVersion versionForPacketType(PacketType type) { return 1; case PacketTypeEntityAddOrEdit: case PacketTypeEntityData: - return VERSION_ENTITIES_ZONE_ENTITIES_HAVE_SKYBOX; + return VERSION_ENTITIES_PARTICLE_ENTITIES_HAVE_TEXTURES; case PacketTypeEntityErase: return 2; case PacketTypeAudioStreamStats: diff --git a/libraries/networking/src/PacketHeaders.h b/libraries/networking/src/PacketHeaders.h index 38aeed4993..82fc927166 100644 --- a/libraries/networking/src/PacketHeaders.h +++ b/libraries/networking/src/PacketHeaders.h @@ -142,5 +142,6 @@ const PacketVersion VERSION_ENTITIES_ZONE_ENTITIES_HAVE_DYNAMIC_SHAPE = 18; const PacketVersion VERSION_ENTITIES_HAVE_NAMES = 19; const PacketVersion VERSION_ENTITIES_ZONE_ENTITIES_HAVE_ATMOSPHERE = 20; const PacketVersion VERSION_ENTITIES_ZONE_ENTITIES_HAVE_SKYBOX = 21; +const PacketVersion VERSION_ENTITIES_PARTICLE_ENTITIES_HAVE_TEXTURES = 22; #endif // hifi_PacketHeaders_h From 666caf0d5981959c03734a2a27ab242903ec6bc9 Mon Sep 17 00:00:00 2001 From: Brad Davis Date: Tue, 12 May 2015 00:25:49 -0700 Subject: [PATCH 09/15] Working on fixing DK1 and santizing the transform stuff --- interface/src/Application.cpp | 78 ++---- interface/src/Camera.cpp | 28 +- interface/src/Camera.h | 29 +-- interface/src/avatar/MyAvatar.cpp | 2 +- interface/src/devices/OculusManager.cpp | 56 ++-- interface/src/devices/OculusManager.h | 2 +- interface/src/devices/TV3DManager.cpp | 12 +- libraries/octree/src/OctreeHeadlessViewer.cpp | 8 +- libraries/octree/src/ViewFrustum.cpp | 242 +++++------------- libraries/octree/src/ViewFrustum.h | 79 ++---- 10 files changed, 157 insertions(+), 379 deletions(-) diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index 7334222b96..0c6d199f2b 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -19,6 +19,7 @@ #include #include #include +#include // include this before QGLWidget, which includes an earlier version of OpenGL #include "InterfaceConfig.h" @@ -965,12 +966,11 @@ void Application::showEditEntitiesHelp() { void Application::resetCamerasOnResizeGL(Camera& camera, const glm::uvec2& size) { if (OculusManager::isConnected()) { - OculusManager::configureCamera(camera, size.x, size.y); + OculusManager::configureCamera(camera); } else if (TV3DManager::isConnected()) { TV3DManager::configureCamera(camera, size.x, size.y); } else { - camera.setAspectRatio((float)size.x / size.y); - camera.setFieldOfView(_fieldOfView.get()); + camera.setProjection(glm::perspective(glm::radians(_fieldOfView.get()), (float)size.x / size.y, DEFAULT_NEAR_CLIP, DEFAULT_FAR_CLIP)); } } @@ -984,7 +984,7 @@ void Application::resizeGL() { renderSize = _glWidget->getDeviceSize() * getRenderResolutionScale(); } if (_renderResolution == toGlm(renderSize)) { - return; + return; } _renderResolution = toGlm(renderSize); @@ -1011,25 +1011,15 @@ void Application::updateProjectionMatrix() { } void Application::updateProjectionMatrix(Camera& camera, bool updateViewFrustum) { - glMatrixMode(GL_PROJECTION); - glLoadIdentity(); + _projectionMatrix = camera.getProjection(); - float left, right, bottom, top, nearVal, farVal; - glm::vec4 nearClipPlane, farClipPlane; + glMatrixMode(GL_PROJECTION); + glLoadMatrixf(glm::value_ptr(_projectionMatrix)); // Tell our viewFrustum about this change, using the application camera if (updateViewFrustum) { loadViewFrustum(camera, _viewFrustum); - _viewFrustum.computeOffAxisFrustum(left, right, bottom, top, nearVal, farVal, nearClipPlane, farClipPlane); - } else { - ViewFrustum tempViewFrustum; - loadViewFrustum(camera, tempViewFrustum); - tempViewFrustum.computeOffAxisFrustum(left, right, bottom, top, nearVal, farVal, nearClipPlane, farClipPlane); - } - glFrustum(left, right, bottom, top, nearVal, farVal); - - // save matrix - glGetFloatv(GL_PROJECTION_MATRIX, (GLfloat*)&_projectionMatrix); + } glMatrixMode(GL_MODELVIEW); } @@ -1250,6 +1240,7 @@ void Application::keyPressEvent(QKeyEvent* event) { } break; +#if 0 case Qt::Key_I: if (isShifted) { _myCamera.setEyeOffsetOrientation(glm::normalize( @@ -1314,6 +1305,8 @@ void Application::keyPressEvent(QKeyEvent* event) { } updateProjectionMatrix(); break; +#endif + case Qt::Key_H: if (isShifted) { Menu::getInstance()->triggerOption(MenuOption::Mirror); @@ -2631,7 +2624,7 @@ void Application::queryOctree(NodeType_t serverType, PacketType packetType, Node _octreeQuery.setCameraAspectRatio(_viewFrustum.getAspectRatio()); _octreeQuery.setCameraNearClip(_viewFrustum.getNearClip()); _octreeQuery.setCameraFarClip(_viewFrustum.getFarClip()); - _octreeQuery.setCameraEyeOffsetPosition(_viewFrustum.getEyeOffsetPosition()); + _octreeQuery.setCameraEyeOffsetPosition(glm::vec3()); auto lodManager = DependencyManager::get(); _octreeQuery.setOctreeSizeScale(lodManager->getOctreeSizeScale()); _octreeQuery.setBoundaryLevelAdjust(lodManager->getBoundaryLevelAdjust()); @@ -2835,25 +2828,11 @@ QRect Application::getDesirableApplicationGeometry() { // void Application::loadViewFrustum(Camera& camera, ViewFrustum& viewFrustum) { // We will use these below, from either the camera or head vectors calculated above - glm::vec3 position(camera.getPosition()); - float fov = camera.getFieldOfView(); // degrees - float nearClip = camera.getNearClip(); - float farClip = camera.getFarClip(); - float aspectRatio = camera.getAspectRatio(); - - glm::quat rotation = camera.getRotation(); + viewFrustum.setProjection(camera.getProjection()); // Set the viewFrustum up with the correct position and orientation of the camera - viewFrustum.setPosition(position); - viewFrustum.setOrientation(rotation); - - // Also make sure it's got the correct lens details from the camera - viewFrustum.setAspectRatio(aspectRatio); - viewFrustum.setFieldOfView(fov); // degrees - viewFrustum.setNearClip(nearClip); - viewFrustum.setFarClip(farClip); - viewFrustum.setEyeOffsetPosition(camera.getEyeOffsetPosition()); - viewFrustum.setEyeOffsetOrientation(camera.getEyeOffsetOrientation()); + viewFrustum.setPosition(camera.getPosition()); + viewFrustum.setOrientation(camera.getRotation()); // Ask the ViewFrustum class to calculate our corners viewFrustum.calculate(); @@ -2954,13 +2933,7 @@ void Application::updateShadowMap() { glm::vec3 shadowFrustumCenter = rotation * ((minima + maxima) * 0.5f); _shadowViewFrustum.setPosition(shadowFrustumCenter); _shadowViewFrustum.setOrientation(rotation); - _shadowViewFrustum.setOrthographic(true); - _shadowViewFrustum.setWidth(maxima.x - minima.x); - _shadowViewFrustum.setHeight(maxima.y - minima.y); - _shadowViewFrustum.setNearClip(minima.z); - _shadowViewFrustum.setFarClip(maxima.z); - _shadowViewFrustum.setEyeOffsetPosition(glm::vec3()); - _shadowViewFrustum.setEyeOffsetOrientation(glm::quat()); + _shadowViewFrustum.setProjection(glm::ortho(minima.x, maxima.x, minima.y, maxima.y, minima.z, maxima.z)); _shadowViewFrustum.calculate(); glMatrixMode(GL_PROJECTION); @@ -3147,12 +3120,6 @@ void Application::displaySide(Camera& theCamera, bool selfAvatarOnly, RenderArgs glFrontFace(GL_CCW); } - glm::vec3 eyeOffsetPos = theCamera.getEyeOffsetPosition(); - glm::quat eyeOffsetOrient = theCamera.getEyeOffsetOrientation(); - glm::vec3 eyeOffsetAxis = glm::axis(eyeOffsetOrient); - glRotatef(-glm::degrees(glm::angle(eyeOffsetOrient)), eyeOffsetAxis.x, eyeOffsetAxis.y, eyeOffsetAxis.z); - glTranslatef(-eyeOffsetPos.x, -eyeOffsetPos.y, -eyeOffsetPos.z); - // transform view according to theCamera // could be myCamera (if in normal mode) // or could be viewFrustumOffsetCamera if in offset mode @@ -3170,8 +3137,6 @@ void Application::displaySide(Camera& theCamera, bool selfAvatarOnly, RenderArgs Transform viewTransform; viewTransform.setTranslation(theCamera.getPosition()); viewTransform.setRotation(rotation); - viewTransform.postTranslate(eyeOffsetPos); - viewTransform.postRotate(eyeOffsetOrient); if (theCamera.getMode() == CAMERA_MODE_MIRROR) { viewTransform.setScale(Transform::Vec3(-1.0f, 1.0f, 1.0f)); } @@ -3243,7 +3208,7 @@ void Application::displaySide(Camera& theCamera, bool selfAvatarOnly, RenderArgs // finally render the starfield if (hasStars) { - _stars.render(theCamera.getFieldOfView(), theCamera.getAspectRatio(), theCamera.getNearClip(), alpha); + _stars.render(_displayViewFrustum.getFieldOfView(), _displayViewFrustum.getAspectRatio(), _displayViewFrustum.getNearClip(), alpha); } // draw the sky dome @@ -3477,15 +3442,16 @@ void Application::renderRearViewMirror(const QRect& region, bool billboard) { // Grab current viewport to reset it at the end int viewport[4]; glGetIntegerv(GL_VIEWPORT, viewport); + float aspect = (float)region.width() / region.height(); + float fov = MIRROR_FIELD_OF_VIEW; // bool eyeRelativeCamera = false; if (billboard) { - _mirrorCamera.setFieldOfView(BILLBOARD_FIELD_OF_VIEW); // degees + fov = BILLBOARD_FIELD_OF_VIEW; // degees _mirrorCamera.setPosition(_myAvatar->getPosition() + _myAvatar->getOrientation() * glm::vec3(0.0f, 0.0f, -1.0f) * BILLBOARD_DISTANCE * _myAvatar->getScale()); } else if (RearMirrorTools::rearViewZoomLevel.get() == BODY) { - _mirrorCamera.setFieldOfView(MIRROR_FIELD_OF_VIEW); // degrees _mirrorCamera.setPosition(_myAvatar->getChestPosition() + _myAvatar->getOrientation() * glm::vec3(0.0f, 0.0f, -1.0f) * MIRROR_REARVIEW_BODY_DISTANCE * _myAvatar->getScale()); @@ -3506,12 +3472,10 @@ void Application::renderRearViewMirror(const QRect& region, bool billboard) { // This was removed in commit 71e59cfa88c6563749594e25494102fe01db38e9 but could be further // investigated in order to adapt the technique while fixing the head rendering issue, // but the complexity of the hack suggests that a better approach - _mirrorCamera.setFieldOfView(MIRROR_FIELD_OF_VIEW); // degrees _mirrorCamera.setPosition(_myAvatar->getHead()->getEyePosition() + _myAvatar->getOrientation() * glm::vec3(0.0f, 0.0f, -1.0f) * MIRROR_REARVIEW_DISTANCE * _myAvatar->getScale()); } - _mirrorCamera.setAspectRatio((float)region.width() / region.height()); - + _mirrorCamera.setProjection(glm::perspective(glm::radians(fov), aspect, DEFAULT_NEAR_CLIP, DEFAULT_FAR_CLIP)); _mirrorCamera.setRotation(_myAvatar->getWorldAlignedOrientation() * glm::quat(glm::vec3(0.0f, PI, 0.0f))); // set the bounds of rear mirror view diff --git a/interface/src/Camera.cpp b/interface/src/Camera.cpp index 1016d60ccf..e501b91dea 100644 --- a/interface/src/Camera.cpp +++ b/interface/src/Camera.cpp @@ -48,10 +48,7 @@ QString modeToString(CameraMode mode) { Camera::Camera() : _mode(CAMERA_MODE_THIRD_PERSON), _position(0.0f, 0.0f, 0.0f), - _fieldOfView(DEFAULT_FIELD_OF_VIEW_DEGREES), - _aspectRatio(16.0f/9.0f), - _nearClip(DEFAULT_NEAR_CLIP), // default - _farClip(DEFAULT_FAR_CLIP), // default + _projection(glm::perspective(glm::radians(DEFAULT_FIELD_OF_VIEW_DEGREES), 16.0f/9.0f, DEFAULT_NEAR_CLIP, DEFAULT_FAR_CLIP)), _hmdPosition(), _hmdRotation(), _isKeepLookingAt(false), @@ -91,32 +88,13 @@ void Camera::setHmdRotation(const glm::quat& hmdRotation) { } } -float Camera::getFarClip() const { - return (_farClip < std::numeric_limits::max()) - ? _farClip - : std::numeric_limits::max() - 1; -} - void Camera::setMode(CameraMode mode) { _mode = mode; emit modeUpdated(modeToString(mode)); } - -void Camera::setFieldOfView(float f) { - _fieldOfView = f; -} - -void Camera::setAspectRatio(float a) { - _aspectRatio = a; -} - -void Camera::setNearClip(float n) { - _nearClip = n; -} - -void Camera::setFarClip(float f) { - _farClip = f; +void Camera::setProjection(const glm::mat4& projection) { + _projection = projection; } PickRay Camera::computePickRay(float x, float y) { diff --git a/interface/src/Camera.h b/interface/src/Camera.h index e06e12f7dc..b9518aa041 100644 --- a/interface/src/Camera.h +++ b/interface/src/Camera.h @@ -43,36 +43,24 @@ public: void update( float deltaTime ); + void setPosition(const glm::vec3& position); void setRotation(const glm::quat& rotation); + void setProjection(const glm::mat4 & projection); void setHmdPosition(const glm::vec3& hmdPosition); void setHmdRotation(const glm::quat& hmdRotation); - void setMode(CameraMode m); - void setFieldOfView(float f); - void setAspectRatio(float a); - void setNearClip(float n); - void setFarClip(float f); - void setEyeOffsetPosition(const glm::vec3& p) { _eyeOffsetPosition = p; } - void setEyeOffsetOrientation(const glm::quat& o) { _eyeOffsetOrientation = o; } glm::quat getRotation() const { return _rotation * _hmdRotation; } + glm::vec3 getPosition() const { return _position + _hmdPosition; } + const glm::mat4& getProjection() const { return _projection; } const glm::vec3& getHmdPosition() const { return _hmdPosition; } const glm::quat& getHmdRotation() const { return _hmdRotation; } - CameraMode getMode() const { return _mode; } - float getFieldOfView() const { return _fieldOfView; } - float getAspectRatio() const { return _aspectRatio; } - float getNearClip() const { return _nearClip; } - float getFarClip() const; - const glm::vec3& getEyeOffsetPosition() const { return _eyeOffsetPosition; } - const glm::quat& getEyeOffsetOrientation() const { return _eyeOffsetOrientation; } + public slots: QString getModeString() const; void setModeString(const QString& mode); - glm::vec3 getPosition() const { return _position + _hmdPosition; } - void setPosition(const glm::vec3& position); - void setOrientation(const glm::quat& orientation) { setRotation(orientation); } glm::quat getOrientation() const { return getRotation(); } @@ -95,13 +83,8 @@ signals: private: CameraMode _mode; glm::vec3 _position; - float _fieldOfView; // degrees - float _aspectRatio; - float _nearClip; - float _farClip; - glm::vec3 _eyeOffsetPosition; - glm::quat _eyeOffsetOrientation; glm::quat _rotation; + glm::mat4 _projection; glm::vec3 _hmdPosition; glm::quat _hmdRotation; bool _isKeepLookingAt; diff --git a/interface/src/avatar/MyAvatar.cpp b/interface/src/avatar/MyAvatar.cpp index fe4485b5db..e573110157 100644 --- a/interface/src/avatar/MyAvatar.cpp +++ b/interface/src/avatar/MyAvatar.cpp @@ -862,7 +862,7 @@ void MyAvatar::updateLookAtTargetAvatar() { glm::vec3 lookForward = getHead()->getFinalOrientationInWorldFrame() * IDENTITY_FRONT; glm::vec3 cameraPosition = Application::getInstance()->getCamera()->getPosition(); - float smallestAngleTo = glm::radians(Application::getInstance()->getCamera()->getFieldOfView()) / 2.0f; + float smallestAngleTo = glm::radians(DEFAULT_FIELD_OF_VIEW_DEGREES) / 2.0f; const float KEEP_LOOKING_AT_CURRENT_ANGLE_FACTOR = 1.3f; const float GREATEST_LOOKING_AT_DISTANCE = 10.0f; diff --git a/interface/src/devices/OculusManager.cpp b/interface/src/devices/OculusManager.cpp index f4693d3c08..75b6ca4e45 100644 --- a/interface/src/devices/OculusManager.cpp +++ b/interface/src/devices/OculusManager.cpp @@ -184,7 +184,7 @@ void OculusManager::connect() { if (!_camera) { _camera = new Camera; - configureCamera(*_camera, 0, 0); // no need to use screen dimensions; they're ignored + configureCamera(*_camera); // no need to use screen dimensions; they're ignored } #ifdef OVR_CLIENT_DISTORTION if (!_programInitialized) { @@ -449,9 +449,19 @@ void OculusManager::endFrameTiming() { } //Sets the camera FoV and aspect ratio -void OculusManager::configureCamera(Camera& camera, int screenWidth, int screenHeight) { - camera.setAspectRatio(_renderTargetSize.w * 0.5f / _renderTargetSize.h); - camera.setFieldOfView(atan(_eyeFov[0].UpTan) * DEGREES_PER_RADIAN * 2.0f); +void OculusManager::configureCamera(Camera& camera) { + ovrFovPort fov; + if (_activeEye == ovrEye_Count) { + // When not rendering, provide a FOV encompasing both eyes + fov = _eyeFov[0]; + fov.RightTan = _eyeFov[1].RightTan; + } else { + // When rendering, provide the exact FOV + fov = _eyeFov[_activeEye]; + } + // Convert the FOV to the correct projection matrix + glm::mat4 projection = toGlm(ovrMatrix4f_Projection(fov, DEFAULT_NEAR_CLIP, DEFAULT_FAR_CLIP, ovrProjection_RightHanded)); + camera.setProjection(projection); } //Displays everything for the oculus, frame timing must be active @@ -544,7 +554,8 @@ void OculusManager::display(QGLWidget * glCanvas, const glm::quat &bodyOrientati glm::quat orientation; glm::vec3 trackerPosition; - + auto deviceSize = qApp->getDeviceSize(); + ovrTrackingState ts = ovrHmd_GetTrackingState(_ovrHmd, ovr_GetTimeInSeconds()); ovrVector3f ovrHeadPosition = ts.HeadPose.ThePose.Position; @@ -582,9 +593,11 @@ void OculusManager::display(QGLWidget * glCanvas, const glm::quat &bodyOrientati whichCamera.setHmdPosition(trackerPosition); whichCamera.setHmdRotation(orientation); + // Update our camera to what the application camera is doing _camera->setRotation(whichCamera.getRotation()); _camera->setPosition(whichCamera.getPosition()); + configureCamera(*_camera); // Store the latest left and right eye render locations for things that need to know glm::vec3 thisEyePosition = position + trackerPosition + @@ -595,25 +608,17 @@ void OculusManager::display(QGLWidget * glCanvas, const glm::quat &bodyOrientati _camera->update(1.0f / Application::getInstance()->getFps()); glMatrixMode(GL_PROJECTION); - glLoadIdentity(); - const ovrFovPort& port = _eyeFov[_activeEye]; - float nearClip = whichCamera.getNearClip(), farClip = whichCamera.getFarClip(); - glFrustum(-nearClip * port.LeftTan, nearClip * port.RightTan, -nearClip * port.DownTan, - nearClip * port.UpTan, nearClip, farClip); - - ovrRecti & vp = _eyeTextures[eye].Header.RenderViewport; - vp.Size.h = _recommendedTexSize.h * _offscreenRenderScale; - vp.Size.w = _recommendedTexSize.w * _offscreenRenderScale; - - glViewport(vp.Pos.x, vp.Pos.y, vp.Size.w, vp.Size.h); + glLoadMatrixf(glm::value_ptr(_camera->getProjection())); glMatrixMode(GL_MODELVIEW); glLoadIdentity(); - // HACK: instead of passing the stereo eye offset directly in the matrix, pass it in the camera offset - //glTranslatef(_eyeRenderDesc[eye].ViewAdjust.x, _eyeRenderDesc[eye].ViewAdjust.y, _eyeRenderDesc[eye].ViewAdjust.z); + ovrRecti & vp = _eyeTextures[eye].Header.RenderViewport; + vp.Size.h = _recommendedTexSize.h * _offscreenRenderScale; + vp.Size.w = _recommendedTexSize.w * _offscreenRenderScale; + + glViewport(vp.Pos.x, vp.Pos.y, vp.Size.w, vp.Size.h); - _camera->setEyeOffsetPosition(glm::vec3(-_eyeRenderDesc[eye].HmdToEyeViewOffset.x, -_eyeRenderDesc[eye].HmdToEyeViewOffset.y, -_eyeRenderDesc[eye].HmdToEyeViewOffset.z)); Application::getInstance()->displaySide(*_camera, false, RenderArgs::MONO); applicationOverlay.displayOverlayTextureHmd(*_camera); @@ -637,7 +642,6 @@ void OculusManager::display(QGLWidget * glCanvas, const glm::quat &bodyOrientati glPopMatrix(); // restore our normal viewport - auto deviceSize = qApp->getDeviceSize(); glViewport(0, 0, deviceSize.width(), deviceSize.height()); #if 0 @@ -651,16 +655,22 @@ void OculusManager::display(QGLWidget * glCanvas, const glm::quat &bodyOrientati //Wait till time-warp to reduce latency ovr_WaitTillTime(_hmdFrameTiming.TimewarpPointSeconds); +#ifdef DEBUG_RENDER_WITHOUT_DISTORTION + auto fboSize = finalFbo->getSize(); + glBindFramebuffer(GL_READ_FRAMEBUFFER, gpu::GLBackend::getFramebufferID(finalFbo)); + glBlitFramebuffer( + 0, 0, fboSize.x, fboSize.y, + 0, 0, deviceSize.width(), deviceSize.height(), + GL_COLOR_BUFFER_BIT, GL_NEAREST); +#else //Clear the color buffer to ensure that there isnt any residual color //Left over from when OR was not connected. glClear(GL_COLOR_BUFFER_BIT); - glBindTexture(GL_TEXTURE_2D, gpu::GLBackend::getTextureID(finalFbo->getRenderBuffer(0))); - //Renders the distorted mesh onto the screen renderDistortionMesh(eyeRenderPose); - glBindTexture(GL_TEXTURE_2D, 0); +#endif glCanvas->swapBuffers(); #else diff --git a/interface/src/devices/OculusManager.h b/interface/src/devices/OculusManager.h index e41c6e8f9b..a6c3bbf4d5 100644 --- a/interface/src/devices/OculusManager.h +++ b/interface/src/devices/OculusManager.h @@ -60,7 +60,7 @@ public: static void beginFrameTiming(); static void endFrameTiming(); static bool allowSwap(); - static void configureCamera(Camera& camera, int screenWidth, int screenHeight); + static void configureCamera(Camera& camera); static void display(QGLWidget * glCanvas, const glm::quat &bodyOrientation, const glm::vec3 &position, Camera& whichCamera); static void reset(); diff --git a/interface/src/devices/TV3DManager.cpp b/interface/src/devices/TV3DManager.cpp index 5d60bf7e19..d90664131f 100644 --- a/interface/src/devices/TV3DManager.cpp +++ b/interface/src/devices/TV3DManager.cpp @@ -43,9 +43,9 @@ void TV3DManager::connect() { void TV3DManager::setFrustum(Camera& whichCamera) { const double DTR = 0.0174532925; // degree to radians const double IOD = 0.05; //intraocular distance - double fovy = whichCamera.getFieldOfView(); // field of view in y-axis - double nearZ = whichCamera.getNearClip(); // near clipping plane - double screenZ = Application::getInstance()->getViewFrustum()->getFocalLength(); // screen projection plane + double fovy = DEFAULT_FIELD_OF_VIEW_DEGREES; // field of view in y-axis + double nearZ = DEFAULT_NEAR_CLIP; // near clipping plane + double screenZ = 0.25f; // screen projection plane double top = nearZ * tan(DTR * fovy / 2.0); //sets top of frustum based on fovy and near clipping plane double right = _aspect * top; // sets right of frustum based on aspect ratio @@ -81,8 +81,8 @@ void TV3DManager::configureCamera(Camera& whichCamera, int screenWidth, int scre } void TV3DManager::display(Camera& whichCamera) { - double nearZ = whichCamera.getNearClip(); // near clipping plane - double farZ = whichCamera.getFarClip(); // far clipping plane + double nearZ = DEFAULT_NEAR_CLIP; // near clipping plane + double farZ = DEFAULT_FAR_CLIP; // far clipping plane // left eye portal int portalX = 0; @@ -125,7 +125,6 @@ void TV3DManager::display(Camera& whichCamera) { glMatrixMode(GL_MODELVIEW); glLoadIdentity(); - eyeCamera.setEyeOffsetPosition(glm::vec3(-_activeEye->modelTranslation,0,0)); Application::getInstance()->displaySide(eyeCamera, false, RenderArgs::MONO); applicationOverlay.displayOverlayTextureStereo(whichCamera, _aspect, fov); @@ -154,7 +153,6 @@ void TV3DManager::display(Camera& whichCamera) { glMatrixMode(GL_MODELVIEW); glLoadIdentity(); - eyeCamera.setEyeOffsetPosition(glm::vec3(-_activeEye->modelTranslation,0,0)); Application::getInstance()->displaySide(eyeCamera, false, RenderArgs::MONO); applicationOverlay.displayOverlayTextureStereo(whichCamera, _aspect, fov); diff --git a/libraries/octree/src/OctreeHeadlessViewer.cpp b/libraries/octree/src/OctreeHeadlessViewer.cpp index 0da694833a..90840854ad 100644 --- a/libraries/octree/src/OctreeHeadlessViewer.cpp +++ b/libraries/octree/src/OctreeHeadlessViewer.cpp @@ -20,10 +20,7 @@ OctreeHeadlessViewer::OctreeHeadlessViewer() : _boundaryLevelAdjust(0), _maxPacketsPerSecond(DEFAULT_MAX_OCTREE_PPS) { - _viewFrustum.setFieldOfView(DEFAULT_FIELD_OF_VIEW_DEGREES); - _viewFrustum.setAspectRatio(DEFAULT_ASPECT_RATIO); - _viewFrustum.setNearClip(DEFAULT_NEAR_CLIP); - _viewFrustum.setFarClip(DEFAULT_FAR_CLIP); + _viewFrustum.setProjection(glm::perspective(glm::radians(DEFAULT_FIELD_OF_VIEW_DEGREES), DEFAULT_ASPECT_RATIO, DEFAULT_NEAR_CLIP, DEFAULT_FAR_CLIP)); } OctreeHeadlessViewer::~OctreeHeadlessViewer() { @@ -67,7 +64,8 @@ void OctreeHeadlessViewer::queryOctree() { _octreeQuery.setCameraAspectRatio(_viewFrustum.getAspectRatio()); _octreeQuery.setCameraNearClip(_viewFrustum.getNearClip()); _octreeQuery.setCameraFarClip(_viewFrustum.getFarClip()); - _octreeQuery.setCameraEyeOffsetPosition(_viewFrustum.getEyeOffsetPosition()); + _octreeQuery.setCameraEyeOffsetPosition(glm::vec3()); + _octreeQuery.setOctreeSizeScale(getVoxelSizeScale()); _octreeQuery.setBoundaryLevelAdjust(getBoundaryLevelAdjust()); diff --git a/libraries/octree/src/ViewFrustum.cpp b/libraries/octree/src/ViewFrustum.cpp index 11ed24799d..2694314b57 100644 --- a/libraries/octree/src/ViewFrustum.cpp +++ b/libraries/octree/src/ViewFrustum.cpp @@ -14,7 +14,7 @@ #include #include #include - +#include #include #include @@ -37,6 +37,36 @@ void ViewFrustum::setOrientation(const glm::quat& orientationAsQuaternion) { _direction = glm::vec3(orientationAsQuaternion * glm::vec4(IDENTITY_FRONT, 0.0f)); } +// Order cooresponds to the order defined in the BoxVertex enum. +static const glm::vec4 NDC_VALUES[8] = { + glm::vec4(-1, -1, -1, 1), + glm::vec4(1, -1, -1, 1), + glm::vec4(1, 1, -1, 1), + glm::vec4(-1, 1, -1, 1), + glm::vec4(-1, -1, 1, 1), + glm::vec4(1, -1, 1, 1), + glm::vec4(1, 1, 1, 1), + glm::vec4(-1, 1, 1, 1), +}; + +void ViewFrustum::setProjection(const glm::mat4& projection) { + _projection = projection; + _inverseProjection = glm::inverse(projection); + + // compute our dimensions the usual way + for (int i = 0; i < 8; ++i) { + _corners[i] = _inverseProjection * NDC_VALUES[i]; + _corners[i] /= _corners[i].w; + } + _nearClip = -_corners[BOTTOM_LEFT_NEAR].z; + _farClip = -_corners[BOTTOM_LEFT_FAR].z; + _aspectRatio = (_corners[TOP_RIGHT_NEAR].x - _corners[BOTTOM_LEFT_NEAR].x) / + (_corners[TOP_RIGHT_NEAR].y - _corners[BOTTOM_LEFT_NEAR].y); + glm::vec3 right = glm::normalize(glm::vec3(_corners[TOP_RIGHT_NEAR])); + glm::vec3 left = glm::normalize(glm::vec3(_corners[TOP_LEFT_NEAR])); + _fieldOfView = abs(glm::degrees(glm::angle(right, left))); +} + // ViewFrustum::calculateViewFrustum() // // Description: this will calculate the view frustum bounds for a given position and direction @@ -45,48 +75,16 @@ void ViewFrustum::setOrientation(const glm::quat& orientationAsQuaternion) { // http://www.lighthouse3d.com/tutorials/view-frustum-culling/view-frustums-shape/ // void ViewFrustum::calculate() { - if (_orthographic) { - calculateOrthographic(); - return; - } - - // compute the off-axis frustum parameters as we would for glFrustum - float left, right, bottom, top, nearVal, farVal; - glm::vec4 nearClipPlane, farClipPlane; - computeOffAxisFrustum(left, right, bottom, top, nearVal, farVal, nearClipPlane, farClipPlane); - - // start with the corners of the near frustum window - glm::vec3 topLeft(left, top, -nearVal); - glm::vec3 topRight(right, top, -nearVal); - glm::vec3 bottomLeft(left, bottom, -nearVal); - glm::vec3 bottomRight(right, bottom, -nearVal); // find the intersections of the rays through the corners with the clip planes in view space, // then transform them to world space - glm::mat4 worldMatrix = glm::translate(_position) * glm::mat4(glm::mat3(_right, _up, -_direction)) * - glm::translate(_eyeOffsetPosition) * glm::mat4_cast(_eyeOffsetOrientation); - _farTopLeft = glm::vec3(worldMatrix * glm::vec4(topLeft * - (-farClipPlane.w / glm::dot(topLeft, glm::vec3(farClipPlane))), 1.0f)); - _farTopRight = glm::vec3(worldMatrix * glm::vec4(topRight * - (-farClipPlane.w / glm::dot(topRight, glm::vec3(farClipPlane))), 1.0f)); - _farBottomLeft = glm::vec3(worldMatrix * glm::vec4(bottomLeft * - (-farClipPlane.w / glm::dot(bottomLeft, glm::vec3(farClipPlane))), 1.0f)); - _farBottomRight = glm::vec3(worldMatrix * glm::vec4(bottomRight * - (-farClipPlane.w / glm::dot(bottomRight, glm::vec3(farClipPlane))), 1.0f)); - _nearTopLeft = glm::vec3(worldMatrix * glm::vec4(topLeft * - (-nearClipPlane.w / glm::dot(topLeft, glm::vec3(nearClipPlane))), 1.0f)); - _nearTopRight = glm::vec3(worldMatrix * glm::vec4(topRight * - (-nearClipPlane.w / glm::dot(topRight, glm::vec3(nearClipPlane))), 1.0f)); - _nearBottomLeft = glm::vec3(worldMatrix * glm::vec4(bottomLeft * - (-nearClipPlane.w / glm::dot(bottomLeft, glm::vec3(nearClipPlane))), 1.0f)); - _nearBottomRight = glm::vec3(worldMatrix * glm::vec4(bottomRight * - (-nearClipPlane.w / glm::dot(bottomRight, glm::vec3(nearClipPlane))), 1.0f)); - - // compute the offset position and axes in world space - _offsetPosition = glm::vec3(worldMatrix * glm::vec4(0.0f, 0.0f, 0.0f, 1.0f)); - _offsetDirection = glm::vec3(worldMatrix * glm::vec4(0.0f, 0.0f, -1.0f, 0.0f)); - _offsetUp = glm::vec3(worldMatrix * glm::vec4(0.0f, 1.0f, 0.0f, 0.0f)); - _offsetRight = glm::vec3(worldMatrix * glm::vec4(1.0f, 0.0f, 0.0f, 0.0f)); + glm::mat4 worldMatrix = glm::translate(_position) * glm::mat4(glm::mat3(_right, _up, -_direction)); + glm::vec4 v; + for (int i = 0; i < 8; ++i) { + v = worldMatrix * _corners[i]; + v /= v.w; + _cornersWorld[i] = glm::vec3(v); + } // compute the six planes // The planes are defined such that the normal points towards the inside of the view frustum. @@ -99,73 +97,26 @@ void ViewFrustum::calculate() { // the function set3Points assumes that the points are given in counter clockwise order, assume you // are inside the frustum, facing the plane. Start with any point, and go counter clockwise for // three consecutive points - - _planes[TOP_PLANE ].set3Points(_nearTopRight,_nearTopLeft,_farTopLeft); - _planes[BOTTOM_PLANE].set3Points(_nearBottomLeft,_nearBottomRight,_farBottomRight); - _planes[LEFT_PLANE ].set3Points(_nearBottomLeft,_farBottomLeft,_farTopLeft); - _planes[RIGHT_PLANE ].set3Points(_farBottomRight,_nearBottomRight,_nearTopRight); - _planes[NEAR_PLANE ].set3Points(_nearBottomRight,_nearBottomLeft,_nearTopLeft); - _planes[FAR_PLANE ].set3Points(_farBottomLeft,_farBottomRight,_farTopRight); + _planes[TOP_PLANE].set3Points(_cornersWorld[TOP_RIGHT_NEAR], _cornersWorld[TOP_LEFT_NEAR], _cornersWorld[TOP_LEFT_FAR]); + _planes[BOTTOM_PLANE].set3Points(_cornersWorld[BOTTOM_LEFT_NEAR], _cornersWorld[BOTTOM_RIGHT_NEAR], _cornersWorld[BOTTOM_RIGHT_FAR]); + _planes[LEFT_PLANE].set3Points(_cornersWorld[BOTTOM_LEFT_NEAR], _cornersWorld[BOTTOM_LEFT_FAR], _cornersWorld[TOP_LEFT_FAR]); + _planes[RIGHT_PLANE].set3Points(_cornersWorld[BOTTOM_RIGHT_FAR], _cornersWorld[BOTTOM_RIGHT_NEAR], _cornersWorld[TOP_RIGHT_FAR]); + _planes[NEAR_PLANE].set3Points(_cornersWorld[BOTTOM_RIGHT_NEAR], _cornersWorld[BOTTOM_LEFT_NEAR], _cornersWorld[TOP_LEFT_NEAR]); + _planes[FAR_PLANE].set3Points(_cornersWorld[BOTTOM_LEFT_FAR], _cornersWorld[BOTTOM_RIGHT_FAR], _cornersWorld[TOP_RIGHT_FAR]); // Also calculate our projection matrix in case people want to project points... // Projection matrix : Field of View, ratio, display range : near to far - const float CLIP_NUDGE = 1.0f; - float farClip = (_farClip != _nearClip) ? _farClip : _nearClip + CLIP_NUDGE; // don't allow near and far to be equal - glm::mat4 projection = glm::perspective(_fieldOfView, _aspectRatio, _nearClip, farClip); glm::vec3 lookAt = _position + _direction; glm::mat4 view = glm::lookAt(_position, lookAt, _up); // Our ModelViewProjection : multiplication of our 3 matrices (note: model is identity, so we can drop it) - _ourModelViewProjectionMatrix = projection * view; // Remember, matrix multiplication is the other way around + _ourModelViewProjectionMatrix = _projection * view; // Remember, matrix multiplication is the other way around // Set up our keyhole bounding box... glm::vec3 corner = _position - _keyholeRadius; _keyholeBoundingCube = AACube(corner,(_keyholeRadius * 2.0f)); } -void ViewFrustum::calculateOrthographic() { - float halfWidth = _width * 0.5f; - float halfHeight = _height * 0.5f; - - // find the corners of the view box in world space - glm::mat4 worldMatrix = glm::translate(_position) * glm::mat4(glm::mat3(_right, _up, -_direction)) * - glm::translate(_eyeOffsetPosition) * glm::mat4_cast(_eyeOffsetOrientation); - _farTopLeft = glm::vec3(worldMatrix * glm::vec4(-halfWidth, halfHeight, -_farClip, 1.0f)); - _farTopRight = glm::vec3(worldMatrix * glm::vec4(halfWidth, halfHeight, -_farClip, 1.0f)); - _farBottomLeft = glm::vec3(worldMatrix * glm::vec4(-halfWidth, -halfHeight, -_farClip, 1.0f)); - _farBottomRight = glm::vec3(worldMatrix * glm::vec4(halfWidth, -halfHeight, -_farClip, 1.0f)); - _nearTopLeft = glm::vec3(worldMatrix * glm::vec4(-halfWidth, halfHeight, -_nearClip, 1.0f)); - _nearTopRight = glm::vec3(worldMatrix * glm::vec4(halfWidth, halfHeight, -_nearClip, 1.0f)); - _nearBottomLeft = glm::vec3(worldMatrix * glm::vec4(-halfWidth, -halfHeight, -_nearClip, 1.0f)); - _nearBottomRight = glm::vec3(worldMatrix * glm::vec4(halfWidth, -halfHeight, -_nearClip, 1.0f)); - - // compute the offset position and axes in world space - _offsetPosition = glm::vec3(worldMatrix * glm::vec4(0.0f, 0.0f, 0.0f, 1.0f)); - _offsetDirection = glm::vec3(worldMatrix * glm::vec4(0.0f, 0.0f, -1.0f, 0.0f)); - _offsetUp = glm::vec3(worldMatrix * glm::vec4(0.0f, 1.0f, 0.0f, 0.0f)); - _offsetRight = glm::vec3(worldMatrix * glm::vec4(1.0f, 0.0f, 0.0f, 0.0f)); - - _planes[TOP_PLANE].set3Points(_nearTopRight, _nearTopLeft, _farTopLeft); - _planes[BOTTOM_PLANE].set3Points(_nearBottomLeft, _nearBottomRight, _farBottomRight); - _planes[LEFT_PLANE].set3Points(_nearBottomLeft, _farBottomLeft, _farTopLeft); - _planes[RIGHT_PLANE].set3Points(_farBottomRight, _nearBottomRight, _nearTopRight); - _planes[NEAR_PLANE].set3Points(_nearBottomRight, _nearBottomLeft, _nearTopLeft); - _planes[FAR_PLANE].set3Points(_farBottomLeft, _farBottomRight, _farTopRight); - - // Also calculate our projection matrix in case people want to project points... - // Projection matrix : Field of View, ratio, display range : near to far - glm::mat4 projection = glm::ortho(-halfWidth, halfWidth, -halfHeight, halfHeight, _nearClip, _farClip); - glm::vec3 lookAt = _position + _direction; - glm::mat4 view = glm::lookAt(_position, lookAt, _up); - - // Our ModelViewProjection : multiplication of our 3 matrices (note: model is identity, so we can drop it) - _ourModelViewProjectionMatrix = projection * view; // Remember, matrix multiplication is the other way around - - // Set up our keyhole bounding box... - glm::vec3 corner = _position - _keyholeRadius; - _keyholeBoundingCube = AACube(corner, (_keyholeRadius * 2.0f)); -} - //enum { TOP_PLANE = 0, BOTTOM_PLANE, LEFT_PLANE, RIGHT_PLANE, NEAR_PLANE, FAR_PLANE }; const char* ViewFrustum::debugPlaneName (int plane) const { switch (plane) { @@ -305,7 +256,6 @@ ViewFrustum::location ViewFrustum::pointInFrustum(const glm::vec3& point) const return keyholeResult; // escape early will be the value from checking the keyhole } } - return regularResult; } @@ -429,9 +379,7 @@ bool ViewFrustum::matches(const ViewFrustum& compareTo, bool debug) const { testMatches(compareTo._aspectRatio, _aspectRatio) && testMatches(compareTo._nearClip, _nearClip) && testMatches(compareTo._farClip, _farClip) && - testMatches(compareTo._focalLength, _focalLength) && - testMatches(compareTo._eyeOffsetPosition, _eyeOffsetPosition) && - testMatches(compareTo._eyeOffsetOrientation, _eyeOffsetOrientation); + testMatches(compareTo._focalLength, _focalLength); if (!result && debug) { qCDebug(octree, "ViewFrustum::matches()... result=%s", debug::valueOf(result)); @@ -466,15 +414,6 @@ bool ViewFrustum::matches(const ViewFrustum& compareTo, bool debug) const { qCDebug(octree, "%s -- compareTo._focalLength=%f _focalLength=%f", (testMatches(compareTo._focalLength, _focalLength) ? "MATCHES " : "NO MATCH"), compareTo._focalLength, _focalLength); - qCDebug(octree, "%s -- compareTo._eyeOffsetPosition=%f,%f,%f _eyeOffsetPosition=%f,%f,%f", - (testMatches(compareTo._eyeOffsetPosition, _eyeOffsetPosition) ? "MATCHES " : "NO MATCH"), - compareTo._eyeOffsetPosition.x, compareTo._eyeOffsetPosition.y, compareTo._eyeOffsetPosition.z, - _eyeOffsetPosition.x, _eyeOffsetPosition.y, _eyeOffsetPosition.z); - qCDebug(octree, "%s -- compareTo._eyeOffsetOrientation=%f,%f,%f,%f _eyeOffsetOrientation=%f,%f,%f,%f", - (testMatches(compareTo._eyeOffsetOrientation, _eyeOffsetOrientation) ? "MATCHES " : "NO MATCH"), - compareTo._eyeOffsetOrientation.x, compareTo._eyeOffsetOrientation.y, - compareTo._eyeOffsetOrientation.z, compareTo._eyeOffsetOrientation.w, - _eyeOffsetOrientation.x, _eyeOffsetOrientation.y, _eyeOffsetOrientation.z, _eyeOffsetOrientation.w); } return result; } @@ -485,9 +424,6 @@ bool ViewFrustum::isVerySimilar(const ViewFrustum& compareTo, bool debug) const const float POSITION_SIMILAR_ENOUGH = 5.0f; // 5 meters float positionDistance = glm::distance(_position, compareTo._position); - const float EYEOFFSET_POSITION_SIMILAR_ENOUGH = 0.15f; // 0.15 meters - float eyeOffsetpositionDistance = glm::distance(_eyeOffsetPosition, compareTo._eyeOffsetPosition); - // Compute the angular distance between the two orientations const float ORIENTATION_SIMILAR_ENOUGH = 10.0f; // 10 degrees in any direction glm::quat dQOrientation = _orientation * glm::inverse(compareTo._orientation); @@ -496,23 +432,14 @@ bool ViewFrustum::isVerySimilar(const ViewFrustum& compareTo, bool debug) const angleOrientation = 0.0f; } - glm::quat dQEyeOffsetOrientation = _eyeOffsetOrientation * glm::inverse(compareTo._eyeOffsetOrientation); - float angleEyeOffsetOrientation = compareTo._eyeOffsetOrientation == _eyeOffsetOrientation - ? 0.0f : glm::degrees(glm::angle(dQEyeOffsetOrientation)); - if (isNaN(angleEyeOffsetOrientation)) { - angleEyeOffsetOrientation = 0.0f; - } - bool result = - testMatches(0, positionDistance, POSITION_SIMILAR_ENOUGH) && - testMatches(0, angleOrientation, ORIENTATION_SIMILAR_ENOUGH) && + testMatches(0, positionDistance, POSITION_SIMILAR_ENOUGH) && + testMatches(0, angleOrientation, ORIENTATION_SIMILAR_ENOUGH) && testMatches(compareTo._fieldOfView, _fieldOfView) && testMatches(compareTo._aspectRatio, _aspectRatio) && testMatches(compareTo._nearClip, _nearClip) && testMatches(compareTo._farClip, _farClip) && - testMatches(compareTo._focalLength, _focalLength) && - testMatches(0, eyeOffsetpositionDistance, EYEOFFSET_POSITION_SIMILAR_ENOUGH) && - testMatches(0, angleEyeOffsetOrientation, ORIENTATION_SIMILAR_ENOUGH); + testMatches(compareTo._focalLength, _focalLength); if (!result && debug) { @@ -529,7 +456,7 @@ bool ViewFrustum::isVerySimilar(const ViewFrustum& compareTo, bool debug) const qCDebug(octree, "%s -- angleOrientation=%f", (testMatches(0, angleOrientation, ORIENTATION_SIMILAR_ENOUGH) ? "IS SIMILAR ENOUGH " : "IS NOT SIMILAR ENOUGH"), angleOrientation); - + qCDebug(octree, "%s -- compareTo._fieldOfView=%f _fieldOfView=%f", (testMatches(compareTo._fieldOfView, _fieldOfView) ? "MATCHES " : "NO MATCH"), compareTo._fieldOfView, _fieldOfView); @@ -545,19 +472,6 @@ bool ViewFrustum::isVerySimilar(const ViewFrustum& compareTo, bool debug) const qCDebug(octree, "%s -- compareTo._focalLength=%f _focalLength=%f", (testMatches(compareTo._focalLength, _focalLength) ? "MATCHES " : "NO MATCH"), compareTo._focalLength, _focalLength); - - qCDebug(octree, "%s -- compareTo._eyeOffsetPosition=%f,%f,%f _eyeOffsetPosition=%f,%f,%f", - (testMatches(compareTo._eyeOffsetPosition, _eyeOffsetPosition, POSITION_SIMILAR_ENOUGH) ? "IS SIMILAR ENOUGH " : "IS NOT SIMILAR ENOUGH"), - compareTo._eyeOffsetPosition.x, compareTo._eyeOffsetPosition.y, compareTo._eyeOffsetPosition.z, - _eyeOffsetPosition.x, _eyeOffsetPosition.y, _eyeOffsetPosition.z); - - qCDebug(octree, "%s -- eyeOffsetpositionDistance=%f", - (testMatches(0,eyeOffsetpositionDistance, EYEOFFSET_POSITION_SIMILAR_ENOUGH) ? "IS SIMILAR ENOUGH " : "IS NOT SIMILAR ENOUGH"), - eyeOffsetpositionDistance); - - qCDebug(octree, "%s -- angleEyeOffsetOrientation=%f", - (testMatches(0, angleEyeOffsetOrientation, ORIENTATION_SIMILAR_ENOUGH) ? "IS SIMILAR ENOUGH " : "IS NOT SIMILAR ENOUGH"), - angleEyeOffsetOrientation); } return result; } @@ -570,39 +484,19 @@ PickRay ViewFrustum::computePickRay(float x, float y) { } void ViewFrustum::computePickRay(float x, float y, glm::vec3& origin, glm::vec3& direction) const { - origin = _nearTopLeft + x * (_nearTopRight - _nearTopLeft) + y * (_nearBottomLeft - _nearTopLeft); - direction = glm::normalize(origin - (_position + _orientation * _eyeOffsetPosition)); + origin = _cornersWorld[TOP_LEFT_NEAR] + x * (_cornersWorld[TOP_RIGHT_NEAR] - _cornersWorld[TOP_LEFT_NEAR]) + + y * (_cornersWorld[BOTTOM_LEFT_NEAR] - _cornersWorld[TOP_LEFT_NEAR]); + direction = glm::normalize(origin - _position); } void ViewFrustum::computeOffAxisFrustum(float& left, float& right, float& bottom, float& top, float& nearValue, float& farValue, glm::vec4& nearClipPlane, glm::vec4& farClipPlane) const { - // compute our dimensions the usual way - float hheight = _nearClip * tanf(_fieldOfView * 0.5f * RADIANS_PER_DEGREE); - float hwidth = _aspectRatio * hheight; - if (isOrthographic()) { - hheight = getHeight(); - hwidth = getWidth(); - } - - // get our frustum corners in view space - glm::mat4 eyeMatrix = glm::mat4_cast(glm::inverse(_eyeOffsetOrientation)) * glm::translate(-_eyeOffsetPosition); - glm::vec4 corners[8]; - float farScale = _farClip / _nearClip; - corners[0] = eyeMatrix * glm::vec4(-hwidth, -hheight, -_nearClip, 1.0f); - corners[1] = eyeMatrix * glm::vec4(hwidth, -hheight, -_nearClip, 1.0f); - corners[2] = eyeMatrix * glm::vec4(hwidth, hheight, -_nearClip, 1.0f); - corners[3] = eyeMatrix * glm::vec4(-hwidth, hheight, -_nearClip, 1.0f); - corners[4] = eyeMatrix * glm::vec4(-hwidth * farScale, -hheight * farScale, -_farClip, 1.0f); - corners[5] = eyeMatrix * glm::vec4(hwidth * farScale, -hheight * farScale, -_farClip, 1.0f); - corners[6] = eyeMatrix * glm::vec4(hwidth * farScale, hheight * farScale, -_farClip, 1.0f); - corners[7] = eyeMatrix * glm::vec4(-hwidth * farScale, hheight * farScale, -_farClip, 1.0f); - // find the minimum and maximum z values, which will be our near and far clip distances nearValue = FLT_MAX; farValue = -FLT_MAX; for (int i = 0; i < 8; i++) { - nearValue = min(nearValue, -corners[i].z); - farValue = max(farValue, -corners[i].z); + nearValue = min(nearValue, -_corners[i].z); + farValue = max(farValue, -_corners[i].z); } // make sure the near clip isn't too small to be valid @@ -610,9 +504,9 @@ void ViewFrustum::computeOffAxisFrustum(float& left, float& right, float& bottom nearValue = max(MIN_NEAR, nearValue); // get the near/far normal and use it to find the clip planes - glm::vec4 normal = eyeMatrix * glm::vec4(0.0f, 0.0f, 1.0f, 0.0f); - nearClipPlane = glm::vec4(-normal.x, -normal.y, -normal.z, glm::dot(normal, corners[0])); - farClipPlane = glm::vec4(normal.x, normal.y, normal.z, -glm::dot(normal, corners[4])); + glm::vec4 normal = glm::vec4(0.0f, 0.0f, 1.0f, 0.0f); + nearClipPlane = glm::vec4(-normal.x, -normal.y, -normal.z, glm::dot(normal, _corners[0])); + farClipPlane = glm::vec4(normal.x, normal.y, normal.z, -glm::dot(normal, _corners[4])); // compute the focal proportion (zero is near clip, one is far clip) float focalProportion = (_focalLength - _nearClip) / (_farClip - _nearClip); @@ -623,7 +517,7 @@ void ViewFrustum::computeOffAxisFrustum(float& left, float& right, float& bottom bottom = FLT_MAX; top = -FLT_MAX; for (int i = 0; i < 4; i++) { - glm::vec4 corner = glm::mix(corners[i], corners[i + 4], focalProportion); + glm::vec4 corner = glm::mix(_corners[i], _corners[i + 4], focalProportion); glm::vec4 intersection = corner * (-nearValue / corner.z); left = min(left, intersection.x); right = max(right, intersection.x); @@ -644,9 +538,6 @@ void ViewFrustum::printDebugDetails() const { qCDebug(octree, "_nearClip=%f", _nearClip); qCDebug(octree, "_farClip=%f", _farClip); qCDebug(octree, "_focalLength=%f", _focalLength); - qCDebug(octree, "_eyeOffsetPosition=%f,%f,%f", _eyeOffsetPosition.x, _eyeOffsetPosition.y, _eyeOffsetPosition.z ); - qCDebug(octree, "_eyeOffsetOrientation=%f,%f,%f,%f", _eyeOffsetOrientation.x, _eyeOffsetOrientation.y, _eyeOffsetOrientation.z, - _eyeOffsetOrientation.w ); } glm::vec2 ViewFrustum::projectPoint(glm::vec3 point, bool& pointInView) const { @@ -846,20 +737,7 @@ float ViewFrustum::distanceToCamera(const glm::vec3& point) const { } void ViewFrustum::evalProjectionMatrix(glm::mat4& proj) const { - if (isOrthographic()) { - glm::vec3 frustumCenter = glm::inverse( _orientation) * _position; - - proj = glm::ortho(frustumCenter.x -0.5f * getWidth(), - frustumCenter.x +0.5f * getWidth(), - frustumCenter.y -0.5f * getHeight(), - frustumCenter.y +0.5f * getHeight(), - -getFarClip(), -getNearClip()); - } else { - float left, right, bottom, top, near, far; - glm::vec4 clip0, clip1; - computeOffAxisFrustum(left, right, bottom, top, near, far, clip0, clip1); - proj = glm::perspective(glm::radians(getFieldOfView()), getAspectRatio(), getNearClip(), getFarClip()); - } + proj = _projection; } void ViewFrustum::evalViewTransform(Transform& view) const { diff --git a/libraries/octree/src/ViewFrustum.h b/libraries/octree/src/ViewFrustum.h index 5b20126293..0422120e51 100644 --- a/libraries/octree/src/ViewFrustum.h +++ b/libraries/octree/src/ViewFrustum.h @@ -50,19 +50,11 @@ public: const glm::vec3& getRight() const { return _right; } // setters for lens attributes - void setOrthographic(bool orthographic) { _orthographic = orthographic; } - void setWidth(float width) { _width = width; } - void setHeight(float height) { _height = height; } - void setFieldOfView(float f) { _fieldOfView = f; } - void setAspectRatio(float a) { _aspectRatio = a; } - void setNearClip(float n) { _nearClip = n; } - void setFarClip(float f) { _farClip = f; } - void setFocalLength(float length) { _focalLength = length; } - void setEyeOffsetPosition(const glm::vec3& p) { _eyeOffsetPosition = p; } - void setEyeOffsetOrientation(const glm::quat& o) { _eyeOffsetOrientation = o; } + void setProjection(const glm::mat4 & projection); + void getFocalLength(float focalLength) { _focalLength = focalLength; } // getters for lens attributes - bool isOrthographic() const { return _orthographic; } + const glm::mat4 getProjection() const { return _projection; }; float getWidth() const { return _width; } float getHeight() const { return _height; } float getFieldOfView() const { return _fieldOfView; } @@ -70,23 +62,16 @@ public: float getNearClip() const { return _nearClip; } float getFarClip() const { return _farClip; } float getFocalLength() const { return _focalLength; } - const glm::vec3& getEyeOffsetPosition() const { return _eyeOffsetPosition; } - const glm::quat& getEyeOffsetOrientation() const { return _eyeOffsetOrientation; } - const glm::vec3& getOffsetPosition() const { return _offsetPosition; } - const glm::vec3& getOffsetDirection() const { return _offsetDirection; } - const glm::vec3& getOffsetUp() const { return _offsetUp; } - const glm::vec3& getOffsetRight() const { return _offsetRight; } + const glm::vec3& getFarTopLeft() const { return _cornersWorld[TOP_LEFT_FAR]; } + const glm::vec3& getFarTopRight() const { return _cornersWorld[TOP_RIGHT_FAR]; } + const glm::vec3& getFarBottomLeft() const { return _cornersWorld[BOTTOM_LEFT_FAR]; } + const glm::vec3& getFarBottomRight() const { return _cornersWorld[BOTTOM_RIGHT_FAR]; } - const glm::vec3& getFarTopLeft() const { return _farTopLeft; } - const glm::vec3& getFarTopRight() const { return _farTopRight; } - const glm::vec3& getFarBottomLeft() const { return _farBottomLeft; } - const glm::vec3& getFarBottomRight() const { return _farBottomRight; } - - const glm::vec3& getNearTopLeft() const { return _nearTopLeft; } - const glm::vec3& getNearTopRight() const { return _nearTopRight; } - const glm::vec3& getNearBottomLeft() const { return _nearBottomLeft; } - const glm::vec3& getNearBottomRight() const { return _nearBottomRight; } + const glm::vec3& getNearTopLeft() const { return _cornersWorld[TOP_LEFT_NEAR]; } + const glm::vec3& getNearTopRight() const { return _cornersWorld[TOP_RIGHT_NEAR]; } + const glm::vec3& getNearBottomLeft() const { return _cornersWorld[BOTTOM_LEFT_NEAR]; } + const glm::vec3& getNearBottomRight() const { return _cornersWorld[BOTTOM_RIGHT_NEAR]; } // get/set for keyhole attribute void setKeyholeRadius(float keyholdRadius) { _keyholeRadius = keyholdRadius; } @@ -132,49 +117,33 @@ private: ViewFrustum::location cubeInKeyhole(const AACube& cube) const; ViewFrustum::location boxInKeyhole(const AABox& box) const; - void calculateOrthographic(); - // camera location/orientation attributes - glm::vec3 _position = glm::vec3(0.0f); // the position in world-frame - glm::quat _orientation = glm::quat(); + glm::vec3 _position; // the position in world-frame + glm::quat _orientation; + + // Lens attributes + glm::mat4 _projection; // calculated for orientation glm::vec3 _direction = IDENTITY_FRONT; glm::vec3 _up = IDENTITY_UP; glm::vec3 _right = IDENTITY_RIGHT; - // Lens attributes - bool _orthographic = false; + // keyhole attributes + float _keyholeRadius = DEFAULT_KEYHOLE_RADIUS; + AACube _keyholeBoundingCube; + + // Calculated values + glm::mat4 _inverseProjection; float _width = 1.0f; float _height = 1.0f; float _aspectRatio = 1.0f; float _nearClip = DEFAULT_NEAR_CLIP; float _farClip = DEFAULT_FAR_CLIP; float _focalLength = 0.25f; - glm::vec3 _eyeOffsetPosition = glm::vec3(0.0f); - glm::quat _eyeOffsetOrientation = glm::quat(); - - // in Degrees, doesn't apply to HMD like Oculus float _fieldOfView = DEFAULT_FIELD_OF_VIEW_DEGREES; - - // keyhole attributes - float _keyholeRadius = DEFAULT_KEYHOLE_RADIUS; - AACube _keyholeBoundingCube; - - - // Calculated values - glm::vec3 _offsetPosition = glm::vec3(0.0f); - glm::vec3 _offsetDirection = glm::vec3(0.0f); - glm::vec3 _offsetUp = glm::vec3(0.0f); - glm::vec3 _offsetRight = glm::vec3(0.0f); - glm::vec3 _farTopLeft = glm::vec3(0.0f); - glm::vec3 _farTopRight = glm::vec3(0.0f); - glm::vec3 _farBottomLeft = glm::vec3(0.0f); - glm::vec3 _farBottomRight = glm::vec3(0.0f); - glm::vec3 _nearTopLeft = glm::vec3(0.0f); - glm::vec3 _nearTopRight = glm::vec3(0.0f); - glm::vec3 _nearBottomLeft = glm::vec3(0.0f); - glm::vec3 _nearBottomRight = glm::vec3(0.0f); + glm::vec4 _corners[8]; + glm::vec3 _cornersWorld[8]; enum { TOP_PLANE = 0, BOTTOM_PLANE, LEFT_PLANE, RIGHT_PLANE, NEAR_PLANE, FAR_PLANE }; ::Plane _planes[6]; // How will this be used? From 464925ecc75c04b2b001791f5aeced9ed5b55ef8 Mon Sep 17 00:00:00 2001 From: Brad Davis Date: Tue, 12 May 2015 01:15:30 -0700 Subject: [PATCH 10/15] Fixing the assignment client --- assignment-client/src/octree/OctreeQueryNode.cpp | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/assignment-client/src/octree/OctreeQueryNode.cpp b/assignment-client/src/octree/OctreeQueryNode.cpp index 30b9ef205b..856b6a1c00 100644 --- a/assignment-client/src/octree/OctreeQueryNode.cpp +++ b/assignment-client/src/octree/OctreeQueryNode.cpp @@ -258,11 +258,11 @@ bool OctreeQueryNode::updateCurrentViewFrustum() { float originalFOV = getCameraFov(); float wideFOV = originalFOV + VIEW_FRUSTUM_FOV_OVERSEND; - newestViewFrustum.setFieldOfView(wideFOV); // hack - newestViewFrustum.setAspectRatio(getCameraAspectRatio()); - newestViewFrustum.setNearClip(getCameraNearClip()); - newestViewFrustum.setFarClip(getCameraFarClip()); - newestViewFrustum.setEyeOffsetPosition(getCameraEyeOffsetPosition()); + newestViewFrustum.setProjection(glm::perspective( + glm::radians(wideFOV), // hack + getCameraAspectRatio(), + getCameraNearClip(), + getCameraFarClip())); // if there has been a change, then recalculate if (!newestViewFrustum.isVerySimilar(_currentViewFrustum)) { From 00864afe53aefd06ffd62a8c2652dba74dab8e04 Mon Sep 17 00:00:00 2001 From: Atlante45 Date: Tue, 12 May 2015 17:06:40 +0200 Subject: [PATCH 11/15] Quiet compiler --- interface/src/devices/DdeFaceTracker.cpp | 2 +- .../entities/src/SimpleEntitySimulation.cpp | 2 -- libraries/model/src/model/Stage.cpp | 25 +++++++++++-------- libraries/model/src/model/TextureStorage.h | 5 ++-- libraries/render-utils/src/GlowEffect.cpp | 12 --------- 5 files changed, 18 insertions(+), 28 deletions(-) diff --git a/interface/src/devices/DdeFaceTracker.cpp b/interface/src/devices/DdeFaceTracker.cpp index 6ed253c1ec..14728f021b 100644 --- a/interface/src/devices/DdeFaceTracker.cpp +++ b/interface/src/devices/DdeFaceTracker.cpp @@ -183,8 +183,8 @@ DdeFaceTracker::DdeFaceTracker(const QHostAddress& host, quint16 serverPort, qui _filteredEyeBlinks(), _lastEyeCoefficients(), _isCalibrating(false), - _calibrationValues(), _calibrationCount(0), + _calibrationValues(), _calibrationBillboard(NULL), _calibrationBillboardID(0), _calibrationMessage(QString()) diff --git a/libraries/entities/src/SimpleEntitySimulation.cpp b/libraries/entities/src/SimpleEntitySimulation.cpp index 518d10d056..18d5c4ecb0 100644 --- a/libraries/entities/src/SimpleEntitySimulation.cpp +++ b/libraries/entities/src/SimpleEntitySimulation.cpp @@ -54,8 +54,6 @@ void SimpleEntitySimulation::removeEntityInternal(EntityItem* entity) { _hasSimulationOwnerEntities.remove(entity); } -const int SIMPLE_SIMULATION_DIRTY_FLAGS = EntityItem::DIRTY_VELOCITIES | EntityItem::DIRTY_MOTION_TYPE; - void SimpleEntitySimulation::changeEntityInternal(EntityItem* entity) { EntitySimulation::changeEntityInternal(entity); if (!entity->getSimulatorID().isNull()) { diff --git a/libraries/model/src/model/Stage.cpp b/libraries/model/src/model/Stage.cpp index e5d23e9191..a255a1f7c9 100644 --- a/libraries/model/src/model/Stage.cpp +++ b/libraries/model/src/model/Stage.cpp @@ -12,6 +12,7 @@ #include #include +#include #include "SkyFromAtmosphere_vert.h" #include "SkyFromAtmosphere_frag.h" @@ -292,17 +293,19 @@ void SunSkyStage::updateGraphicsObject() const { } // Background - switch (getBackgroundMode()) { - case NO_BACKGROUND: { - break; - } - case SKY_DOME: { - break; - } - case SKY_BOX: { - break; - } - }; + switch (getBackgroundMode()) { + case NO_BACKGROUND: { + break; + } + case SKY_DOME: { + break; + } + case SKY_BOX: { + break; + } + case NUM_BACKGROUND_MODES: + Q_UNREACHABLE(); + }; static int firstTime = 0; if (firstTime == 0) { diff --git a/libraries/model/src/model/TextureStorage.h b/libraries/model/src/model/TextureStorage.h index 2b19a6cc1d..ebc027298b 100755 --- a/libraries/model/src/model/TextureStorage.h +++ b/libraries/model/src/model/TextureStorage.h @@ -38,9 +38,10 @@ public: ~TextureStorage(); const QUrl& getUrl() const { return _url; } - const gpu::Texture::Type getType() const { return _usage._type; } + gpu::Texture::Type getType() const { return _usage._type; } const gpu::TexturePointer& getGPUTexture() const { return _gpuTexture; } - + + virtual void reset() { Storage::reset(); } void reset(const QUrl& url, const TextureUsage& usage); protected: diff --git a/libraries/render-utils/src/GlowEffect.cpp b/libraries/render-utils/src/GlowEffect.cpp index bb18729f12..cb69421b82 100644 --- a/libraries/render-utils/src/GlowEffect.cpp +++ b/libraries/render-utils/src/GlowEffect.cpp @@ -117,18 +117,6 @@ void GlowEffect::end() { glBlendColor(0.0f, 0.0f, 0.0f, _intensity = _intensityStack.pop()); } -static void maybeBind(const gpu::FramebufferPointer& fbo) { - if (fbo) { - glBindFramebuffer(GL_FRAMEBUFFER, gpu::GLBackend::getFramebufferID(fbo)); - } -} - -static void maybeRelease(const gpu::FramebufferPointer& fbo) { - if (fbo) { - glBindFramebuffer(GL_FRAMEBUFFER, 0); - } -} - gpu::FramebufferPointer GlowEffect::render() { PerformanceTimer perfTimer("glowEffect"); From 9f7e90d03687a92509aa2cc8c8ee13a0e6909ee2 Mon Sep 17 00:00:00 2001 From: Ryan Huffman Date: Tue, 12 May 2015 08:40:44 -0700 Subject: [PATCH 12/15] Add color picker to entity properties --- examples/html/colpick.js | 520 ++++++++++++++++++++++++++++ examples/html/css/colpick.css | 420 ++++++++++++++++++++++ examples/html/entityProperties.html | 107 +++++- examples/html/jquery-2.1.4.min.js | 4 + examples/html/style.css | 7 + 5 files changed, 1052 insertions(+), 6 deletions(-) create mode 100644 examples/html/colpick.js create mode 100644 examples/html/css/colpick.css create mode 100644 examples/html/jquery-2.1.4.min.js diff --git a/examples/html/colpick.js b/examples/html/colpick.js new file mode 100644 index 0000000000..d77432a342 --- /dev/null +++ b/examples/html/colpick.js @@ -0,0 +1,520 @@ +/* +colpick Color Picker +Copyright 2013 Jose Vargas. Licensed under GPL license. Based on Stefan Petre's Color Picker www.eyecon.ro, dual licensed under the MIT and GPL licenses + +For usage and examples: colpick.com/plugin + */ + +(function ($) { + var colpick = function () { + var + tpl = '
#
R
G
B
H
S
B
', + defaults = { + showEvent: 'click', + onShow: function () {}, + onBeforeShow: function(){}, + onHide: function () {}, + onChange: function () {}, + onSubmit: function () {}, + colorScheme: 'light', + color: '3289c7', + livePreview: true, + flat: false, + layout: 'full', + submit: 1, + submitText: 'OK', + height: 156 + }, + //Fill the inputs of the plugin + fillRGBFields = function (hsb, cal) { + var rgb = hsbToRgb(hsb); + $(cal).data('colpick').fields + .eq(1).val(rgb.r).end() + .eq(2).val(rgb.g).end() + .eq(3).val(rgb.b).end(); + }, + fillHSBFields = function (hsb, cal) { + $(cal).data('colpick').fields + .eq(4).val(Math.round(hsb.h)).end() + .eq(5).val(Math.round(hsb.s)).end() + .eq(6).val(Math.round(hsb.b)).end(); + }, + fillHexFields = function (hsb, cal) { + $(cal).data('colpick').fields.eq(0).val(hsbToHex(hsb)); + }, + //Set the round selector position + setSelector = function (hsb, cal) { + $(cal).data('colpick').selector.css('backgroundColor', '#' + hsbToHex({h: hsb.h, s: 100, b: 100})); + $(cal).data('colpick').selectorIndic.css({ + left: parseInt($(cal).data('colpick').height * hsb.s/100, 10), + top: parseInt($(cal).data('colpick').height * (100-hsb.b)/100, 10) + }); + }, + //Set the hue selector position + setHue = function (hsb, cal) { + $(cal).data('colpick').hue.css('top', parseInt($(cal).data('colpick').height - $(cal).data('colpick').height * hsb.h/360, 10)); + }, + //Set current and new colors + setCurrentColor = function (hsb, cal) { + $(cal).data('colpick').currentColor.css('backgroundColor', '#' + hsbToHex(hsb)); + }, + setNewColor = function (hsb, cal) { + $(cal).data('colpick').newColor.css('backgroundColor', '#' + hsbToHex(hsb)); + }, + //Called when the new color is changed + change = function (ev) { + var cal = $(this).parent().parent(), col; + if (this.parentNode.className.indexOf('_hex') > 0) { + cal.data('colpick').color = col = hexToHsb(fixHex(this.value)); + fillRGBFields(col, cal.get(0)); + fillHSBFields(col, cal.get(0)); + } else if (this.parentNode.className.indexOf('_hsb') > 0) { + cal.data('colpick').color = col = fixHSB({ + h: parseInt(cal.data('colpick').fields.eq(4).val(), 10), + s: parseInt(cal.data('colpick').fields.eq(5).val(), 10), + b: parseInt(cal.data('colpick').fields.eq(6).val(), 10) + }); + fillRGBFields(col, cal.get(0)); + fillHexFields(col, cal.get(0)); + } else { + cal.data('colpick').color = col = rgbToHsb(fixRGB({ + r: parseInt(cal.data('colpick').fields.eq(1).val(), 10), + g: parseInt(cal.data('colpick').fields.eq(2).val(), 10), + b: parseInt(cal.data('colpick').fields.eq(3).val(), 10) + })); + fillHexFields(col, cal.get(0)); + fillHSBFields(col, cal.get(0)); + } + setSelector(col, cal.get(0)); + setHue(col, cal.get(0)); + setNewColor(col, cal.get(0)); + cal.data('colpick').onChange.apply(cal.parent(), [col, hsbToHex(col), hsbToRgb(col), cal.data('colpick').el, 0]); + }, + //Change style on blur and on focus of inputs + blur = function (ev) { + $(this).parent().removeClass('colpick_focus'); + }, + focus = function () { + $(this).parent().parent().data('colpick').fields.parent().removeClass('colpick_focus'); + $(this).parent().addClass('colpick_focus'); + }, + //Increment/decrement arrows functions + downIncrement = function (ev) { + ev.preventDefault ? ev.preventDefault() : ev.returnValue = false; + var field = $(this).parent().find('input').focus(); + var current = { + el: $(this).parent().addClass('colpick_slider'), + max: this.parentNode.className.indexOf('_hsb_h') > 0 ? 360 : (this.parentNode.className.indexOf('_hsb') > 0 ? 100 : 255), + y: ev.pageY, + field: field, + val: parseInt(field.val(), 10), + preview: $(this).parent().parent().data('colpick').livePreview + }; + $(document).mouseup(current, upIncrement); + $(document).mousemove(current, moveIncrement); + }, + moveIncrement = function (ev) { + ev.data.field.val(Math.max(0, Math.min(ev.data.max, parseInt(ev.data.val - ev.pageY + ev.data.y, 10)))); + if (ev.data.preview) { + change.apply(ev.data.field.get(0), [true]); + } + return false; + }, + upIncrement = function (ev) { + change.apply(ev.data.field.get(0), [true]); + ev.data.el.removeClass('colpick_slider').find('input').focus(); + $(document).off('mouseup', upIncrement); + $(document).off('mousemove', moveIncrement); + return false; + }, + //Hue slider functions + downHue = function (ev) { + ev.preventDefault ? ev.preventDefault() : ev.returnValue = false; + var current = { + cal: $(this).parent(), + y: $(this).offset().top + }; + $(document).on('mouseup touchend',current,upHue); + $(document).on('mousemove touchmove',current,moveHue); + + var pageY = ((ev.type == 'touchstart') ? ev.originalEvent.changedTouches[0].pageY : ev.pageY ); + change.apply( + current.cal.data('colpick') + .fields.eq(4).val(parseInt(360*(current.cal.data('colpick').height - (pageY - current.y))/current.cal.data('colpick').height, 10)) + .get(0), + [current.cal.data('colpick').livePreview] + ); + return false; + }, + moveHue = function (ev) { + var pageY = ((ev.type == 'touchmove') ? ev.originalEvent.changedTouches[0].pageY : ev.pageY ); + change.apply( + ev.data.cal.data('colpick') + .fields.eq(4).val(parseInt(360*(ev.data.cal.data('colpick').height - Math.max(0,Math.min(ev.data.cal.data('colpick').height,(pageY - ev.data.y))))/ev.data.cal.data('colpick').height, 10)) + .get(0), + [ev.data.preview] + ); + return false; + }, + upHue = function (ev) { + fillRGBFields(ev.data.cal.data('colpick').color, ev.data.cal.get(0)); + fillHexFields(ev.data.cal.data('colpick').color, ev.data.cal.get(0)); + $(document).off('mouseup touchend',upHue); + $(document).off('mousemove touchmove',moveHue); + return false; + }, + //Color selector functions + downSelector = function (ev) { + ev.preventDefault ? ev.preventDefault() : ev.returnValue = false; + var current = { + cal: $(this).parent(), + pos: $(this).offset() + }; + current.preview = current.cal.data('colpick').livePreview; + + $(document).on('mouseup touchend',current,upSelector); + $(document).on('mousemove touchmove',current,moveSelector); + + var payeX,pageY; + if(ev.type == 'touchstart') { + pageX = ev.originalEvent.changedTouches[0].pageX, + pageY = ev.originalEvent.changedTouches[0].pageY; + } else { + pageX = ev.pageX; + pageY = ev.pageY; + } + + change.apply( + current.cal.data('colpick').fields + .eq(6).val(parseInt(100*(current.cal.data('colpick').height - (pageY - current.pos.top))/current.cal.data('colpick').height, 10)).end() + .eq(5).val(parseInt(100*(pageX - current.pos.left)/current.cal.data('colpick').height, 10)) + .get(0), + [current.preview] + ); + return false; + }, + moveSelector = function (ev) { + var payeX,pageY; + if(ev.type == 'touchmove') { + pageX = ev.originalEvent.changedTouches[0].pageX, + pageY = ev.originalEvent.changedTouches[0].pageY; + } else { + pageX = ev.pageX; + pageY = ev.pageY; + } + + change.apply( + ev.data.cal.data('colpick').fields + .eq(6).val(parseInt(100*(ev.data.cal.data('colpick').height - Math.max(0,Math.min(ev.data.cal.data('colpick').height,(pageY - ev.data.pos.top))))/ev.data.cal.data('colpick').height, 10)).end() + .eq(5).val(parseInt(100*(Math.max(0,Math.min(ev.data.cal.data('colpick').height,(pageX - ev.data.pos.left))))/ev.data.cal.data('colpick').height, 10)) + .get(0), + [ev.data.preview] + ); + return false; + }, + upSelector = function (ev) { + fillRGBFields(ev.data.cal.data('colpick').color, ev.data.cal.get(0)); + fillHexFields(ev.data.cal.data('colpick').color, ev.data.cal.get(0)); + $(document).off('mouseup touchend',upSelector); + $(document).off('mousemove touchmove',moveSelector); + return false; + }, + //Submit button + clickSubmit = function (ev) { + var cal = $(this).parent(); + var col = cal.data('colpick').color; + cal.data('colpick').origColor = col; + setCurrentColor(col, cal.get(0)); + cal.data('colpick').onSubmit(col, hsbToHex(col), hsbToRgb(col), cal.data('colpick').el); + }, + //Show/hide the color picker + show = function (ev) { + // Prevent the trigger of any direct parent + ev.stopPropagation(); + var cal = $('#' + $(this).data('colpickId')); + cal.data('colpick').onBeforeShow.apply(this, [cal.get(0)]); + var pos = $(this).offset(); + var top = pos.top + this.offsetHeight; + var left = pos.left; + var viewPort = getViewport(); + var calW = cal.width(); + if (left + calW > viewPort.l + viewPort.w) { + left -= calW; + } + cal.css({left: left + 'px', top: top + 'px'}); + if (cal.data('colpick').onShow.apply(this, [cal.get(0)]) != false) { + cal.show(); + } + //Hide when user clicks outside + $('html').mousedown({cal:cal}, hide); + cal.mousedown(function(ev){ev.stopPropagation();}) + }, + hide = function (ev) { + if (ev.data.cal.data('colpick').onHide.apply(this, [ev.data.cal.get(0)]) != false) { + ev.data.cal.hide(); + } + $('html').off('mousedown', hide); + }, + getViewport = function () { + var m = document.compatMode == 'CSS1Compat'; + return { + l : window.pageXOffset || (m ? document.documentElement.scrollLeft : document.body.scrollLeft), + w : window.innerWidth || (m ? document.documentElement.clientWidth : document.body.clientWidth) + }; + }, + //Fix the values if the user enters a negative or high value + fixHSB = function (hsb) { + return { + h: Math.min(360, Math.max(0, hsb.h)), + s: Math.min(100, Math.max(0, hsb.s)), + b: Math.min(100, Math.max(0, hsb.b)) + }; + }, + fixRGB = function (rgb) { + return { + r: Math.min(255, Math.max(0, rgb.r)), + g: Math.min(255, Math.max(0, rgb.g)), + b: Math.min(255, Math.max(0, rgb.b)) + }; + }, + fixHex = function (hex) { + var len = 6 - hex.length; + if (len > 0) { + var o = []; + for (var i=0; i
').attr('style','height:8.333333%; filter:progid:DXImageTransform.Microsoft.gradient(GradientType=0,startColorstr='+stops[i]+', endColorstr='+stops[i+1]+'); -ms-filter: "progid:DXImageTransform.Microsoft.gradient(GradientType=0,startColorstr='+stops[i]+', endColorstr='+stops[i+1]+')";'); + huebar.append(div); + } + } else { + stopList = stops.join(','); + huebar.attr('style','background:-webkit-linear-gradient(top,'+stopList+'); background: -o-linear-gradient(top,'+stopList+'); background: -ms-linear-gradient(top,'+stopList+'); background:-moz-linear-gradient(top,'+stopList+'); -webkit-linear-gradient(top,'+stopList+'); background:linear-gradient(to bottom,'+stopList+'); '); + } + cal.find('div.colpick_hue').on('mousedown touchstart',downHue); + options.newColor = cal.find('div.colpick_new_color'); + options.currentColor = cal.find('div.colpick_current_color'); + //Store options and fill with default color + cal.data('colpick', options); + fillRGBFields(options.color, cal.get(0)); + fillHSBFields(options.color, cal.get(0)); + fillHexFields(options.color, cal.get(0)); + setHue(options.color, cal.get(0)); + setSelector(options.color, cal.get(0)); + setCurrentColor(options.color, cal.get(0)); + setNewColor(options.color, cal.get(0)); + //Append to body if flat=false, else show in place + if (options.flat) { + cal.appendTo(this).show(); + cal.css({ + position: 'relative', + display: 'block' + }); + } else { + cal.appendTo(document.body); + $(this).on(options.showEvent, show); + cal.css({ + position:'absolute' + }); + } + } + }); + }, + //Shows the picker + showPicker: function() { + return this.each( function () { + if ($(this).data('colpickId')) { + show.apply(this); + } + }); + }, + //Hides the picker + hidePicker: function() { + return this.each( function () { + if ($(this).data('colpickId')) { + $('#' + $(this).data('colpickId')).hide(); + } + }); + }, + //Sets a color as new and current (default) + setColor: function(col, setCurrent) { + setCurrent = (typeof setCurrent === "undefined") ? 1 : setCurrent; + if (typeof col == 'string') { + col = hexToHsb(col); + } else if (col.r != undefined && col.g != undefined && col.b != undefined) { + col = rgbToHsb(col); + } else if (col.h != undefined && col.s != undefined && col.b != undefined) { + col = fixHSB(col); + } else { + return this; + } + return this.each(function(){ + if ($(this).data('colpickId')) { + var cal = $('#' + $(this).data('colpickId')); + cal.data('colpick').color = col; + cal.data('colpick').origColor = col; + fillRGBFields(col, cal.get(0)); + fillHSBFields(col, cal.get(0)); + fillHexFields(col, cal.get(0)); + setHue(col, cal.get(0)); + setSelector(col, cal.get(0)); + + setNewColor(col, cal.get(0)); + cal.data('colpick').onChange.apply(cal.parent(), [col, hsbToHex(col), hsbToRgb(col), cal.data('colpick').el, 1]); + if(setCurrent) { + setCurrentColor(col, cal.get(0)); + } + } + }); + } + }; + }(); + //Color space convertions + var hexToRgb = function (hex) { + var hex = parseInt(((hex.indexOf('#') > -1) ? hex.substring(1) : hex), 16); + return {r: hex >> 16, g: (hex & 0x00FF00) >> 8, b: (hex & 0x0000FF)}; + }; + var hexToHsb = function (hex) { + return rgbToHsb(hexToRgb(hex)); + }; + var rgbToHsb = function (rgb) { + var hsb = {h: 0, s: 0, b: 0}; + var min = Math.min(rgb.r, rgb.g, rgb.b); + var max = Math.max(rgb.r, rgb.g, rgb.b); + var delta = max - min; + hsb.b = max; + hsb.s = max != 0 ? 255 * delta / max : 0; + if (hsb.s != 0) { + if (rgb.r == max) hsb.h = (rgb.g - rgb.b) / delta; + else if (rgb.g == max) hsb.h = 2 + (rgb.b - rgb.r) / delta; + else hsb.h = 4 + (rgb.r - rgb.g) / delta; + } else hsb.h = -1; + hsb.h *= 60; + if (hsb.h < 0) hsb.h += 360; + hsb.s *= 100/255; + hsb.b *= 100/255; + return hsb; + }; + var hsbToRgb = function (hsb) { + var rgb = {}; + var h = hsb.h; + var s = hsb.s*255/100; + var v = hsb.b*255/100; + if(s == 0) { + rgb.r = rgb.g = rgb.b = v; + } else { + var t1 = v; + var t2 = (255-s)*v/255; + var t3 = (t1-t2)*(h%60)/60; + if(h==360) h = 0; + if(h<60) {rgb.r=t1; rgb.b=t2; rgb.g=t2+t3} + else if(h<120) {rgb.g=t1; rgb.b=t2; rgb.r=t1-t3} + else if(h<180) {rgb.g=t1; rgb.r=t2; rgb.b=t2+t3} + else if(h<240) {rgb.b=t1; rgb.r=t2; rgb.g=t1-t3} + else if(h<300) {rgb.b=t1; rgb.g=t2; rgb.r=t2+t3} + else if(h<360) {rgb.r=t1; rgb.g=t2; rgb.b=t1-t3} + else {rgb.r=0; rgb.g=0; rgb.b=0} + } + return {r:Math.round(rgb.r), g:Math.round(rgb.g), b:Math.round(rgb.b)}; + }; + var rgbToHex = function (rgb) { + var hex = [ + rgb.r.toString(16), + rgb.g.toString(16), + rgb.b.toString(16) + ]; + $.each(hex, function (nr, val) { + if (val.length == 1) { + hex[nr] = '0' + val; + } + }); + return hex.join(''); + }; + var hsbToHex = function (hsb) { + return rgbToHex(hsbToRgb(hsb)); + }; + $.fn.extend({ + colpick: colpick.init, + colpickHide: colpick.hidePicker, + colpickShow: colpick.showPicker, + colpickSetColor: colpick.setColor + }); + $.extend({ + colpick:{ + rgbToHex: rgbToHex, + rgbToHsb: rgbToHsb, + hsbToHex: hsbToHex, + hsbToRgb: hsbToRgb, + hexToHsb: hexToHsb, + hexToRgb: hexToRgb + } + }); +})(jQuery); diff --git a/examples/html/css/colpick.css b/examples/html/css/colpick.css new file mode 100644 index 0000000000..564f60cb3b --- /dev/null +++ b/examples/html/css/colpick.css @@ -0,0 +1,420 @@ +/* +colpick Color Picker / colpick.com +*/ + +/*Main container*/ +.colpick { + position: absolute; + width: 346px; + height: 170px; + overflow: hidden; + display: none; + font-family: Arial, Helvetica, sans-serif; + background:#ebebeb; + border: 1px solid #bbb; + -webkit-border-radius: 5px; + -moz-border-radius: 5px; + border-radius: 5px; + + /*Prevents selecting text when dragging the selectors*/ + -webkit-user-select: none; + -moz-user-select: none; + -ms-user-select: none; + -o-user-select: none; + user-select: none; +} +/*Color selection box with gradients*/ +.colpick_color { + position: absolute; + left: 7px; + top: 7px; + width: 156px; + height: 156px; + overflow: hidden; + outline: 1px solid #aaa; + cursor: crosshair; +} +.colpick_color_overlay1 { + position: absolute; + left:0; + top:0; + width: 156px; + height: 156px; + -ms-filter: "progid:DXImageTransform.Microsoft.gradient(GradientType=1,startColorstr='#ffffff', endColorstr='#00ffffff')"; /* IE8 */ + background: -moz-linear-gradient(left, rgba(255,255,255,1) 0%, rgba(255,255,255,0) 100%); /* FF3.6+ */ + background: -webkit-gradient(linear, left top, right top, color-stop(0%,rgba(255,255,255,1)), color-stop(100%,rgba(255,255,255,0))); /* Chrome,Safari4+ */ + background: -webkit-linear-gradient(left, rgba(255,255,255,1) 0%,rgba(255,255,255,0) 100%); /* Chrome10+,Safari5.1+ */ + background: -o-linear-gradient(left, rgba(255,255,255,1) 0%,rgba(255,255,255,0) 100%); /* Opera 11.10+ */ + background: -ms-linear-gradient(left, rgba(255,255,255,1) 0%,rgba(255,255,255,0) 100%); /* IE10+ */ + background: linear-gradient(to right, rgba(255,255,255,1) 0%, rgba(255,255,255,0) 100%); + filter: progid:DXImageTransform.Microsoft.gradient(GradientType=1,startColorstr='#ffffff', endColorstr='#00ffffff'); /* IE6 & IE7 */ +} +.colpick_color_overlay2 { + position: absolute; + left:0; + top:0; + width: 156px; + height: 156px; + -ms-filter: "progid:DXImageTransform.Microsoft.gradient(GradientType=0,startColorstr='#00000000', endColorstr='#000000')"; /* IE8 */ + background: -moz-linear-gradient(top, rgba(0,0,0,0) 0%, rgba(0,0,0,1) 100%); /* FF3.6+ */ + background: -webkit-gradient(linear, left top, left bottom, color-stop(0%,rgba(0,0,0,0)), color-stop(100%,rgba(0,0,0,1))); /* Chrome,Safari4+ */ + background: -webkit-linear-gradient(top, rgba(0,0,0,0) 0%,rgba(0,0,0,1) 100%); /* Chrome10+,Safari5.1+ */ + background: -o-linear-gradient(top, rgba(0,0,0,0) 0%,rgba(0,0,0,1) 100%); /* Opera 11.10+ */ + background: -ms-linear-gradient(top, rgba(0,0,0,0) 0%,rgba(0,0,0,1) 100%); /* IE10+ */ + background: linear-gradient(to bottom, rgba(0,0,0,0) 0%,rgba(0,0,0,1) 100%); /* W3C */ + filter: progid:DXImageTransform.Microsoft.gradient( startColorstr='#00000000', endColorstr='#000000',GradientType=0 ); /* IE6-9 */ +} +/*Circular color selector*/ +.colpick_selector_outer { + background:none; + position: absolute; + width: 11px; + height: 11px; + margin: -6px 0 0 -6px; + border: 1px solid black; + border-radius: 50%; +} +.colpick_selector_inner{ + position: absolute; + width: 9px; + height: 9px; + border: 1px solid white; + border-radius: 50%; +} +/*Vertical hue bar*/ +.colpick_hue { + position: absolute; + top: 6px; + left: 175px; + width: 19px; + height: 156px; + border: 1px solid #aaa; + cursor: n-resize; +} +/*Hue bar sliding indicator*/ +.colpick_hue_arrs { + position: absolute; + left: -8px; + width: 35px; + height: 7px; + margin: -7px 0 0 0; +} +.colpick_hue_larr { + position:absolute; + width: 0; + height: 0; + border-top: 6px solid transparent; + border-bottom: 6px solid transparent; + border-left: 7px solid #858585; +} +.colpick_hue_rarr { + position:absolute; + right:0; + width: 0; + height: 0; + border-top: 6px solid transparent; + border-bottom: 6px solid transparent; + border-right: 7px solid #858585; +} +/*New color box*/ +.colpick_new_color { + position: absolute; + left: 207px; + top: 6px; + width: 60px; + height: 27px; + background: #f00; + border: 1px solid #8f8f8f; +} +/*Current color box*/ +.colpick_current_color { + position: absolute; + left: 277px; + top: 6px; + width: 60px; + height: 27px; + background: #f00; + border: 1px solid #8f8f8f; +} +/*Input field containers*/ +.colpick_field, .colpick_hex_field { + position: absolute; + height: 20px; + width: 60px; + overflow:hidden; + background:#f3f3f3; + color:#b8b8b8; + font-size:12px; + border:1px solid #bdbdbd; + -webkit-border-radius: 3px; + -moz-border-radius: 3px; + border-radius: 3px; +} +.colpick_rgb_r { + top: 40px; + left: 207px; +} +.colpick_rgb_g { + top: 67px; + left: 207px; +} +.colpick_rgb_b { + top: 94px; + left: 207px; +} +.colpick_hsb_h { + top: 40px; + left: 277px; +} +.colpick_hsb_s { + top: 67px; + left: 277px; +} +.colpick_hsb_b { + top: 94px; + left: 277px; +} +.colpick_hex_field { + width: 68px; + left: 207px; + top: 121px; +} +/*Text field container on focus*/ +.colpick_focus { + border-color: #999; +} +/*Field label container*/ +.colpick_field_letter { + position: absolute; + width: 12px; + height: 20px; + line-height: 20px; + padding-left: 4px; + background: #efefef; + border-right: 1px solid #bdbdbd; + font-weight: bold; + color:#777; +} +/*Text inputs*/ +.colpick_field input, .colpick_hex_field input { + position: absolute; + right: 11px; + margin: 0; + padding: 0; + height: 20px; + line-height: 20px; + background: transparent; + border: none; + font-size: 12px; + font-family: Arial, Helvetica, sans-serif; + color: #555; + text-align: right; + outline: none; +} +.colpick_hex_field input { + right: 4px; +} +/*Field up/down arrows*/ +.colpick_field_arrs { + position: absolute; + top: 0; + right: 0; + width: 9px; + height: 21px; + cursor: n-resize; +} +.colpick_field_uarr { + position: absolute; + top: 5px; + width: 0; + height: 0; + border-left: 4px solid transparent; + border-right: 4px solid transparent; + border-bottom: 4px solid #959595; +} +.colpick_field_darr { + position: absolute; + bottom:5px; + width: 0; + height: 0; + border-left: 4px solid transparent; + border-right: 4px solid transparent; + border-top: 4px solid #959595; +} +/*Submit/Select button*/ +.colpick_submit { + position: absolute; + left: 207px; + top: 149px; + width: 130px; + height: 22px; + line-height:22px; + background: #efefef; + text-align: center; + color: #555; + font-size: 12px; + font-weight:bold; + border: 1px solid #bdbdbd; + -webkit-border-radius: 3px; + -moz-border-radius: 3px; + border-radius: 3px; +} +.colpick_submit:hover { + background:#f3f3f3; + border-color:#999; + cursor: pointer; +} + +/*full layout with no submit button*/ +.colpick_full_ns .colpick_submit, .colpick_full_ns .colpick_current_color{ + display:none; +} +.colpick_full_ns .colpick_new_color { + width: 130px; + height: 25px; +} +.colpick_full_ns .colpick_rgb_r, .colpick_full_ns .colpick_hsb_h { + top: 42px; +} +.colpick_full_ns .colpick_rgb_g, .colpick_full_ns .colpick_hsb_s { + top: 73px; +} +.colpick_full_ns .colpick_rgb_b, .colpick_full_ns .colpick_hsb_b { + top: 104px; +} +.colpick_full_ns .colpick_hex_field { + top: 135px; +} + +/*rgbhex layout*/ +.colpick_rgbhex .colpick_hsb_h, .colpick_rgbhex .colpick_hsb_s, .colpick_rgbhex .colpick_hsb_b { + display:none; +} +.colpick_rgbhex { + width:282px; +} +.colpick_rgbhex .colpick_field, .colpick_rgbhex .colpick_submit { + width:68px; +} +.colpick_rgbhex .colpick_new_color { + width:34px; + border-right:none; +} +.colpick_rgbhex .colpick_current_color { + width:34px; + left:240px; + border-left:none; +} + +/*rgbhex layout, no submit button*/ +.colpick_rgbhex_ns .colpick_submit, .colpick_rgbhex_ns .colpick_current_color{ + display:none; +} +.colpick_rgbhex_ns .colpick_new_color{ + width:68px; + border: 1px solid #8f8f8f; +} +.colpick_rgbhex_ns .colpick_rgb_r { + top: 42px; +} +.colpick_rgbhex_ns .colpick_rgb_g { + top: 73px; +} +.colpick_rgbhex_ns .colpick_rgb_b { + top: 104px; +} +.colpick_rgbhex_ns .colpick_hex_field { + top: 135px; +} + +/*hex layout*/ +.colpick_hex .colpick_hsb_h, .colpick_hex .colpick_hsb_s, .colpick_hex .colpick_hsb_b, .colpick_hex .colpick_rgb_r, .colpick_hex .colpick_rgb_g, .colpick_hex .colpick_rgb_b { + display:none; +} +.colpick_hex { + width:206px; + height:201px; +} +.colpick_hex .colpick_hex_field { + width:72px; + height:25px; + top:168px; + left:80px; +} +.colpick_hex .colpick_hex_field div, .colpick_hex .colpick_hex_field input { + height: 25px; + line-height: 25px; +} +.colpick_hex .colpick_new_color { + left:9px; + top:168px; + width:30px; + border-right:none; +} +.colpick_hex .colpick_current_color { + left:39px; + top:168px; + width:30px; + border-left:none; +} +.colpick_hex .colpick_submit { + left:164px; + top: 168px; + width:30px; + height:25px; + line-height: 25px; +} + +/*hex layout, no submit button*/ +.colpick_hex_ns .colpick_submit, .colpick_hex_ns .colpick_current_color { + display:none; +} +.colpick_hex_ns .colpick_hex_field { + width:80px; +} +.colpick_hex_ns .colpick_new_color{ + width:60px; + border: 1px solid #8f8f8f; +} + +/*Dark color scheme*/ +.colpick_dark { + background: #161616; + border-color: #2a2a2a; +} +.colpick_dark .colpick_color { + outline-color: #333; +} +.colpick_dark .colpick_hue { + border-color: #555; +} +.colpick_dark .colpick_field, .colpick_dark .colpick_hex_field { + background: #101010; + border-color: #2d2d2d; +} +.colpick_dark .colpick_field_letter { + background: #131313; + border-color: #2d2d2d; + color: #696969; +} +.colpick_dark .colpick_field input, .colpick_dark .colpick_hex_field input { + color: #7a7a7a; +} +.colpick_dark .colpick_field_uarr { + border-bottom-color:#696969; +} +.colpick_dark .colpick_field_darr { + border-top-color:#696969; +} +.colpick_dark .colpick_focus { + border-color:#444; +} +.colpick_dark .colpick_submit { + background: #131313; + border-color:#2d2d2d; + color:#7a7a7a; +} +.colpick_dark .colpick_submit:hover { + background-color:#101010; + border-color:#444; +} \ No newline at end of file diff --git a/examples/html/entityProperties.html b/examples/html/entityProperties.html index 56fc84ef96..2bf161dee3 100644 --- a/examples/html/entityProperties.html +++ b/examples/html/entityProperties.html @@ -1,6 +1,9 @@ + + + @@ -915,6 +1004,7 @@
Color
+
R
G
B
@@ -1004,6 +1094,7 @@
Text Color
+
R
G
B
@@ -1012,6 +1103,7 @@
Background Color
+
R
G
B
@@ -1027,6 +1119,7 @@
Color
+
R
G
B
@@ -1061,6 +1154,7 @@
Key Light Color
+
R
G
B
@@ -1139,6 +1233,7 @@
Skybox Color
+
R
G
B
diff --git a/examples/html/jquery-2.1.4.min.js b/examples/html/jquery-2.1.4.min.js new file mode 100644 index 0000000000..49990d6e14 --- /dev/null +++ b/examples/html/jquery-2.1.4.min.js @@ -0,0 +1,4 @@ +/*! jQuery v2.1.4 | (c) 2005, 2015 jQuery Foundation, Inc. | jquery.org/license */ +!function(a,b){"object"==typeof module&&"object"==typeof module.exports?module.exports=a.document?b(a,!0):function(a){if(!a.document)throw new Error("jQuery requires a window with a document");return b(a)}:b(a)}("undefined"!=typeof window?window:this,function(a,b){var c=[],d=c.slice,e=c.concat,f=c.push,g=c.indexOf,h={},i=h.toString,j=h.hasOwnProperty,k={},l=a.document,m="2.1.4",n=function(a,b){return new n.fn.init(a,b)},o=/^[\s\uFEFF\xA0]+|[\s\uFEFF\xA0]+$/g,p=/^-ms-/,q=/-([\da-z])/gi,r=function(a,b){return b.toUpperCase()};n.fn=n.prototype={jquery:m,constructor:n,selector:"",length:0,toArray:function(){return d.call(this)},get:function(a){return null!=a?0>a?this[a+this.length]:this[a]:d.call(this)},pushStack:function(a){var b=n.merge(this.constructor(),a);return b.prevObject=this,b.context=this.context,b},each:function(a,b){return n.each(this,a,b)},map:function(a){return this.pushStack(n.map(this,function(b,c){return a.call(b,c,b)}))},slice:function(){return this.pushStack(d.apply(this,arguments))},first:function(){return this.eq(0)},last:function(){return this.eq(-1)},eq:function(a){var b=this.length,c=+a+(0>a?b:0);return this.pushStack(c>=0&&b>c?[this[c]]:[])},end:function(){return this.prevObject||this.constructor(null)},push:f,sort:c.sort,splice:c.splice},n.extend=n.fn.extend=function(){var a,b,c,d,e,f,g=arguments[0]||{},h=1,i=arguments.length,j=!1;for("boolean"==typeof g&&(j=g,g=arguments[h]||{},h++),"object"==typeof g||n.isFunction(g)||(g={}),h===i&&(g=this,h--);i>h;h++)if(null!=(a=arguments[h]))for(b in a)c=g[b],d=a[b],g!==d&&(j&&d&&(n.isPlainObject(d)||(e=n.isArray(d)))?(e?(e=!1,f=c&&n.isArray(c)?c:[]):f=c&&n.isPlainObject(c)?c:{},g[b]=n.extend(j,f,d)):void 0!==d&&(g[b]=d));return g},n.extend({expando:"jQuery"+(m+Math.random()).replace(/\D/g,""),isReady:!0,error:function(a){throw new Error(a)},noop:function(){},isFunction:function(a){return"function"===n.type(a)},isArray:Array.isArray,isWindow:function(a){return null!=a&&a===a.window},isNumeric:function(a){return!n.isArray(a)&&a-parseFloat(a)+1>=0},isPlainObject:function(a){return"object"!==n.type(a)||a.nodeType||n.isWindow(a)?!1:a.constructor&&!j.call(a.constructor.prototype,"isPrototypeOf")?!1:!0},isEmptyObject:function(a){var b;for(b in a)return!1;return!0},type:function(a){return null==a?a+"":"object"==typeof a||"function"==typeof a?h[i.call(a)]||"object":typeof a},globalEval:function(a){var b,c=eval;a=n.trim(a),a&&(1===a.indexOf("use strict")?(b=l.createElement("script"),b.text=a,l.head.appendChild(b).parentNode.removeChild(b)):c(a))},camelCase:function(a){return a.replace(p,"ms-").replace(q,r)},nodeName:function(a,b){return a.nodeName&&a.nodeName.toLowerCase()===b.toLowerCase()},each:function(a,b,c){var d,e=0,f=a.length,g=s(a);if(c){if(g){for(;f>e;e++)if(d=b.apply(a[e],c),d===!1)break}else for(e in a)if(d=b.apply(a[e],c),d===!1)break}else if(g){for(;f>e;e++)if(d=b.call(a[e],e,a[e]),d===!1)break}else for(e in a)if(d=b.call(a[e],e,a[e]),d===!1)break;return a},trim:function(a){return null==a?"":(a+"").replace(o,"")},makeArray:function(a,b){var c=b||[];return null!=a&&(s(Object(a))?n.merge(c,"string"==typeof a?[a]:a):f.call(c,a)),c},inArray:function(a,b,c){return null==b?-1:g.call(b,a,c)},merge:function(a,b){for(var c=+b.length,d=0,e=a.length;c>d;d++)a[e++]=b[d];return a.length=e,a},grep:function(a,b,c){for(var d,e=[],f=0,g=a.length,h=!c;g>f;f++)d=!b(a[f],f),d!==h&&e.push(a[f]);return e},map:function(a,b,c){var d,f=0,g=a.length,h=s(a),i=[];if(h)for(;g>f;f++)d=b(a[f],f,c),null!=d&&i.push(d);else for(f in a)d=b(a[f],f,c),null!=d&&i.push(d);return e.apply([],i)},guid:1,proxy:function(a,b){var c,e,f;return"string"==typeof b&&(c=a[b],b=a,a=c),n.isFunction(a)?(e=d.call(arguments,2),f=function(){return a.apply(b||this,e.concat(d.call(arguments)))},f.guid=a.guid=a.guid||n.guid++,f):void 0},now:Date.now,support:k}),n.each("Boolean Number String Function Array Date RegExp Object Error".split(" "),function(a,b){h["[object "+b+"]"]=b.toLowerCase()});function s(a){var b="length"in a&&a.length,c=n.type(a);return"function"===c||n.isWindow(a)?!1:1===a.nodeType&&b?!0:"array"===c||0===b||"number"==typeof b&&b>0&&b-1 in a}var t=function(a){var b,c,d,e,f,g,h,i,j,k,l,m,n,o,p,q,r,s,t,u="sizzle"+1*new Date,v=a.document,w=0,x=0,y=ha(),z=ha(),A=ha(),B=function(a,b){return a===b&&(l=!0),0},C=1<<31,D={}.hasOwnProperty,E=[],F=E.pop,G=E.push,H=E.push,I=E.slice,J=function(a,b){for(var c=0,d=a.length;d>c;c++)if(a[c]===b)return c;return-1},K="checked|selected|async|autofocus|autoplay|controls|defer|disabled|hidden|ismap|loop|multiple|open|readonly|required|scoped",L="[\\x20\\t\\r\\n\\f]",M="(?:\\\\.|[\\w-]|[^\\x00-\\xa0])+",N=M.replace("w","w#"),O="\\["+L+"*("+M+")(?:"+L+"*([*^$|!~]?=)"+L+"*(?:'((?:\\\\.|[^\\\\'])*)'|\"((?:\\\\.|[^\\\\\"])*)\"|("+N+"))|)"+L+"*\\]",P=":("+M+")(?:\\((('((?:\\\\.|[^\\\\'])*)'|\"((?:\\\\.|[^\\\\\"])*)\")|((?:\\\\.|[^\\\\()[\\]]|"+O+")*)|.*)\\)|)",Q=new RegExp(L+"+","g"),R=new RegExp("^"+L+"+|((?:^|[^\\\\])(?:\\\\.)*)"+L+"+$","g"),S=new RegExp("^"+L+"*,"+L+"*"),T=new RegExp("^"+L+"*([>+~]|"+L+")"+L+"*"),U=new RegExp("="+L+"*([^\\]'\"]*?)"+L+"*\\]","g"),V=new RegExp(P),W=new RegExp("^"+N+"$"),X={ID:new RegExp("^#("+M+")"),CLASS:new RegExp("^\\.("+M+")"),TAG:new RegExp("^("+M.replace("w","w*")+")"),ATTR:new RegExp("^"+O),PSEUDO:new RegExp("^"+P),CHILD:new RegExp("^:(only|first|last|nth|nth-last)-(child|of-type)(?:\\("+L+"*(even|odd|(([+-]|)(\\d*)n|)"+L+"*(?:([+-]|)"+L+"*(\\d+)|))"+L+"*\\)|)","i"),bool:new RegExp("^(?:"+K+")$","i"),needsContext:new RegExp("^"+L+"*[>+~]|:(even|odd|eq|gt|lt|nth|first|last)(?:\\("+L+"*((?:-\\d)?\\d*)"+L+"*\\)|)(?=[^-]|$)","i")},Y=/^(?:input|select|textarea|button)$/i,Z=/^h\d$/i,$=/^[^{]+\{\s*\[native \w/,_=/^(?:#([\w-]+)|(\w+)|\.([\w-]+))$/,aa=/[+~]/,ba=/'|\\/g,ca=new RegExp("\\\\([\\da-f]{1,6}"+L+"?|("+L+")|.)","ig"),da=function(a,b,c){var d="0x"+b-65536;return d!==d||c?b:0>d?String.fromCharCode(d+65536):String.fromCharCode(d>>10|55296,1023&d|56320)},ea=function(){m()};try{H.apply(E=I.call(v.childNodes),v.childNodes),E[v.childNodes.length].nodeType}catch(fa){H={apply:E.length?function(a,b){G.apply(a,I.call(b))}:function(a,b){var c=a.length,d=0;while(a[c++]=b[d++]);a.length=c-1}}}function ga(a,b,d,e){var f,h,j,k,l,o,r,s,w,x;if((b?b.ownerDocument||b:v)!==n&&m(b),b=b||n,d=d||[],k=b.nodeType,"string"!=typeof a||!a||1!==k&&9!==k&&11!==k)return d;if(!e&&p){if(11!==k&&(f=_.exec(a)))if(j=f[1]){if(9===k){if(h=b.getElementById(j),!h||!h.parentNode)return d;if(h.id===j)return d.push(h),d}else if(b.ownerDocument&&(h=b.ownerDocument.getElementById(j))&&t(b,h)&&h.id===j)return d.push(h),d}else{if(f[2])return H.apply(d,b.getElementsByTagName(a)),d;if((j=f[3])&&c.getElementsByClassName)return H.apply(d,b.getElementsByClassName(j)),d}if(c.qsa&&(!q||!q.test(a))){if(s=r=u,w=b,x=1!==k&&a,1===k&&"object"!==b.nodeName.toLowerCase()){o=g(a),(r=b.getAttribute("id"))?s=r.replace(ba,"\\$&"):b.setAttribute("id",s),s="[id='"+s+"'] ",l=o.length;while(l--)o[l]=s+ra(o[l]);w=aa.test(a)&&pa(b.parentNode)||b,x=o.join(",")}if(x)try{return H.apply(d,w.querySelectorAll(x)),d}catch(y){}finally{r||b.removeAttribute("id")}}}return i(a.replace(R,"$1"),b,d,e)}function ha(){var a=[];function b(c,e){return a.push(c+" ")>d.cacheLength&&delete b[a.shift()],b[c+" "]=e}return b}function ia(a){return a[u]=!0,a}function ja(a){var b=n.createElement("div");try{return!!a(b)}catch(c){return!1}finally{b.parentNode&&b.parentNode.removeChild(b),b=null}}function ka(a,b){var c=a.split("|"),e=a.length;while(e--)d.attrHandle[c[e]]=b}function la(a,b){var c=b&&a,d=c&&1===a.nodeType&&1===b.nodeType&&(~b.sourceIndex||C)-(~a.sourceIndex||C);if(d)return d;if(c)while(c=c.nextSibling)if(c===b)return-1;return a?1:-1}function ma(a){return function(b){var c=b.nodeName.toLowerCase();return"input"===c&&b.type===a}}function na(a){return function(b){var c=b.nodeName.toLowerCase();return("input"===c||"button"===c)&&b.type===a}}function oa(a){return ia(function(b){return b=+b,ia(function(c,d){var e,f=a([],c.length,b),g=f.length;while(g--)c[e=f[g]]&&(c[e]=!(d[e]=c[e]))})})}function pa(a){return a&&"undefined"!=typeof a.getElementsByTagName&&a}c=ga.support={},f=ga.isXML=function(a){var b=a&&(a.ownerDocument||a).documentElement;return b?"HTML"!==b.nodeName:!1},m=ga.setDocument=function(a){var b,e,g=a?a.ownerDocument||a:v;return g!==n&&9===g.nodeType&&g.documentElement?(n=g,o=g.documentElement,e=g.defaultView,e&&e!==e.top&&(e.addEventListener?e.addEventListener("unload",ea,!1):e.attachEvent&&e.attachEvent("onunload",ea)),p=!f(g),c.attributes=ja(function(a){return a.className="i",!a.getAttribute("className")}),c.getElementsByTagName=ja(function(a){return a.appendChild(g.createComment("")),!a.getElementsByTagName("*").length}),c.getElementsByClassName=$.test(g.getElementsByClassName),c.getById=ja(function(a){return o.appendChild(a).id=u,!g.getElementsByName||!g.getElementsByName(u).length}),c.getById?(d.find.ID=function(a,b){if("undefined"!=typeof b.getElementById&&p){var c=b.getElementById(a);return c&&c.parentNode?[c]:[]}},d.filter.ID=function(a){var b=a.replace(ca,da);return function(a){return a.getAttribute("id")===b}}):(delete d.find.ID,d.filter.ID=function(a){var b=a.replace(ca,da);return function(a){var c="undefined"!=typeof a.getAttributeNode&&a.getAttributeNode("id");return c&&c.value===b}}),d.find.TAG=c.getElementsByTagName?function(a,b){return"undefined"!=typeof b.getElementsByTagName?b.getElementsByTagName(a):c.qsa?b.querySelectorAll(a):void 0}:function(a,b){var c,d=[],e=0,f=b.getElementsByTagName(a);if("*"===a){while(c=f[e++])1===c.nodeType&&d.push(c);return d}return f},d.find.CLASS=c.getElementsByClassName&&function(a,b){return p?b.getElementsByClassName(a):void 0},r=[],q=[],(c.qsa=$.test(g.querySelectorAll))&&(ja(function(a){o.appendChild(a).innerHTML="",a.querySelectorAll("[msallowcapture^='']").length&&q.push("[*^$]="+L+"*(?:''|\"\")"),a.querySelectorAll("[selected]").length||q.push("\\["+L+"*(?:value|"+K+")"),a.querySelectorAll("[id~="+u+"-]").length||q.push("~="),a.querySelectorAll(":checked").length||q.push(":checked"),a.querySelectorAll("a#"+u+"+*").length||q.push(".#.+[+~]")}),ja(function(a){var b=g.createElement("input");b.setAttribute("type","hidden"),a.appendChild(b).setAttribute("name","D"),a.querySelectorAll("[name=d]").length&&q.push("name"+L+"*[*^$|!~]?="),a.querySelectorAll(":enabled").length||q.push(":enabled",":disabled"),a.querySelectorAll("*,:x"),q.push(",.*:")})),(c.matchesSelector=$.test(s=o.matches||o.webkitMatchesSelector||o.mozMatchesSelector||o.oMatchesSelector||o.msMatchesSelector))&&ja(function(a){c.disconnectedMatch=s.call(a,"div"),s.call(a,"[s!='']:x"),r.push("!=",P)}),q=q.length&&new RegExp(q.join("|")),r=r.length&&new RegExp(r.join("|")),b=$.test(o.compareDocumentPosition),t=b||$.test(o.contains)?function(a,b){var c=9===a.nodeType?a.documentElement:a,d=b&&b.parentNode;return a===d||!(!d||1!==d.nodeType||!(c.contains?c.contains(d):a.compareDocumentPosition&&16&a.compareDocumentPosition(d)))}:function(a,b){if(b)while(b=b.parentNode)if(b===a)return!0;return!1},B=b?function(a,b){if(a===b)return l=!0,0;var d=!a.compareDocumentPosition-!b.compareDocumentPosition;return d?d:(d=(a.ownerDocument||a)===(b.ownerDocument||b)?a.compareDocumentPosition(b):1,1&d||!c.sortDetached&&b.compareDocumentPosition(a)===d?a===g||a.ownerDocument===v&&t(v,a)?-1:b===g||b.ownerDocument===v&&t(v,b)?1:k?J(k,a)-J(k,b):0:4&d?-1:1)}:function(a,b){if(a===b)return l=!0,0;var c,d=0,e=a.parentNode,f=b.parentNode,h=[a],i=[b];if(!e||!f)return a===g?-1:b===g?1:e?-1:f?1:k?J(k,a)-J(k,b):0;if(e===f)return la(a,b);c=a;while(c=c.parentNode)h.unshift(c);c=b;while(c=c.parentNode)i.unshift(c);while(h[d]===i[d])d++;return d?la(h[d],i[d]):h[d]===v?-1:i[d]===v?1:0},g):n},ga.matches=function(a,b){return ga(a,null,null,b)},ga.matchesSelector=function(a,b){if((a.ownerDocument||a)!==n&&m(a),b=b.replace(U,"='$1']"),!(!c.matchesSelector||!p||r&&r.test(b)||q&&q.test(b)))try{var d=s.call(a,b);if(d||c.disconnectedMatch||a.document&&11!==a.document.nodeType)return d}catch(e){}return ga(b,n,null,[a]).length>0},ga.contains=function(a,b){return(a.ownerDocument||a)!==n&&m(a),t(a,b)},ga.attr=function(a,b){(a.ownerDocument||a)!==n&&m(a);var e=d.attrHandle[b.toLowerCase()],f=e&&D.call(d.attrHandle,b.toLowerCase())?e(a,b,!p):void 0;return void 0!==f?f:c.attributes||!p?a.getAttribute(b):(f=a.getAttributeNode(b))&&f.specified?f.value:null},ga.error=function(a){throw new Error("Syntax error, unrecognized expression: "+a)},ga.uniqueSort=function(a){var b,d=[],e=0,f=0;if(l=!c.detectDuplicates,k=!c.sortStable&&a.slice(0),a.sort(B),l){while(b=a[f++])b===a[f]&&(e=d.push(f));while(e--)a.splice(d[e],1)}return k=null,a},e=ga.getText=function(a){var b,c="",d=0,f=a.nodeType;if(f){if(1===f||9===f||11===f){if("string"==typeof a.textContent)return a.textContent;for(a=a.firstChild;a;a=a.nextSibling)c+=e(a)}else if(3===f||4===f)return a.nodeValue}else while(b=a[d++])c+=e(b);return c},d=ga.selectors={cacheLength:50,createPseudo:ia,match:X,attrHandle:{},find:{},relative:{">":{dir:"parentNode",first:!0}," ":{dir:"parentNode"},"+":{dir:"previousSibling",first:!0},"~":{dir:"previousSibling"}},preFilter:{ATTR:function(a){return a[1]=a[1].replace(ca,da),a[3]=(a[3]||a[4]||a[5]||"").replace(ca,da),"~="===a[2]&&(a[3]=" "+a[3]+" "),a.slice(0,4)},CHILD:function(a){return a[1]=a[1].toLowerCase(),"nth"===a[1].slice(0,3)?(a[3]||ga.error(a[0]),a[4]=+(a[4]?a[5]+(a[6]||1):2*("even"===a[3]||"odd"===a[3])),a[5]=+(a[7]+a[8]||"odd"===a[3])):a[3]&&ga.error(a[0]),a},PSEUDO:function(a){var b,c=!a[6]&&a[2];return X.CHILD.test(a[0])?null:(a[3]?a[2]=a[4]||a[5]||"":c&&V.test(c)&&(b=g(c,!0))&&(b=c.indexOf(")",c.length-b)-c.length)&&(a[0]=a[0].slice(0,b),a[2]=c.slice(0,b)),a.slice(0,3))}},filter:{TAG:function(a){var b=a.replace(ca,da).toLowerCase();return"*"===a?function(){return!0}:function(a){return a.nodeName&&a.nodeName.toLowerCase()===b}},CLASS:function(a){var b=y[a+" "];return b||(b=new RegExp("(^|"+L+")"+a+"("+L+"|$)"))&&y(a,function(a){return b.test("string"==typeof a.className&&a.className||"undefined"!=typeof a.getAttribute&&a.getAttribute("class")||"")})},ATTR:function(a,b,c){return function(d){var e=ga.attr(d,a);return null==e?"!="===b:b?(e+="","="===b?e===c:"!="===b?e!==c:"^="===b?c&&0===e.indexOf(c):"*="===b?c&&e.indexOf(c)>-1:"$="===b?c&&e.slice(-c.length)===c:"~="===b?(" "+e.replace(Q," ")+" ").indexOf(c)>-1:"|="===b?e===c||e.slice(0,c.length+1)===c+"-":!1):!0}},CHILD:function(a,b,c,d,e){var f="nth"!==a.slice(0,3),g="last"!==a.slice(-4),h="of-type"===b;return 1===d&&0===e?function(a){return!!a.parentNode}:function(b,c,i){var j,k,l,m,n,o,p=f!==g?"nextSibling":"previousSibling",q=b.parentNode,r=h&&b.nodeName.toLowerCase(),s=!i&&!h;if(q){if(f){while(p){l=b;while(l=l[p])if(h?l.nodeName.toLowerCase()===r:1===l.nodeType)return!1;o=p="only"===a&&!o&&"nextSibling"}return!0}if(o=[g?q.firstChild:q.lastChild],g&&s){k=q[u]||(q[u]={}),j=k[a]||[],n=j[0]===w&&j[1],m=j[0]===w&&j[2],l=n&&q.childNodes[n];while(l=++n&&l&&l[p]||(m=n=0)||o.pop())if(1===l.nodeType&&++m&&l===b){k[a]=[w,n,m];break}}else if(s&&(j=(b[u]||(b[u]={}))[a])&&j[0]===w)m=j[1];else while(l=++n&&l&&l[p]||(m=n=0)||o.pop())if((h?l.nodeName.toLowerCase()===r:1===l.nodeType)&&++m&&(s&&((l[u]||(l[u]={}))[a]=[w,m]),l===b))break;return m-=e,m===d||m%d===0&&m/d>=0}}},PSEUDO:function(a,b){var c,e=d.pseudos[a]||d.setFilters[a.toLowerCase()]||ga.error("unsupported pseudo: "+a);return e[u]?e(b):e.length>1?(c=[a,a,"",b],d.setFilters.hasOwnProperty(a.toLowerCase())?ia(function(a,c){var d,f=e(a,b),g=f.length;while(g--)d=J(a,f[g]),a[d]=!(c[d]=f[g])}):function(a){return e(a,0,c)}):e}},pseudos:{not:ia(function(a){var b=[],c=[],d=h(a.replace(R,"$1"));return d[u]?ia(function(a,b,c,e){var f,g=d(a,null,e,[]),h=a.length;while(h--)(f=g[h])&&(a[h]=!(b[h]=f))}):function(a,e,f){return b[0]=a,d(b,null,f,c),b[0]=null,!c.pop()}}),has:ia(function(a){return function(b){return ga(a,b).length>0}}),contains:ia(function(a){return a=a.replace(ca,da),function(b){return(b.textContent||b.innerText||e(b)).indexOf(a)>-1}}),lang:ia(function(a){return W.test(a||"")||ga.error("unsupported lang: "+a),a=a.replace(ca,da).toLowerCase(),function(b){var c;do if(c=p?b.lang:b.getAttribute("xml:lang")||b.getAttribute("lang"))return c=c.toLowerCase(),c===a||0===c.indexOf(a+"-");while((b=b.parentNode)&&1===b.nodeType);return!1}}),target:function(b){var c=a.location&&a.location.hash;return c&&c.slice(1)===b.id},root:function(a){return a===o},focus:function(a){return a===n.activeElement&&(!n.hasFocus||n.hasFocus())&&!!(a.type||a.href||~a.tabIndex)},enabled:function(a){return a.disabled===!1},disabled:function(a){return a.disabled===!0},checked:function(a){var b=a.nodeName.toLowerCase();return"input"===b&&!!a.checked||"option"===b&&!!a.selected},selected:function(a){return a.parentNode&&a.parentNode.selectedIndex,a.selected===!0},empty:function(a){for(a=a.firstChild;a;a=a.nextSibling)if(a.nodeType<6)return!1;return!0},parent:function(a){return!d.pseudos.empty(a)},header:function(a){return Z.test(a.nodeName)},input:function(a){return Y.test(a.nodeName)},button:function(a){var b=a.nodeName.toLowerCase();return"input"===b&&"button"===a.type||"button"===b},text:function(a){var b;return"input"===a.nodeName.toLowerCase()&&"text"===a.type&&(null==(b=a.getAttribute("type"))||"text"===b.toLowerCase())},first:oa(function(){return[0]}),last:oa(function(a,b){return[b-1]}),eq:oa(function(a,b,c){return[0>c?c+b:c]}),even:oa(function(a,b){for(var c=0;b>c;c+=2)a.push(c);return a}),odd:oa(function(a,b){for(var c=1;b>c;c+=2)a.push(c);return a}),lt:oa(function(a,b,c){for(var d=0>c?c+b:c;--d>=0;)a.push(d);return a}),gt:oa(function(a,b,c){for(var d=0>c?c+b:c;++db;b++)d+=a[b].value;return d}function sa(a,b,c){var d=b.dir,e=c&&"parentNode"===d,f=x++;return b.first?function(b,c,f){while(b=b[d])if(1===b.nodeType||e)return a(b,c,f)}:function(b,c,g){var h,i,j=[w,f];if(g){while(b=b[d])if((1===b.nodeType||e)&&a(b,c,g))return!0}else while(b=b[d])if(1===b.nodeType||e){if(i=b[u]||(b[u]={}),(h=i[d])&&h[0]===w&&h[1]===f)return j[2]=h[2];if(i[d]=j,j[2]=a(b,c,g))return!0}}}function ta(a){return a.length>1?function(b,c,d){var e=a.length;while(e--)if(!a[e](b,c,d))return!1;return!0}:a[0]}function ua(a,b,c){for(var d=0,e=b.length;e>d;d++)ga(a,b[d],c);return c}function va(a,b,c,d,e){for(var f,g=[],h=0,i=a.length,j=null!=b;i>h;h++)(f=a[h])&&(!c||c(f,d,e))&&(g.push(f),j&&b.push(h));return g}function wa(a,b,c,d,e,f){return d&&!d[u]&&(d=wa(d)),e&&!e[u]&&(e=wa(e,f)),ia(function(f,g,h,i){var j,k,l,m=[],n=[],o=g.length,p=f||ua(b||"*",h.nodeType?[h]:h,[]),q=!a||!f&&b?p:va(p,m,a,h,i),r=c?e||(f?a:o||d)?[]:g:q;if(c&&c(q,r,h,i),d){j=va(r,n),d(j,[],h,i),k=j.length;while(k--)(l=j[k])&&(r[n[k]]=!(q[n[k]]=l))}if(f){if(e||a){if(e){j=[],k=r.length;while(k--)(l=r[k])&&j.push(q[k]=l);e(null,r=[],j,i)}k=r.length;while(k--)(l=r[k])&&(j=e?J(f,l):m[k])>-1&&(f[j]=!(g[j]=l))}}else r=va(r===g?r.splice(o,r.length):r),e?e(null,g,r,i):H.apply(g,r)})}function xa(a){for(var b,c,e,f=a.length,g=d.relative[a[0].type],h=g||d.relative[" "],i=g?1:0,k=sa(function(a){return a===b},h,!0),l=sa(function(a){return J(b,a)>-1},h,!0),m=[function(a,c,d){var e=!g&&(d||c!==j)||((b=c).nodeType?k(a,c,d):l(a,c,d));return b=null,e}];f>i;i++)if(c=d.relative[a[i].type])m=[sa(ta(m),c)];else{if(c=d.filter[a[i].type].apply(null,a[i].matches),c[u]){for(e=++i;f>e;e++)if(d.relative[a[e].type])break;return wa(i>1&&ta(m),i>1&&ra(a.slice(0,i-1).concat({value:" "===a[i-2].type?"*":""})).replace(R,"$1"),c,e>i&&xa(a.slice(i,e)),f>e&&xa(a=a.slice(e)),f>e&&ra(a))}m.push(c)}return ta(m)}function ya(a,b){var c=b.length>0,e=a.length>0,f=function(f,g,h,i,k){var l,m,o,p=0,q="0",r=f&&[],s=[],t=j,u=f||e&&d.find.TAG("*",k),v=w+=null==t?1:Math.random()||.1,x=u.length;for(k&&(j=g!==n&&g);q!==x&&null!=(l=u[q]);q++){if(e&&l){m=0;while(o=a[m++])if(o(l,g,h)){i.push(l);break}k&&(w=v)}c&&((l=!o&&l)&&p--,f&&r.push(l))}if(p+=q,c&&q!==p){m=0;while(o=b[m++])o(r,s,g,h);if(f){if(p>0)while(q--)r[q]||s[q]||(s[q]=F.call(i));s=va(s)}H.apply(i,s),k&&!f&&s.length>0&&p+b.length>1&&ga.uniqueSort(i)}return k&&(w=v,j=t),r};return c?ia(f):f}return h=ga.compile=function(a,b){var c,d=[],e=[],f=A[a+" "];if(!f){b||(b=g(a)),c=b.length;while(c--)f=xa(b[c]),f[u]?d.push(f):e.push(f);f=A(a,ya(e,d)),f.selector=a}return f},i=ga.select=function(a,b,e,f){var i,j,k,l,m,n="function"==typeof a&&a,o=!f&&g(a=n.selector||a);if(e=e||[],1===o.length){if(j=o[0]=o[0].slice(0),j.length>2&&"ID"===(k=j[0]).type&&c.getById&&9===b.nodeType&&p&&d.relative[j[1].type]){if(b=(d.find.ID(k.matches[0].replace(ca,da),b)||[])[0],!b)return e;n&&(b=b.parentNode),a=a.slice(j.shift().value.length)}i=X.needsContext.test(a)?0:j.length;while(i--){if(k=j[i],d.relative[l=k.type])break;if((m=d.find[l])&&(f=m(k.matches[0].replace(ca,da),aa.test(j[0].type)&&pa(b.parentNode)||b))){if(j.splice(i,1),a=f.length&&ra(j),!a)return H.apply(e,f),e;break}}}return(n||h(a,o))(f,b,!p,e,aa.test(a)&&pa(b.parentNode)||b),e},c.sortStable=u.split("").sort(B).join("")===u,c.detectDuplicates=!!l,m(),c.sortDetached=ja(function(a){return 1&a.compareDocumentPosition(n.createElement("div"))}),ja(function(a){return a.innerHTML="","#"===a.firstChild.getAttribute("href")})||ka("type|href|height|width",function(a,b,c){return c?void 0:a.getAttribute(b,"type"===b.toLowerCase()?1:2)}),c.attributes&&ja(function(a){return a.innerHTML="",a.firstChild.setAttribute("value",""),""===a.firstChild.getAttribute("value")})||ka("value",function(a,b,c){return c||"input"!==a.nodeName.toLowerCase()?void 0:a.defaultValue}),ja(function(a){return null==a.getAttribute("disabled")})||ka(K,function(a,b,c){var d;return c?void 0:a[b]===!0?b.toLowerCase():(d=a.getAttributeNode(b))&&d.specified?d.value:null}),ga}(a);n.find=t,n.expr=t.selectors,n.expr[":"]=n.expr.pseudos,n.unique=t.uniqueSort,n.text=t.getText,n.isXMLDoc=t.isXML,n.contains=t.contains;var u=n.expr.match.needsContext,v=/^<(\w+)\s*\/?>(?:<\/\1>|)$/,w=/^.[^:#\[\.,]*$/;function x(a,b,c){if(n.isFunction(b))return n.grep(a,function(a,d){return!!b.call(a,d,a)!==c});if(b.nodeType)return n.grep(a,function(a){return a===b!==c});if("string"==typeof b){if(w.test(b))return n.filter(b,a,c);b=n.filter(b,a)}return n.grep(a,function(a){return g.call(b,a)>=0!==c})}n.filter=function(a,b,c){var d=b[0];return c&&(a=":not("+a+")"),1===b.length&&1===d.nodeType?n.find.matchesSelector(d,a)?[d]:[]:n.find.matches(a,n.grep(b,function(a){return 1===a.nodeType}))},n.fn.extend({find:function(a){var b,c=this.length,d=[],e=this;if("string"!=typeof a)return this.pushStack(n(a).filter(function(){for(b=0;c>b;b++)if(n.contains(e[b],this))return!0}));for(b=0;c>b;b++)n.find(a,e[b],d);return d=this.pushStack(c>1?n.unique(d):d),d.selector=this.selector?this.selector+" "+a:a,d},filter:function(a){return this.pushStack(x(this,a||[],!1))},not:function(a){return this.pushStack(x(this,a||[],!0))},is:function(a){return!!x(this,"string"==typeof a&&u.test(a)?n(a):a||[],!1).length}});var y,z=/^(?:\s*(<[\w\W]+>)[^>]*|#([\w-]*))$/,A=n.fn.init=function(a,b){var c,d;if(!a)return this;if("string"==typeof a){if(c="<"===a[0]&&">"===a[a.length-1]&&a.length>=3?[null,a,null]:z.exec(a),!c||!c[1]&&b)return!b||b.jquery?(b||y).find(a):this.constructor(b).find(a);if(c[1]){if(b=b instanceof n?b[0]:b,n.merge(this,n.parseHTML(c[1],b&&b.nodeType?b.ownerDocument||b:l,!0)),v.test(c[1])&&n.isPlainObject(b))for(c in b)n.isFunction(this[c])?this[c](b[c]):this.attr(c,b[c]);return this}return d=l.getElementById(c[2]),d&&d.parentNode&&(this.length=1,this[0]=d),this.context=l,this.selector=a,this}return a.nodeType?(this.context=this[0]=a,this.length=1,this):n.isFunction(a)?"undefined"!=typeof y.ready?y.ready(a):a(n):(void 0!==a.selector&&(this.selector=a.selector,this.context=a.context),n.makeArray(a,this))};A.prototype=n.fn,y=n(l);var B=/^(?:parents|prev(?:Until|All))/,C={children:!0,contents:!0,next:!0,prev:!0};n.extend({dir:function(a,b,c){var d=[],e=void 0!==c;while((a=a[b])&&9!==a.nodeType)if(1===a.nodeType){if(e&&n(a).is(c))break;d.push(a)}return d},sibling:function(a,b){for(var c=[];a;a=a.nextSibling)1===a.nodeType&&a!==b&&c.push(a);return c}}),n.fn.extend({has:function(a){var b=n(a,this),c=b.length;return this.filter(function(){for(var a=0;c>a;a++)if(n.contains(this,b[a]))return!0})},closest:function(a,b){for(var c,d=0,e=this.length,f=[],g=u.test(a)||"string"!=typeof a?n(a,b||this.context):0;e>d;d++)for(c=this[d];c&&c!==b;c=c.parentNode)if(c.nodeType<11&&(g?g.index(c)>-1:1===c.nodeType&&n.find.matchesSelector(c,a))){f.push(c);break}return this.pushStack(f.length>1?n.unique(f):f)},index:function(a){return a?"string"==typeof a?g.call(n(a),this[0]):g.call(this,a.jquery?a[0]:a):this[0]&&this[0].parentNode?this.first().prevAll().length:-1},add:function(a,b){return this.pushStack(n.unique(n.merge(this.get(),n(a,b))))},addBack:function(a){return this.add(null==a?this.prevObject:this.prevObject.filter(a))}});function D(a,b){while((a=a[b])&&1!==a.nodeType);return a}n.each({parent:function(a){var b=a.parentNode;return b&&11!==b.nodeType?b:null},parents:function(a){return n.dir(a,"parentNode")},parentsUntil:function(a,b,c){return n.dir(a,"parentNode",c)},next:function(a){return D(a,"nextSibling")},prev:function(a){return D(a,"previousSibling")},nextAll:function(a){return n.dir(a,"nextSibling")},prevAll:function(a){return n.dir(a,"previousSibling")},nextUntil:function(a,b,c){return n.dir(a,"nextSibling",c)},prevUntil:function(a,b,c){return n.dir(a,"previousSibling",c)},siblings:function(a){return n.sibling((a.parentNode||{}).firstChild,a)},children:function(a){return n.sibling(a.firstChild)},contents:function(a){return a.contentDocument||n.merge([],a.childNodes)}},function(a,b){n.fn[a]=function(c,d){var e=n.map(this,b,c);return"Until"!==a.slice(-5)&&(d=c),d&&"string"==typeof d&&(e=n.filter(d,e)),this.length>1&&(C[a]||n.unique(e),B.test(a)&&e.reverse()),this.pushStack(e)}});var E=/\S+/g,F={};function G(a){var b=F[a]={};return n.each(a.match(E)||[],function(a,c){b[c]=!0}),b}n.Callbacks=function(a){a="string"==typeof a?F[a]||G(a):n.extend({},a);var b,c,d,e,f,g,h=[],i=!a.once&&[],j=function(l){for(b=a.memory&&l,c=!0,g=e||0,e=0,f=h.length,d=!0;h&&f>g;g++)if(h[g].apply(l[0],l[1])===!1&&a.stopOnFalse){b=!1;break}d=!1,h&&(i?i.length&&j(i.shift()):b?h=[]:k.disable())},k={add:function(){if(h){var c=h.length;!function g(b){n.each(b,function(b,c){var d=n.type(c);"function"===d?a.unique&&k.has(c)||h.push(c):c&&c.length&&"string"!==d&&g(c)})}(arguments),d?f=h.length:b&&(e=c,j(b))}return this},remove:function(){return h&&n.each(arguments,function(a,b){var c;while((c=n.inArray(b,h,c))>-1)h.splice(c,1),d&&(f>=c&&f--,g>=c&&g--)}),this},has:function(a){return a?n.inArray(a,h)>-1:!(!h||!h.length)},empty:function(){return h=[],f=0,this},disable:function(){return h=i=b=void 0,this},disabled:function(){return!h},lock:function(){return i=void 0,b||k.disable(),this},locked:function(){return!i},fireWith:function(a,b){return!h||c&&!i||(b=b||[],b=[a,b.slice?b.slice():b],d?i.push(b):j(b)),this},fire:function(){return k.fireWith(this,arguments),this},fired:function(){return!!c}};return k},n.extend({Deferred:function(a){var b=[["resolve","done",n.Callbacks("once memory"),"resolved"],["reject","fail",n.Callbacks("once memory"),"rejected"],["notify","progress",n.Callbacks("memory")]],c="pending",d={state:function(){return c},always:function(){return e.done(arguments).fail(arguments),this},then:function(){var a=arguments;return n.Deferred(function(c){n.each(b,function(b,f){var g=n.isFunction(a[b])&&a[b];e[f[1]](function(){var a=g&&g.apply(this,arguments);a&&n.isFunction(a.promise)?a.promise().done(c.resolve).fail(c.reject).progress(c.notify):c[f[0]+"With"](this===d?c.promise():this,g?[a]:arguments)})}),a=null}).promise()},promise:function(a){return null!=a?n.extend(a,d):d}},e={};return d.pipe=d.then,n.each(b,function(a,f){var g=f[2],h=f[3];d[f[1]]=g.add,h&&g.add(function(){c=h},b[1^a][2].disable,b[2][2].lock),e[f[0]]=function(){return e[f[0]+"With"](this===e?d:this,arguments),this},e[f[0]+"With"]=g.fireWith}),d.promise(e),a&&a.call(e,e),e},when:function(a){var b=0,c=d.call(arguments),e=c.length,f=1!==e||a&&n.isFunction(a.promise)?e:0,g=1===f?a:n.Deferred(),h=function(a,b,c){return function(e){b[a]=this,c[a]=arguments.length>1?d.call(arguments):e,c===i?g.notifyWith(b,c):--f||g.resolveWith(b,c)}},i,j,k;if(e>1)for(i=new Array(e),j=new Array(e),k=new Array(e);e>b;b++)c[b]&&n.isFunction(c[b].promise)?c[b].promise().done(h(b,k,c)).fail(g.reject).progress(h(b,j,i)):--f;return f||g.resolveWith(k,c),g.promise()}});var H;n.fn.ready=function(a){return n.ready.promise().done(a),this},n.extend({isReady:!1,readyWait:1,holdReady:function(a){a?n.readyWait++:n.ready(!0)},ready:function(a){(a===!0?--n.readyWait:n.isReady)||(n.isReady=!0,a!==!0&&--n.readyWait>0||(H.resolveWith(l,[n]),n.fn.triggerHandler&&(n(l).triggerHandler("ready"),n(l).off("ready"))))}});function I(){l.removeEventListener("DOMContentLoaded",I,!1),a.removeEventListener("load",I,!1),n.ready()}n.ready.promise=function(b){return H||(H=n.Deferred(),"complete"===l.readyState?setTimeout(n.ready):(l.addEventListener("DOMContentLoaded",I,!1),a.addEventListener("load",I,!1))),H.promise(b)},n.ready.promise();var J=n.access=function(a,b,c,d,e,f,g){var h=0,i=a.length,j=null==c;if("object"===n.type(c)){e=!0;for(h in c)n.access(a,b,h,c[h],!0,f,g)}else if(void 0!==d&&(e=!0,n.isFunction(d)||(g=!0),j&&(g?(b.call(a,d),b=null):(j=b,b=function(a,b,c){return j.call(n(a),c)})),b))for(;i>h;h++)b(a[h],c,g?d:d.call(a[h],h,b(a[h],c)));return e?a:j?b.call(a):i?b(a[0],c):f};n.acceptData=function(a){return 1===a.nodeType||9===a.nodeType||!+a.nodeType};function K(){Object.defineProperty(this.cache={},0,{get:function(){return{}}}),this.expando=n.expando+K.uid++}K.uid=1,K.accepts=n.acceptData,K.prototype={key:function(a){if(!K.accepts(a))return 0;var b={},c=a[this.expando];if(!c){c=K.uid++;try{b[this.expando]={value:c},Object.defineProperties(a,b)}catch(d){b[this.expando]=c,n.extend(a,b)}}return this.cache[c]||(this.cache[c]={}),c},set:function(a,b,c){var d,e=this.key(a),f=this.cache[e];if("string"==typeof b)f[b]=c;else if(n.isEmptyObject(f))n.extend(this.cache[e],b);else for(d in b)f[d]=b[d];return f},get:function(a,b){var c=this.cache[this.key(a)];return void 0===b?c:c[b]},access:function(a,b,c){var d;return void 0===b||b&&"string"==typeof b&&void 0===c?(d=this.get(a,b),void 0!==d?d:this.get(a,n.camelCase(b))):(this.set(a,b,c),void 0!==c?c:b)},remove:function(a,b){var c,d,e,f=this.key(a),g=this.cache[f];if(void 0===b)this.cache[f]={};else{n.isArray(b)?d=b.concat(b.map(n.camelCase)):(e=n.camelCase(b),b in g?d=[b,e]:(d=e,d=d in g?[d]:d.match(E)||[])),c=d.length;while(c--)delete g[d[c]]}},hasData:function(a){return!n.isEmptyObject(this.cache[a[this.expando]]||{})},discard:function(a){a[this.expando]&&delete this.cache[a[this.expando]]}};var L=new K,M=new K,N=/^(?:\{[\w\W]*\}|\[[\w\W]*\])$/,O=/([A-Z])/g;function P(a,b,c){var d;if(void 0===c&&1===a.nodeType)if(d="data-"+b.replace(O,"-$1").toLowerCase(),c=a.getAttribute(d),"string"==typeof c){try{c="true"===c?!0:"false"===c?!1:"null"===c?null:+c+""===c?+c:N.test(c)?n.parseJSON(c):c}catch(e){}M.set(a,b,c)}else c=void 0;return c}n.extend({hasData:function(a){return M.hasData(a)||L.hasData(a)},data:function(a,b,c){ +return M.access(a,b,c)},removeData:function(a,b){M.remove(a,b)},_data:function(a,b,c){return L.access(a,b,c)},_removeData:function(a,b){L.remove(a,b)}}),n.fn.extend({data:function(a,b){var c,d,e,f=this[0],g=f&&f.attributes;if(void 0===a){if(this.length&&(e=M.get(f),1===f.nodeType&&!L.get(f,"hasDataAttrs"))){c=g.length;while(c--)g[c]&&(d=g[c].name,0===d.indexOf("data-")&&(d=n.camelCase(d.slice(5)),P(f,d,e[d])));L.set(f,"hasDataAttrs",!0)}return e}return"object"==typeof a?this.each(function(){M.set(this,a)}):J(this,function(b){var c,d=n.camelCase(a);if(f&&void 0===b){if(c=M.get(f,a),void 0!==c)return c;if(c=M.get(f,d),void 0!==c)return c;if(c=P(f,d,void 0),void 0!==c)return c}else this.each(function(){var c=M.get(this,d);M.set(this,d,b),-1!==a.indexOf("-")&&void 0!==c&&M.set(this,a,b)})},null,b,arguments.length>1,null,!0)},removeData:function(a){return this.each(function(){M.remove(this,a)})}}),n.extend({queue:function(a,b,c){var d;return a?(b=(b||"fx")+"queue",d=L.get(a,b),c&&(!d||n.isArray(c)?d=L.access(a,b,n.makeArray(c)):d.push(c)),d||[]):void 0},dequeue:function(a,b){b=b||"fx";var c=n.queue(a,b),d=c.length,e=c.shift(),f=n._queueHooks(a,b),g=function(){n.dequeue(a,b)};"inprogress"===e&&(e=c.shift(),d--),e&&("fx"===b&&c.unshift("inprogress"),delete f.stop,e.call(a,g,f)),!d&&f&&f.empty.fire()},_queueHooks:function(a,b){var c=b+"queueHooks";return L.get(a,c)||L.access(a,c,{empty:n.Callbacks("once memory").add(function(){L.remove(a,[b+"queue",c])})})}}),n.fn.extend({queue:function(a,b){var c=2;return"string"!=typeof a&&(b=a,a="fx",c--),arguments.lengthx",k.noCloneChecked=!!b.cloneNode(!0).lastChild.defaultValue}();var U="undefined";k.focusinBubbles="onfocusin"in a;var V=/^key/,W=/^(?:mouse|pointer|contextmenu)|click/,X=/^(?:focusinfocus|focusoutblur)$/,Y=/^([^.]*)(?:\.(.+)|)$/;function Z(){return!0}function $(){return!1}function _(){try{return l.activeElement}catch(a){}}n.event={global:{},add:function(a,b,c,d,e){var f,g,h,i,j,k,l,m,o,p,q,r=L.get(a);if(r){c.handler&&(f=c,c=f.handler,e=f.selector),c.guid||(c.guid=n.guid++),(i=r.events)||(i=r.events={}),(g=r.handle)||(g=r.handle=function(b){return typeof n!==U&&n.event.triggered!==b.type?n.event.dispatch.apply(a,arguments):void 0}),b=(b||"").match(E)||[""],j=b.length;while(j--)h=Y.exec(b[j])||[],o=q=h[1],p=(h[2]||"").split(".").sort(),o&&(l=n.event.special[o]||{},o=(e?l.delegateType:l.bindType)||o,l=n.event.special[o]||{},k=n.extend({type:o,origType:q,data:d,handler:c,guid:c.guid,selector:e,needsContext:e&&n.expr.match.needsContext.test(e),namespace:p.join(".")},f),(m=i[o])||(m=i[o]=[],m.delegateCount=0,l.setup&&l.setup.call(a,d,p,g)!==!1||a.addEventListener&&a.addEventListener(o,g,!1)),l.add&&(l.add.call(a,k),k.handler.guid||(k.handler.guid=c.guid)),e?m.splice(m.delegateCount++,0,k):m.push(k),n.event.global[o]=!0)}},remove:function(a,b,c,d,e){var f,g,h,i,j,k,l,m,o,p,q,r=L.hasData(a)&&L.get(a);if(r&&(i=r.events)){b=(b||"").match(E)||[""],j=b.length;while(j--)if(h=Y.exec(b[j])||[],o=q=h[1],p=(h[2]||"").split(".").sort(),o){l=n.event.special[o]||{},o=(d?l.delegateType:l.bindType)||o,m=i[o]||[],h=h[2]&&new RegExp("(^|\\.)"+p.join("\\.(?:.*\\.|)")+"(\\.|$)"),g=f=m.length;while(f--)k=m[f],!e&&q!==k.origType||c&&c.guid!==k.guid||h&&!h.test(k.namespace)||d&&d!==k.selector&&("**"!==d||!k.selector)||(m.splice(f,1),k.selector&&m.delegateCount--,l.remove&&l.remove.call(a,k));g&&!m.length&&(l.teardown&&l.teardown.call(a,p,r.handle)!==!1||n.removeEvent(a,o,r.handle),delete i[o])}else for(o in i)n.event.remove(a,o+b[j],c,d,!0);n.isEmptyObject(i)&&(delete r.handle,L.remove(a,"events"))}},trigger:function(b,c,d,e){var f,g,h,i,k,m,o,p=[d||l],q=j.call(b,"type")?b.type:b,r=j.call(b,"namespace")?b.namespace.split("."):[];if(g=h=d=d||l,3!==d.nodeType&&8!==d.nodeType&&!X.test(q+n.event.triggered)&&(q.indexOf(".")>=0&&(r=q.split("."),q=r.shift(),r.sort()),k=q.indexOf(":")<0&&"on"+q,b=b[n.expando]?b:new n.Event(q,"object"==typeof b&&b),b.isTrigger=e?2:3,b.namespace=r.join("."),b.namespace_re=b.namespace?new RegExp("(^|\\.)"+r.join("\\.(?:.*\\.|)")+"(\\.|$)"):null,b.result=void 0,b.target||(b.target=d),c=null==c?[b]:n.makeArray(c,[b]),o=n.event.special[q]||{},e||!o.trigger||o.trigger.apply(d,c)!==!1)){if(!e&&!o.noBubble&&!n.isWindow(d)){for(i=o.delegateType||q,X.test(i+q)||(g=g.parentNode);g;g=g.parentNode)p.push(g),h=g;h===(d.ownerDocument||l)&&p.push(h.defaultView||h.parentWindow||a)}f=0;while((g=p[f++])&&!b.isPropagationStopped())b.type=f>1?i:o.bindType||q,m=(L.get(g,"events")||{})[b.type]&&L.get(g,"handle"),m&&m.apply(g,c),m=k&&g[k],m&&m.apply&&n.acceptData(g)&&(b.result=m.apply(g,c),b.result===!1&&b.preventDefault());return b.type=q,e||b.isDefaultPrevented()||o._default&&o._default.apply(p.pop(),c)!==!1||!n.acceptData(d)||k&&n.isFunction(d[q])&&!n.isWindow(d)&&(h=d[k],h&&(d[k]=null),n.event.triggered=q,d[q](),n.event.triggered=void 0,h&&(d[k]=h)),b.result}},dispatch:function(a){a=n.event.fix(a);var b,c,e,f,g,h=[],i=d.call(arguments),j=(L.get(this,"events")||{})[a.type]||[],k=n.event.special[a.type]||{};if(i[0]=a,a.delegateTarget=this,!k.preDispatch||k.preDispatch.call(this,a)!==!1){h=n.event.handlers.call(this,a,j),b=0;while((f=h[b++])&&!a.isPropagationStopped()){a.currentTarget=f.elem,c=0;while((g=f.handlers[c++])&&!a.isImmediatePropagationStopped())(!a.namespace_re||a.namespace_re.test(g.namespace))&&(a.handleObj=g,a.data=g.data,e=((n.event.special[g.origType]||{}).handle||g.handler).apply(f.elem,i),void 0!==e&&(a.result=e)===!1&&(a.preventDefault(),a.stopPropagation()))}return k.postDispatch&&k.postDispatch.call(this,a),a.result}},handlers:function(a,b){var c,d,e,f,g=[],h=b.delegateCount,i=a.target;if(h&&i.nodeType&&(!a.button||"click"!==a.type))for(;i!==this;i=i.parentNode||this)if(i.disabled!==!0||"click"!==a.type){for(d=[],c=0;h>c;c++)f=b[c],e=f.selector+" ",void 0===d[e]&&(d[e]=f.needsContext?n(e,this).index(i)>=0:n.find(e,this,null,[i]).length),d[e]&&d.push(f);d.length&&g.push({elem:i,handlers:d})}return h]*)\/>/gi,ba=/<([\w:]+)/,ca=/<|&#?\w+;/,da=/<(?:script|style|link)/i,ea=/checked\s*(?:[^=]|=\s*.checked.)/i,fa=/^$|\/(?:java|ecma)script/i,ga=/^true\/(.*)/,ha=/^\s*\s*$/g,ia={option:[1,""],thead:[1,"","
"],col:[2,"","
"],tr:[2,"","
"],td:[3,"","
"],_default:[0,"",""]};ia.optgroup=ia.option,ia.tbody=ia.tfoot=ia.colgroup=ia.caption=ia.thead,ia.th=ia.td;function ja(a,b){return n.nodeName(a,"table")&&n.nodeName(11!==b.nodeType?b:b.firstChild,"tr")?a.getElementsByTagName("tbody")[0]||a.appendChild(a.ownerDocument.createElement("tbody")):a}function ka(a){return a.type=(null!==a.getAttribute("type"))+"/"+a.type,a}function la(a){var b=ga.exec(a.type);return b?a.type=b[1]:a.removeAttribute("type"),a}function ma(a,b){for(var c=0,d=a.length;d>c;c++)L.set(a[c],"globalEval",!b||L.get(b[c],"globalEval"))}function na(a,b){var c,d,e,f,g,h,i,j;if(1===b.nodeType){if(L.hasData(a)&&(f=L.access(a),g=L.set(b,f),j=f.events)){delete g.handle,g.events={};for(e in j)for(c=0,d=j[e].length;d>c;c++)n.event.add(b,e,j[e][c])}M.hasData(a)&&(h=M.access(a),i=n.extend({},h),M.set(b,i))}}function oa(a,b){var c=a.getElementsByTagName?a.getElementsByTagName(b||"*"):a.querySelectorAll?a.querySelectorAll(b||"*"):[];return void 0===b||b&&n.nodeName(a,b)?n.merge([a],c):c}function pa(a,b){var c=b.nodeName.toLowerCase();"input"===c&&T.test(a.type)?b.checked=a.checked:("input"===c||"textarea"===c)&&(b.defaultValue=a.defaultValue)}n.extend({clone:function(a,b,c){var d,e,f,g,h=a.cloneNode(!0),i=n.contains(a.ownerDocument,a);if(!(k.noCloneChecked||1!==a.nodeType&&11!==a.nodeType||n.isXMLDoc(a)))for(g=oa(h),f=oa(a),d=0,e=f.length;e>d;d++)pa(f[d],g[d]);if(b)if(c)for(f=f||oa(a),g=g||oa(h),d=0,e=f.length;e>d;d++)na(f[d],g[d]);else na(a,h);return g=oa(h,"script"),g.length>0&&ma(g,!i&&oa(a,"script")),h},buildFragment:function(a,b,c,d){for(var e,f,g,h,i,j,k=b.createDocumentFragment(),l=[],m=0,o=a.length;o>m;m++)if(e=a[m],e||0===e)if("object"===n.type(e))n.merge(l,e.nodeType?[e]:e);else if(ca.test(e)){f=f||k.appendChild(b.createElement("div")),g=(ba.exec(e)||["",""])[1].toLowerCase(),h=ia[g]||ia._default,f.innerHTML=h[1]+e.replace(aa,"<$1>")+h[2],j=h[0];while(j--)f=f.lastChild;n.merge(l,f.childNodes),f=k.firstChild,f.textContent=""}else l.push(b.createTextNode(e));k.textContent="",m=0;while(e=l[m++])if((!d||-1===n.inArray(e,d))&&(i=n.contains(e.ownerDocument,e),f=oa(k.appendChild(e),"script"),i&&ma(f),c)){j=0;while(e=f[j++])fa.test(e.type||"")&&c.push(e)}return k},cleanData:function(a){for(var b,c,d,e,f=n.event.special,g=0;void 0!==(c=a[g]);g++){if(n.acceptData(c)&&(e=c[L.expando],e&&(b=L.cache[e]))){if(b.events)for(d in b.events)f[d]?n.event.remove(c,d):n.removeEvent(c,d,b.handle);L.cache[e]&&delete L.cache[e]}delete M.cache[c[M.expando]]}}}),n.fn.extend({text:function(a){return J(this,function(a){return void 0===a?n.text(this):this.empty().each(function(){(1===this.nodeType||11===this.nodeType||9===this.nodeType)&&(this.textContent=a)})},null,a,arguments.length)},append:function(){return this.domManip(arguments,function(a){if(1===this.nodeType||11===this.nodeType||9===this.nodeType){var b=ja(this,a);b.appendChild(a)}})},prepend:function(){return this.domManip(arguments,function(a){if(1===this.nodeType||11===this.nodeType||9===this.nodeType){var b=ja(this,a);b.insertBefore(a,b.firstChild)}})},before:function(){return this.domManip(arguments,function(a){this.parentNode&&this.parentNode.insertBefore(a,this)})},after:function(){return this.domManip(arguments,function(a){this.parentNode&&this.parentNode.insertBefore(a,this.nextSibling)})},remove:function(a,b){for(var c,d=a?n.filter(a,this):this,e=0;null!=(c=d[e]);e++)b||1!==c.nodeType||n.cleanData(oa(c)),c.parentNode&&(b&&n.contains(c.ownerDocument,c)&&ma(oa(c,"script")),c.parentNode.removeChild(c));return this},empty:function(){for(var a,b=0;null!=(a=this[b]);b++)1===a.nodeType&&(n.cleanData(oa(a,!1)),a.textContent="");return this},clone:function(a,b){return a=null==a?!1:a,b=null==b?a:b,this.map(function(){return n.clone(this,a,b)})},html:function(a){return J(this,function(a){var b=this[0]||{},c=0,d=this.length;if(void 0===a&&1===b.nodeType)return b.innerHTML;if("string"==typeof a&&!da.test(a)&&!ia[(ba.exec(a)||["",""])[1].toLowerCase()]){a=a.replace(aa,"<$1>");try{for(;d>c;c++)b=this[c]||{},1===b.nodeType&&(n.cleanData(oa(b,!1)),b.innerHTML=a);b=0}catch(e){}}b&&this.empty().append(a)},null,a,arguments.length)},replaceWith:function(){var a=arguments[0];return this.domManip(arguments,function(b){a=this.parentNode,n.cleanData(oa(this)),a&&a.replaceChild(b,this)}),a&&(a.length||a.nodeType)?this:this.remove()},detach:function(a){return this.remove(a,!0)},domManip:function(a,b){a=e.apply([],a);var c,d,f,g,h,i,j=0,l=this.length,m=this,o=l-1,p=a[0],q=n.isFunction(p);if(q||l>1&&"string"==typeof p&&!k.checkClone&&ea.test(p))return this.each(function(c){var d=m.eq(c);q&&(a[0]=p.call(this,c,d.html())),d.domManip(a,b)});if(l&&(c=n.buildFragment(a,this[0].ownerDocument,!1,this),d=c.firstChild,1===c.childNodes.length&&(c=d),d)){for(f=n.map(oa(c,"script"),ka),g=f.length;l>j;j++)h=c,j!==o&&(h=n.clone(h,!0,!0),g&&n.merge(f,oa(h,"script"))),b.call(this[j],h,j);if(g)for(i=f[f.length-1].ownerDocument,n.map(f,la),j=0;g>j;j++)h=f[j],fa.test(h.type||"")&&!L.access(h,"globalEval")&&n.contains(i,h)&&(h.src?n._evalUrl&&n._evalUrl(h.src):n.globalEval(h.textContent.replace(ha,"")))}return this}}),n.each({appendTo:"append",prependTo:"prepend",insertBefore:"before",insertAfter:"after",replaceAll:"replaceWith"},function(a,b){n.fn[a]=function(a){for(var c,d=[],e=n(a),g=e.length-1,h=0;g>=h;h++)c=h===g?this:this.clone(!0),n(e[h])[b](c),f.apply(d,c.get());return this.pushStack(d)}});var qa,ra={};function sa(b,c){var d,e=n(c.createElement(b)).appendTo(c.body),f=a.getDefaultComputedStyle&&(d=a.getDefaultComputedStyle(e[0]))?d.display:n.css(e[0],"display");return e.detach(),f}function ta(a){var b=l,c=ra[a];return c||(c=sa(a,b),"none"!==c&&c||(qa=(qa||n("