diff --git a/interface/CMakeLists.txt b/interface/CMakeLists.txt index 9d56764ffa..ecc6ff79cb 100644 --- a/interface/CMakeLists.txt +++ b/interface/CMakeLists.txt @@ -93,6 +93,7 @@ include(${MACRO_DIR}/LinkHifiLibrary.cmake) link_hifi_library(shared ${TARGET_NAME} ${ROOT_DIR}) link_hifi_library(octree ${TARGET_NAME} ${ROOT_DIR}) link_hifi_library(voxels ${TARGET_NAME} ${ROOT_DIR}) +link_hifi_library(metavoxels ${TARGET_NAME} ${ROOT_DIR}) link_hifi_library(particles ${TARGET_NAME} ${ROOT_DIR}) link_hifi_library(avatars ${TARGET_NAME} ${ROOT_DIR}) link_hifi_library(audio ${TARGET_NAME} ${ROOT_DIR}) @@ -135,7 +136,7 @@ if (LIBOVR_FOUND AND NOT DISABLE_LIBOVR) target_link_libraries(${TARGET_NAME} ${LIBOVR_LIBRARIES}) endif (LIBOVR_FOUND AND NOT DISABLE_LIBOVR) -qt5_use_modules(${TARGET_NAME} Core Gui Multimedia Network OpenGL Svg WebKit WebKitWidgets) +qt5_use_modules(${TARGET_NAME} Core Gui Multimedia Network OpenGL Script Svg WebKit WebKitWidgets) # include headers for interface and InterfaceConfig. include_directories( diff --git a/interface/resources/scripts/sphere.js b/interface/resources/scripts/sphere.js new file mode 100644 index 0000000000..403374e812 --- /dev/null +++ b/interface/resources/scripts/sphere.js @@ -0,0 +1,171 @@ +// +// sphere.js +// interface +// +// Created by Andrzej Kapolka on 12/17/13. +// Copyright (c) 2013 High Fidelity, Inc. All rights reserved. +// + +function strictIndexOf(array, element) { + for (var i = 0; i < array.length; i++) { + if (array[i] == element) { + return i; + } + } + return -1; +} + +var colorIndex; +var normalIndex; +var visitor; +var info; + +var MAX_DEPTH = 4; + +var sphereCenter = [ 0.5, 0.5, 0.5 ]; +var sphereColor = 0xFFFF00FF; +var sphereRadius = 0.25; +var sphereRadiusSquared = sphereRadius * sphereRadius; + +function lengthSquared(x, y, z) { + return x*x + y*y + z*z; +} + +function setNormal(vector) { + if (normalIndex != -1) { + var length = Math.sqrt(lengthSquared(vector[0], vector[1], vector[2])); + if (length == 0.0) { + info.attributeValues[normalIndex] = 0x007F00; + + } else { + var scale = 127.0 / length; + info.attributeValues[normalIndex] = + (Math.floor(vector[0] * scale) & 0xFF) << 16 | + (Math.floor(vector[1] * scale) & 0xFF) << 8 | + Math.floor(vector[2] * scale) & 0xFF; + } + } +} + +function guide(minimum, size, depth) { + info.minimum = minimum; + info.size = size; + + // start with a relative fast bounding volume test to find most non-intersecting states + var maximum = [ minimum[0] + size, minimum[1] + size, minimum[2] + size ]; + if (minimum[0] >= sphereCenter[0] + sphereRadius || + minimum[1] >= sphereCenter[1] + sphereRadius || + minimum[2] >= sphereCenter[2] + sphereRadius || + maximum[0] <= sphereCenter[0] - sphereRadius || + maximum[1] <= sphereCenter[1] - sphereRadius || + maximum[2] <= sphereCenter[2] - sphereRadius) { + info.isLeaf = true; + if (colorIndex != -1) { + info.attributeValues[colorIndex] = 0x0; + } + visitor.visit(info); + return; + } + + var halfSize = size / 2; + var center = [ minimum[0] + halfSize, minimum[1] + halfSize, minimum[2] + halfSize ]; + var vector = [ center[0] - sphereCenter[0], center[1] - sphereCenter[1], center[2] - sphereCenter[2] ]; + + // count the number of points inside the sphere + var inside = 0; + if (lengthSquared(sphereCenter[0] - minimum[0], sphereCenter[1] - minimum[1], sphereCenter[2] - minimum[2]) <= + sphereRadiusSquared) { + inside++; + } + if (lengthSquared(sphereCenter[0] - maximum[0], sphereCenter[1] - minimum[1], sphereCenter[2] - minimum[2]) <= + sphereRadiusSquared) { + inside++; + } + if (lengthSquared(sphereCenter[0] - minimum[0], sphereCenter[1] - maximum[1], sphereCenter[2] - minimum[2]) <= + sphereRadiusSquared) { + inside++; + } + if (lengthSquared(sphereCenter[0] - maximum[0], sphereCenter[1] - maximum[1], sphereCenter[2] - minimum[2]) <= + sphereRadiusSquared) { + inside++; + } + if (lengthSquared(sphereCenter[0] - minimum[0], sphereCenter[1] - minimum[1], sphereCenter[2] - maximum[2]) <= + sphereRadiusSquared) { + inside++; + } + if (lengthSquared(sphereCenter[0] - maximum[0], sphereCenter[1] - minimum[1], sphereCenter[2] - maximum[2]) <= + sphereRadiusSquared) { + inside++; + } + if (lengthSquared(sphereCenter[0] - minimum[0], sphereCenter[1] - maximum[1], sphereCenter[2] - maximum[2]) <= + sphereRadiusSquared) { + inside++; + } + if (lengthSquared(sphereCenter[0] - maximum[0], sphereCenter[1] - maximum[1], sphereCenter[2] - maximum[2]) <= + sphereRadiusSquared) { + inside++; + } + + // see if all points are in the sphere + if (inside == 8) { + info.isLeaf = true; + if (colorIndex != -1) { + info.attributeValues[colorIndex] = sphereColor; + } + setNormal(vector); + visitor.visit(info); + return; + } + + // if we've reached max depth, compute alpha using a volume estimate + if (depth == MAX_DEPTH) { + info.isLeaf = true; + if (inside >= 3) { + if (colorIndex != -1) { + info.attributeValues[colorIndex] = sphereColor; + } + setNormal(vector); + + } else { + if (colorIndex != -1) { + info.attributeValues[colorIndex] = 0x0; + } + } + visitor.visit(info); + return; + } + + // recurse + info.isLeaf = false; + if (!visitor.visit(info)) { + return; + } + depth += 1; + guide(minimum, halfSize, depth); + guide([ center[0], minimum[1], minimum[2] ], halfSize, depth); + guide([ minimum[0], center[1], minimum[2] ], halfSize, depth); + guide([ center[0], center[1], minimum[2] ], halfSize, depth); + guide([ minimum[0], minimum[1], center[2] ], halfSize, depth); + guide([ center[0], minimum[1], center[2] ], halfSize, depth); + guide([ minimum[0], center[1], center[2] ], halfSize, depth); + guide([ center[0], center[1], center[2] ], halfSize, depth); +} + +(function(visitation) { + var attributes = visitation.visitor.getAttributes(); + colorIndex = strictIndexOf(attributes, AttributeRegistry.colorAttribute); + normalIndex = strictIndexOf(attributes, AttributeRegistry.normalAttribute); + visitor = visitation.visitor; + info = { attributeValues: new Array(attributes.length) }; + + // have the sphere orbit the center and pulse in size + var time = new Date().getTime(); + var ROTATE_PERIOD = 400.0; + sphereCenter[0] = 0.5 + 0.25 * Math.cos(time / ROTATE_PERIOD); + sphereCenter[2] = 0.5 + 0.25 * Math.sin(time / ROTATE_PERIOD); + var PULSE_PERIOD = 300.0; + sphereRadius = 0.25 + 0.0625 * Math.cos(time / PULSE_PERIOD); + sphereRadiusSquared = sphereRadius * sphereRadius; + + guide(visitation.info.minimum, visitation.info.size, 0); +}) diff --git a/interface/resources/shaders/metavoxel_point.vert b/interface/resources/shaders/metavoxel_point.vert new file mode 100644 index 0000000000..b0472088c0 --- /dev/null +++ b/interface/resources/shaders/metavoxel_point.vert @@ -0,0 +1,25 @@ +#version 120 + +// +// metavoxel_point.vert +// vertex shader +// +// Created by Andrzej Kapolka on 12/12/13. +// Copyright (c) 2013 High Fidelity, Inc. All rights reserved. +// + +uniform float pointScale; + +void main(void) { + + // standard diffuse lighting + gl_FrontColor = vec4(gl_Color.rgb * (gl_LightModel.ambient.rgb + gl_LightSource[0].ambient.rgb + + gl_LightSource[0].diffuse.rgb * max(0.0, dot(gl_NormalMatrix * gl_Normal, gl_LightSource[0].position.xyz))), + gl_Color.a); + + // extract the first three components of the vertex for position + gl_Position = gl_ModelViewProjectionMatrix * vec4(gl_Vertex.xyz, 1.0); + + // the final component is the size in world space + gl_PointSize = pointScale * gl_Vertex.w / gl_Position.w; +} diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index 5dff756eec..d5c874ba6b 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -1858,6 +1858,8 @@ void Application::init() { _particles.init(); _particles.setViewFrustum(getViewFrustum()); + _metavoxels.init(); + _particleCollisionSystem.init(&_particleEditSender, _particles.getTree(), _voxels.getTree(), &_audio, &_myAvatar); _palette.init(_glWidget->width(), _glWidget->height()); @@ -2376,6 +2378,15 @@ void Application::updateParticles(float deltaTime) { } } +void Application::updateMetavoxels(float deltaTime) { + bool showWarnings = Menu::getInstance()->isOptionChecked(MenuOption::PipelineWarnings); + PerformanceWarning warn(showWarnings, "Application::updateMetavoxels()"); + + if (Menu::getInstance()->isOptionChecked(MenuOption::Metavoxels)) { + _metavoxels.simulate(deltaTime); + } +} + void Application::updateTransmitter(float deltaTime) { bool showWarnings = Menu::getInstance()->isOptionChecked(MenuOption::PipelineWarnings); PerformanceWarning warn(showWarnings, "Application::updateTransmitter()"); @@ -2521,6 +2532,7 @@ void Application::update(float deltaTime) { updateAvatars(deltaTime, mouseRayOrigin, mouseRayDirection); //loop through all the other avatars and simulate them... updateMyAvatarSimulation(deltaTime); // Simulate myself updateParticles(deltaTime); // Simulate particle cloud movements + updateMetavoxels(deltaTime); // update metavoxels updateTransmitter(deltaTime); // transmitter drive or pick updateCamera(deltaTime); // handle various camera tweaks like off axis projection updateDialogs(deltaTime); // update various stats dialogs if present @@ -3068,6 +3080,13 @@ void Application::displaySide(Camera& whichCamera, bool selfAvatarOnly) { } } + // also, metavoxels + if (Menu::getInstance()->isOptionChecked(MenuOption::Metavoxels)) { + PerformanceWarning warn(Menu::getInstance()->isOptionChecked(MenuOption::PipelineWarnings), + "Application::displaySide() ... metavoxels..."); + _metavoxels.render(); + } + // render particles... _particles.render(); diff --git a/interface/src/Application.h b/interface/src/Application.h index 6a948f5b7a..5f8aaffd23 100644 --- a/interface/src/Application.h +++ b/interface/src/Application.h @@ -58,9 +58,10 @@ #include "renderer/AmbientOcclusionEffect.h" #include "renderer/GeometryCache.h" #include "renderer/GlowEffect.h" -#include "renderer/VoxelShader.h" +#include "renderer/MetavoxelSystem.h" #include "renderer/PointShader.h" #include "renderer/TextureCache.h" +#include "renderer/VoxelShader.h" #include "ui/BandwidthDialog.h" #include "ui/ChatEntry.h" #include "ui/VoxelStatsDialog.h" @@ -282,6 +283,7 @@ private: void updateThreads(float deltaTime); void updateMyAvatarSimulation(float deltaTime); void updateParticles(float deltaTime); + void updateMetavoxels(float deltaTime); void updateTransmitter(float deltaTime); void updateCamera(float deltaTime); void updateDialogs(float deltaTime); @@ -362,6 +364,8 @@ private: QByteArray _voxelsFilename; bool _wantToKillLocalVoxels; + MetavoxelSystem _metavoxels; + ViewFrustum _viewFrustum; // current state of view frustum, perspective, orientation, etc. Oscilloscope _audioScope; diff --git a/interface/src/Audio.cpp b/interface/src/Audio.cpp index 25a5f01d06..400ff534df 100644 --- a/interface/src/Audio.cpp +++ b/interface/src/Audio.cpp @@ -174,9 +174,8 @@ void linearResampling(int16_t* sourceSamples, int16_t* destinationSamples, if (sourceAudioFormat == destinationAudioFormat) { memcpy(destinationSamples, sourceSamples, numSourceSamples * sizeof(int16_t)); } else { - int destinationChannels = (destinationAudioFormat.channelCount() >= 2) ? 2 : destinationAudioFormat.channelCount(); float sourceToDestinationFactor = (sourceAudioFormat.sampleRate() / (float) destinationAudioFormat.sampleRate()) - * (sourceAudioFormat.channelCount() / (float) destinationChannels); + * (sourceAudioFormat.channelCount() / (float) destinationAudioFormat.channelCount()); // take into account the number of channels in source and destination // accomodate for the case where have an output with > 2 channels @@ -203,14 +202,15 @@ void linearResampling(int16_t* sourceSamples, int16_t* destinationSamples, // upsample from 24 to 48 // for now this only supports a stereo to stereo conversion - this is our case for network audio to output int sourceIndex = 0; - int destinationToSourceFactor = (1 / sourceToDestinationFactor); int dtsSampleRateFactor = (destinationAudioFormat.sampleRate() / sourceAudioFormat.sampleRate()); + int sampleShift = destinationAudioFormat.channelCount() * dtsSampleRateFactor; + int destinationToSourceFactor = (1 / sourceToDestinationFactor); - for (int i = 0; i < numDestinationSamples; i += destinationAudioFormat.channelCount() * dtsSampleRateFactor) { + for (int i = 0; i < numDestinationSamples; i += sampleShift) { sourceIndex = (i / destinationToSourceFactor); // fill the L/R channels and make the rest silent - for (int j = i; j < i + (dtsSampleRateFactor * destinationAudioFormat.channelCount()); j++) { + for (int j = i; j < i + sampleShift; j++) { if (j % destinationAudioFormat.channelCount() == 0) { // left channel destinationSamples[j] = sourceSamples[sourceIndex]; diff --git a/interface/src/Menu.cpp b/interface/src/Menu.cpp index 20afe38e67..ab725da812 100644 --- a/interface/src/Menu.cpp +++ b/interface/src/Menu.cpp @@ -285,7 +285,8 @@ Menu::Menu() : addCheckableActionToQMenuAndActionHash(renderOptionsMenu, MenuOption::ParticleCloud, 0, false); addCheckableActionToQMenuAndActionHash(renderOptionsMenu, MenuOption::Shadows, 0, false); - + addCheckableActionToQMenuAndActionHash(renderOptionsMenu, MenuOption::Metavoxels, 0, false); + QMenu* voxelOptionsMenu = developerMenu->addMenu("Voxel Options"); diff --git a/interface/src/Menu.h b/interface/src/Menu.h index f896423b00..44e5505f2d 100644 --- a/interface/src/Menu.h +++ b/interface/src/Menu.h @@ -219,6 +219,7 @@ namespace MenuOption { const QString Login = "Login"; const QString LookAtIndicator = "Look-at Indicator"; const QString LookAtVectors = "Look-at Vectors"; + const QString Metavoxels = "Metavoxels"; const QString Mirror = "Mirror"; const QString MoveWithLean = "Move with Lean"; const QString NewVoxelCullingMode = "New Voxel Culling Mode"; diff --git a/interface/src/renderer/MetavoxelSystem.cpp b/interface/src/renderer/MetavoxelSystem.cpp new file mode 100644 index 0000000000..543a8b6301 --- /dev/null +++ b/interface/src/renderer/MetavoxelSystem.cpp @@ -0,0 +1,122 @@ +// +// MetavoxelSystem.cpp +// interface +// +// Created by Andrzej Kapolka on 12/10/13. +// Copyright (c) 2013 High Fidelity, Inc. All rights reserved. +// + +#include +#include +#include + +#include + +#include "Application.h" +#include "MetavoxelSystem.h" + +ProgramObject MetavoxelSystem::_program; +int MetavoxelSystem::_pointScaleLocation; + +MetavoxelSystem::MetavoxelSystem() : + _pointVisitor(_points), + _buffer(QOpenGLBuffer::VertexBuffer) { +} + +void MetavoxelSystem::init() { + if (!_program.isLinked()) { + switchToResourcesParentIfRequired(); + _program.addShaderFromSourceFile(QGLShader::Vertex, "resources/shaders/metavoxel_point.vert"); + _program.link(); + + _pointScaleLocation = _program.uniformLocation("pointScale"); + } + + AttributeRegistry::getInstance()->configureScriptEngine(&_scriptEngine); + + QFile scriptFile("resources/scripts/sphere.js"); + scriptFile.open(QIODevice::ReadOnly); + QScriptValue guideFunction = _scriptEngine.evaluate(QTextStream(&scriptFile).readAll()); + _data.setAttributeValue(MetavoxelPath(), AttributeValue(AttributeRegistry::getInstance()->getGuideAttribute(), + encodeInline(PolymorphicDataPointer(new ScriptedMetavoxelGuide(guideFunction))))); + + _buffer.setUsagePattern(QOpenGLBuffer::DynamicDraw); + _buffer.create(); +} + +void MetavoxelSystem::simulate(float deltaTime) { + _points.clear(); + _data.guide(_pointVisitor); + + _buffer.bind(); + int bytes = _points.size() * sizeof(Point); + if (_buffer.size() < bytes) { + _buffer.allocate(_points.constData(), bytes); + } else { + _buffer.write(0, _points.constData(), bytes); + } + _buffer.release(); +} + +void MetavoxelSystem::render() { + int viewport[4]; + glGetIntegerv(GL_VIEWPORT, viewport); + const int VIEWPORT_WIDTH_INDEX = 2; + const int VIEWPORT_HEIGHT_INDEX = 3; + float viewportWidth = viewport[VIEWPORT_WIDTH_INDEX]; + float viewportHeight = viewport[VIEWPORT_HEIGHT_INDEX]; + float viewportDiagonal = sqrtf(viewportWidth*viewportWidth + viewportHeight*viewportHeight); + float worldDiagonal = glm::distance(Application::getInstance()->getViewFrustum()->getNearBottomLeft(), + Application::getInstance()->getViewFrustum()->getNearTopRight()); + + _program.bind(); + _program.setUniformValue(_pointScaleLocation, viewportDiagonal * + Application::getInstance()->getViewFrustum()->getNearClip() / worldDiagonal); + + _buffer.bind(); + + Point* pt = 0; + glVertexPointer(4, GL_FLOAT, sizeof(Point), &pt->vertex); + glColorPointer(4, GL_UNSIGNED_BYTE, sizeof(Point), &pt->color); + glNormalPointer(GL_BYTE, sizeof(Point), &pt->normal); + + glEnableClientState(GL_VERTEX_ARRAY); + glEnableClientState(GL_COLOR_ARRAY); + glEnableClientState(GL_NORMAL_ARRAY); + + glEnable(GL_VERTEX_PROGRAM_POINT_SIZE_ARB); + + glDrawArrays(GL_POINTS, 0, _points.size()); + + glDisable(GL_VERTEX_PROGRAM_POINT_SIZE_ARB); + + glDisableClientState(GL_VERTEX_ARRAY); + glDisableClientState(GL_COLOR_ARRAY); + glDisableClientState(GL_NORMAL_ARRAY); + + _buffer.release(); + + _program.release(); +} + +MetavoxelSystem::PointVisitor::PointVisitor(QVector& points) : + MetavoxelVisitor(QVector() << + AttributeRegistry::getInstance()->getColorAttribute() << + AttributeRegistry::getInstance()->getNormalAttribute()), + _points(points) { +} + +bool MetavoxelSystem::PointVisitor::visit(const MetavoxelInfo& info) { + if (!info.isLeaf) { + return true; + } + QRgb color = info.attributeValues.at(0).getInlineValue(); + QRgb normal = info.attributeValues.at(1).getInlineValue(); + int alpha = qAlpha(color); + if (alpha > 0) { + Point point = { glm::vec4(info.minimum + glm::vec3(info.size, info.size, info.size) * 0.5f, info.size), + { qRed(color), qGreen(color), qBlue(color), alpha }, { qRed(normal), qGreen(normal), qBlue(normal) } }; + _points.append(point); + } + return false; +} diff --git a/interface/src/renderer/MetavoxelSystem.h b/interface/src/renderer/MetavoxelSystem.h new file mode 100644 index 0000000000..b8617d99a4 --- /dev/null +++ b/interface/src/renderer/MetavoxelSystem.h @@ -0,0 +1,61 @@ +// +// MetavoxelSystem.h +// interface +// +// Created by Andrzej Kapolka on 12/10/13. +// Copyright (c) 2013 High Fidelity, Inc. All rights reserved. +// + +#ifndef __interface__MetavoxelSystem__ +#define __interface__MetavoxelSystem__ + +#include +#include +#include + +#include + +#include + +#include "ProgramObject.h" + +/// Renders a metavoxel tree. +class MetavoxelSystem { +public: + + MetavoxelSystem(); + + void init(); + + void simulate(float deltaTime); + void render(); + +private: + + class Point { + public: + glm::vec4 vertex; + quint8 color[4]; + quint8 normal[3]; + }; + + class PointVisitor : public MetavoxelVisitor { + public: + PointVisitor(QVector& points); + virtual bool visit(const MetavoxelInfo& info); + + private: + QVector& _points; + }; + + static ProgramObject _program; + static int _pointScaleLocation; + + QScriptEngine _scriptEngine; + MetavoxelData _data; + QVector _points; + PointVisitor _pointVisitor; + QOpenGLBuffer _buffer; +}; + +#endif /* defined(__interface__MetavoxelSystem__) */ diff --git a/libraries/metavoxels/CMakeLists.txt b/libraries/metavoxels/CMakeLists.txt new file mode 100644 index 0000000000..0f9c1c695c --- /dev/null +++ b/libraries/metavoxels/CMakeLists.txt @@ -0,0 +1,20 @@ +cmake_minimum_required(VERSION 2.8) + +set(ROOT_DIR ../..) +set(MACRO_DIR ${ROOT_DIR}/cmake/macros) + +# setup for find modules +set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} "${CMAKE_CURRENT_SOURCE_DIR}/../../cmake/modules/") + +set(TARGET_NAME metavoxels) + +find_package(Qt5Widgets REQUIRED) + +include(${MACRO_DIR}/SetupHifiLibrary.cmake) +setup_hifi_library(${TARGET_NAME}) + +qt5_use_modules(${TARGET_NAME} Widgets Script) + +include(${MACRO_DIR}/IncludeGLM.cmake) +include_glm(${TARGET_NAME} ${ROOT_DIR}) + diff --git a/libraries/metavoxels/src/AttributeRegistry.cpp b/libraries/metavoxels/src/AttributeRegistry.cpp new file mode 100644 index 0000000000..5fb03035da --- /dev/null +++ b/libraries/metavoxels/src/AttributeRegistry.cpp @@ -0,0 +1,143 @@ +// +// AttributeRegistry.cpp +// metavoxels +// +// Created by Andrzej Kapolka on 12/6/13. +// Copyright (c) 2013 High Fidelity, Inc. All rights reserved. +// + +#include + +#include "AttributeRegistry.h" +#include "MetavoxelData.h" + +AttributeRegistry AttributeRegistry::_instance; + +AttributeRegistry::AttributeRegistry() : + _guideAttribute(registerAttribute(new PolymorphicAttribute("guide", PolymorphicDataPointer(new DefaultMetavoxelGuide())))), + _colorAttribute(registerAttribute(new QRgbAttribute("color"))), + _normalAttribute(registerAttribute(new QRgbAttribute("normal", qRgb(0, 127, 0)))) { +} + +void AttributeRegistry::configureScriptEngine(QScriptEngine* engine) { + QScriptValue registry = engine->newObject(); + registry.setProperty("colorAttribute", engine->newQObject(_colorAttribute.data())); + registry.setProperty("normalAttribute", engine->newQObject(_normalAttribute.data())); + registry.setProperty("getAttribute", engine->newFunction(getAttribute, 1)); + engine->globalObject().setProperty("AttributeRegistry", registry); +} + +AttributePointer AttributeRegistry::registerAttribute(AttributePointer attribute) { + AttributePointer& pointer = _attributes[attribute->getName()]; + if (!pointer) { + pointer = attribute; + } + return pointer; +} + +QScriptValue AttributeRegistry::getAttribute(QScriptContext* context, QScriptEngine* engine) { + return engine->newQObject(_instance.getAttribute(context->argument(0).toString()).data(), QScriptEngine::QtOwnership, + QScriptEngine::PreferExistingWrapperObject); +} + +AttributeValue::AttributeValue(const AttributePointer& attribute) : + _attribute(attribute), _value(attribute ? attribute->getDefaultValue() : NULL) { +} + +AttributeValue::AttributeValue(const AttributePointer& attribute, void* value) : + _attribute(attribute), _value(value) { +} + +void* AttributeValue::copy() const { + return _attribute->create(_value); +} + +bool AttributeValue::isDefault() const { + return !_attribute || _attribute->equal(_value, _attribute->getDefaultValue()); +} + +bool AttributeValue::operator==(const AttributeValue& other) const { + return _attribute == other._attribute && (!_attribute || _attribute->equal(_value, other._value)); +} + +bool AttributeValue::operator==(void* other) const { + return _attribute && _attribute->equal(_value, other); +} + +OwnedAttributeValue::OwnedAttributeValue(const AttributePointer& attribute) : + AttributeValue(attribute, attribute ? attribute->create() : NULL) { +} + +OwnedAttributeValue::OwnedAttributeValue(const AttributePointer& attribute, void* value) : + AttributeValue(attribute, attribute ? attribute->create(value) : NULL) { +} + +OwnedAttributeValue::OwnedAttributeValue(const AttributeValue& other) : + AttributeValue(other.getAttribute(), other.getAttribute() ? other.copy() : NULL) { +} + +OwnedAttributeValue::~OwnedAttributeValue() { + if (_attribute) { + _attribute->destroy(_value); + } +} + +OwnedAttributeValue& OwnedAttributeValue::operator=(const AttributeValue& other) { + if (_attribute) { + _attribute->destroy(_value); + } + if ((_attribute = other.getAttribute())) { + _value = _attribute->create(other.getValue()); + } + return *this; +} + +Attribute::Attribute(const QString& name) { + setObjectName(name); +} + +Attribute::~Attribute() { +} + +QRgbAttribute::QRgbAttribute(const QString& name, QRgb defaultValue) : + InlineAttribute(name, defaultValue) { +} + +bool QRgbAttribute::merge(void*& parent, void* children[]) const { + QRgb firstValue = decodeInline(children[0]); + int totalRed = qRed(firstValue); + int totalGreen = qGreen(firstValue); + int totalBlue = qBlue(firstValue); + int totalAlpha = qAlpha(firstValue); + bool allChildrenEqual = true; + for (int i = 1; i < Attribute::MERGE_COUNT; i++) { + QRgb value = decodeInline(children[i]); + totalRed += qRed(value); + totalGreen += qGreen(value); + totalBlue += qBlue(value); + totalAlpha += qAlpha(value); + allChildrenEqual &= (firstValue == value); + } + parent = encodeInline(qRgba(totalRed / MERGE_COUNT, totalGreen / MERGE_COUNT, + totalBlue / MERGE_COUNT, totalAlpha / MERGE_COUNT)); + return allChildrenEqual; +} + +void* QRgbAttribute::createFromScript(const QScriptValue& value, QScriptEngine* engine) const { + return encodeInline((QRgb)value.toUInt32()); +} + +PolymorphicData::~PolymorphicData() { +} + +template<> PolymorphicData* QExplicitlySharedDataPointer::clone() { + return d->clone(); +} + +PolymorphicAttribute::PolymorphicAttribute(const QString& name, const PolymorphicDataPointer& defaultValue) : + InlineAttribute(name, defaultValue) { +} + +bool PolymorphicAttribute::merge(void*& parent, void* children[]) const { + return false; +} diff --git a/libraries/metavoxels/src/AttributeRegistry.h b/libraries/metavoxels/src/AttributeRegistry.h new file mode 100644 index 0000000000..a5f9c08f8b --- /dev/null +++ b/libraries/metavoxels/src/AttributeRegistry.h @@ -0,0 +1,275 @@ +// +// AttributeRegistry.h +// metavoxels +// +// Created by Andrzej Kapolka on 12/6/13. +// Copyright (c) 2013 High Fidelity, Inc. All rights reserved. +// + +#ifndef __interface__AttributeRegistry__ +#define __interface__AttributeRegistry__ + +#include +#include +#include +#include +#include +#include +#include + +#include "Bitstream.h" + +class QScriptContext; +class QScriptEngine; +class QScriptValue; + +class Attribute; + +typedef QSharedPointer AttributePointer; + +/// Maintains information about metavoxel attribute types. +class AttributeRegistry { +public: + + /// Returns a pointer to the singleton registry instance. + static AttributeRegistry* getInstance() { return &_instance; } + + AttributeRegistry(); + + /// Configures the supplied script engine with the global AttributeRegistry property. + void configureScriptEngine(QScriptEngine* engine); + + /// Registers an attribute with the system. The registry assumes ownership of the object. + /// \return either the pointer passed as an argument, if the attribute wasn't already registered, or the existing + /// attribute + AttributePointer registerAttribute(Attribute* attribute) { return registerAttribute(AttributePointer(attribute)); } + + /// Registers an attribute with the system. + /// \return either the pointer passed as an argument, if the attribute wasn't already registered, or the existing + /// attribute + AttributePointer registerAttribute(AttributePointer attribute); + + /// Retrieves an attribute by name. + AttributePointer getAttribute(const QString& name) const { return _attributes.value(name); } + + /// Returns a reference to the standard PolymorphicDataPointer "guide" attribute. + const AttributePointer& getGuideAttribute() const { return _guideAttribute; } + + /// Returns a reference to the standard QRgb "color" attribute. + const AttributePointer& getColorAttribute() const { return _colorAttribute; } + + /// Returns a reference to the standard QRgb "normal" attribute. + const AttributePointer& getNormalAttribute() const { return _normalAttribute; } + +private: + + static QScriptValue getAttribute(QScriptContext* context, QScriptEngine* engine); + + static AttributeRegistry _instance; + + QHash _attributes; + AttributePointer _guideAttribute; + AttributePointer _colorAttribute; + AttributePointer _normalAttribute; +}; + +/// Converts a value to a void pointer. +template inline void* encodeInline(T value) { + return *(void**)&value; +} + +/// Extracts a value from a void pointer. +template inline T decodeInline(void* value) { + return *(T*)&value; +} + +/// Pairs an attribute value with its type. +class AttributeValue { +public: + + AttributeValue(const AttributePointer& attribute = AttributePointer()); + AttributeValue(const AttributePointer& attribute, void* value); + + AttributePointer getAttribute() const { return _attribute; } + void* getValue() const { return _value; } + + template void setInlineValue(T value) { _value = encodeInline(value); } + template T getInlineValue() const { return decodeInline(_value); } + + template T* getPointerValue() const { return static_cast(_value); } + + void* copy() const; + + bool isDefault() const; + + bool operator==(const AttributeValue& other) const; + bool operator==(void* other) const; + +protected: + + AttributePointer _attribute; + void* _value; +}; + +// Assumes ownership of an attribute value. +class OwnedAttributeValue : public AttributeValue { +public: + + OwnedAttributeValue(const AttributePointer& attribute = AttributePointer()); + OwnedAttributeValue(const AttributePointer& attribute, void* value); + OwnedAttributeValue(const AttributeValue& other); + ~OwnedAttributeValue(); + + OwnedAttributeValue& operator=(const AttributeValue& other); +}; + +/// Represents a registered attribute. +class Attribute : public QObject { + Q_OBJECT + +public: + + static const int MERGE_COUNT = 8; + + Attribute(const QString& name); + virtual ~Attribute(); + + Q_INVOKABLE QString getName() const { return objectName(); } + + void* create() const { return create(getDefaultValue()); } + virtual void* create(void* copy) const = 0; + virtual void destroy(void* value) const = 0; + + virtual bool read(Bitstream& in, void*& value) const = 0; + virtual bool write(Bitstream& out, void* value) const = 0; + + virtual bool equal(void* first, void* second) const = 0; + + /// Merges the value of a parent and its children. + /// \return whether or not the children and parent values are all equal + virtual bool merge(void*& parent, void* children[]) const = 0; + + virtual void* getDefaultValue() const = 0; + + virtual void* createFromScript(const QScriptValue& value, QScriptEngine* engine) const { return create(); } +}; + +/// A simple attribute class that stores its values inline. +template class InlineAttribute : public Attribute { +public: + + InlineAttribute(const QString& name, const T& defaultValue = T()) : Attribute(name), _defaultValue(defaultValue) { } + + virtual void* create(void* copy) const { void* value; new (&value) T(*(T*)©); return value; } + virtual void destroy(void* value) const { ((T*)&value)->~T(); } + + virtual bool read(Bitstream& in, void*& value) const { value = getDefaultValue(); in.read(&value, bits); return false; } + virtual bool write(Bitstream& out, void* value) const { out.write(&value, bits); return false; } + + virtual bool equal(void* first, void* second) const { return decodeInline(first) == decodeInline(second); } + + virtual void* getDefaultValue() const { return encodeInline(_defaultValue); } + +private: + + T _defaultValue; +}; + +/// Provides merging using the =, ==, += and /= operators. +template class SimpleInlineAttribute : public InlineAttribute { +public: + + SimpleInlineAttribute(const QString& name, T defaultValue = T()) : InlineAttribute(name, defaultValue) { } + + virtual bool merge(void*& parent, void* children[]) const; +}; + +template inline bool SimpleInlineAttribute::merge(void*& parent, void* children[]) const { + T& merged = *(T*)&parent; + merged = decodeInline(children[0]); + bool allChildrenEqual = true; + for (int i = 1; i < Attribute::MERGE_COUNT; i++) { + merged += decodeInline(children[i]); + allChildrenEqual &= (decodeInline(children[0]) == decodeInline(children[i])); + } + merged /= Attribute::MERGE_COUNT; + return allChildrenEqual; +} + +/// Provides appropriate averaging for RGBA values. +class QRgbAttribute : public InlineAttribute { +public: + + QRgbAttribute(const QString& name, QRgb defaultValue = QRgb()); + + virtual bool merge(void*& parent, void* children[]) const; + + virtual void* createFromScript(const QScriptValue& value, QScriptEngine* engine) const; +}; + +/// An attribute class that stores pointers to its values. +template class PointerAttribute : public Attribute { +public: + + PointerAttribute(const QString& name, T defaultValue = T()) : Attribute(name), _defaultValue(defaultValue) { } + + virtual void* create(void* copy) const { new T(*static_cast(copy)); } + virtual void destroy(void* value) const { delete static_cast(value); } + + virtual bool read(Bitstream& in, void*& value) const { in >> *static_cast(value); return true; } + virtual bool write(Bitstream& out, void* value) const { out << *static_cast(value); return true; } + + virtual bool equal(void* first, void* second) const { return *static_cast(first) == *static_cast(second); } + + virtual void* getDefaultValue() const { return const_cast((void*)&_defaultValue); } + +private: + + T _defaultValue; +}; + +/// Provides merging using the =, ==, += and /= operators. +template class SimplePointerAttribute : public PointerAttribute { +public: + + SimplePointerAttribute(const QString& name, T defaultValue = T()) : PointerAttribute(name, defaultValue) { } + + virtual bool merge(void*& parent, void* children[]) const; +}; + +template inline bool SimplePointerAttribute::merge(void*& parent, void* children[]) const { + T& merged = *static_cast(parent); + merged = *static_cast(children[0]); + bool allChildrenEqual = true; + for (int i = 1; i < Attribute::MERGE_COUNT; i++) { + merged += *static_cast(children[i]); + allChildrenEqual &= (*static_cast(children[0]) == *static_cast(children[i])); + } + merged /= Attribute::MERGE_COUNT; + return allChildrenEqual; +} + +/// Base class for polymorphic attribute data. +class PolymorphicData : public QSharedData { +public: + + virtual ~PolymorphicData(); + + /// Creates a new clone of this object. + virtual PolymorphicData* clone() const = 0; +}; + +template<> PolymorphicData* QExplicitlySharedDataPointer::clone(); + +typedef QExplicitlySharedDataPointer PolymorphicDataPointer; + +/// Provides polymorphic streaming and averaging. +class PolymorphicAttribute : public InlineAttribute { +public: + + PolymorphicAttribute(const QString& name, const PolymorphicDataPointer& defaultValue = PolymorphicDataPointer()); + + virtual bool merge(void*& parent, void* children[]) const; +}; + +#endif /* defined(__interface__AttributeRegistry__) */ diff --git a/libraries/metavoxels/src/Bitstream.cpp b/libraries/metavoxels/src/Bitstream.cpp new file mode 100644 index 0000000000..ac91bdf767 --- /dev/null +++ b/libraries/metavoxels/src/Bitstream.cpp @@ -0,0 +1,81 @@ +// +// Bitstream.cpp +// metavoxels +// +// Created by Andrzej Kapolka on 12/2/13. +// Copyright (c) 2013 High Fidelity, Inc. All rights reserved. +// + +#include + +#include "Bitstream.h" + +Bitstream::Bitstream(QDataStream& underlying) + : _underlying(underlying), _byte(0), _position(0) { +} + +const int BITS_IN_BYTE = 8; +const int LAST_BIT_POSITION = BITS_IN_BYTE - 1; + +Bitstream& Bitstream::write(const void* data, int bits, int offset) { + const quint8* source = (const quint8*)data; + while (bits > 0) { + int bitsToWrite = qMin(BITS_IN_BYTE - _position, qMin(BITS_IN_BYTE - offset, bits)); + _byte |= ((*source >> offset) & ((1 << bitsToWrite) - 1)) << _position; + if ((_position += bitsToWrite) == BITS_IN_BYTE) { + flush(); + } + if ((offset += bitsToWrite) == BITS_IN_BYTE) { + source++; + offset = 0; + } + bits -= bitsToWrite; + } + return *this; +} + +Bitstream& Bitstream::read(void* data, int bits, int offset) { + quint8* dest = (quint8*)data; + while (bits > 0) { + if (_position == 0) { + _underlying >> _byte; + } + int bitsToRead = qMin(BITS_IN_BYTE - _position, qMin(BITS_IN_BYTE - offset, bits)); + *dest |= ((_byte >> _position) & ((1 << bitsToRead) - 1)) << offset; + _position = (_position + bitsToRead) & LAST_BIT_POSITION; + if ((offset += bitsToRead) == BITS_IN_BYTE) { + dest++; + offset = 0; + } + bits -= bitsToRead; + } + return *this; +} + +void Bitstream::flush() { + if (_position != 0) { + _underlying << _byte; + _byte = 0; + _position = 0; + } +} + + +Bitstream& Bitstream::operator<<(bool value) { + if (value) { + _byte |= (1 << _position); + } + if (++_position == BITS_IN_BYTE) { + flush(); + } + return *this; +} + +Bitstream& Bitstream::operator>>(bool& value) { + if (_position == 0) { + _underlying >> _byte; + } + value = _byte & (1 << _position); + _position = (_position + 1) & LAST_BIT_POSITION; + return *this; +} diff --git a/libraries/metavoxels/src/Bitstream.h b/libraries/metavoxels/src/Bitstream.h new file mode 100644 index 0000000000..12a1b88886 --- /dev/null +++ b/libraries/metavoxels/src/Bitstream.h @@ -0,0 +1,45 @@ +// +// Bitstream.h +// metavoxels +// +// Created by Andrzej Kapolka on 12/2/13. +// Copyright (c) 2013 High Fidelity, Inc. All rights reserved. +// + +#ifndef __interface__Bitstream__ +#define __interface__Bitstream__ + +#include + +class QDataStream; + +/// A stream for bit-aligned data. +class Bitstream { +public: + + Bitstream(QDataStream& underlying); + + /// Writes a set of bits to the underlying stream. + /// \param bits the number of bits to write + /// \param offset the offset of the first bit + Bitstream& write(const void* data, int bits, int offset = 0); + + /// Reads a set of bits from the underlying stream. + /// \param bits the number of bits to read + /// \param offset the offset of the first bit + Bitstream& read(void* data, int bits, int offset = 0); + + /// Flushes any unwritten bits to the underlying stream. + void flush(); + + Bitstream& operator<<(bool value); + Bitstream& operator>>(bool& value); + +private: + + QDataStream& _underlying; + quint8 _byte; + int _position; +}; + +#endif /* defined(__interface__Bitstream__) */ diff --git a/libraries/metavoxels/src/MetavoxelData.cpp b/libraries/metavoxels/src/MetavoxelData.cpp new file mode 100644 index 0000000000..636982b63a --- /dev/null +++ b/libraries/metavoxels/src/MetavoxelData.cpp @@ -0,0 +1,281 @@ +// +// MetavoxelData.cpp +// metavoxels +// +// Created by Andrzej Kapolka on 12/6/13. +// Copyright (c) 2013 High Fidelity, Inc. All rights reserved. +// + +#include +#include + +#include "MetavoxelData.h" + +MetavoxelData::~MetavoxelData() { + for (QHash::const_iterator it = _roots.constBegin(); it != _roots.constEnd(); it++) { + it.value()->destroy(it.key()); + delete it.value(); + } +} + +void MetavoxelData::guide(MetavoxelVisitor& visitor) { + // start with the root values/defaults (plus the guide attribute) + const float TOP_LEVEL_SIZE = 1.0f; + const QVector& attributes = visitor.getAttributes(); + MetavoxelVisitation firstVisitation = { visitor, QVector(attributes.size() + 1), + { glm::vec3(), TOP_LEVEL_SIZE, QVector(attributes.size() + 1) } }; + for (int i = 0; i < attributes.size(); i++) { + MetavoxelNode* node = _roots.value(attributes[i]); + firstVisitation.nodes[i] = node; + firstVisitation.info.attributeValues[i] = node ? node->getAttributeValue(attributes[i]) : attributes[i]; + } + AttributePointer guideAttribute = AttributeRegistry::getInstance()->getGuideAttribute(); + MetavoxelNode* node = _roots.value(guideAttribute); + firstVisitation.nodes.last() = node; + firstVisitation.info.attributeValues.last() = node ? node->getAttributeValue(guideAttribute) : guideAttribute; + static_cast(firstVisitation.info.attributeValues.last().getInlineValue< + PolymorphicDataPointer>().data())->guide(firstVisitation); +} + +void MetavoxelData::setAttributeValue(const MetavoxelPath& path, const AttributeValue& attributeValue) { + MetavoxelNode*& node = _roots[attributeValue.getAttribute()]; + if (node == NULL) { + node = new MetavoxelNode(attributeValue.getAttribute()); + } + if (node->setAttributeValue(path, 0, attributeValue) && attributeValue.isDefault()) { + node->destroy(attributeValue.getAttribute()); + delete node; + _roots.remove(attributeValue.getAttribute()); + } +} + +AttributeValue MetavoxelData::getAttributeValue(const MetavoxelPath& path, const AttributePointer& attribute) const { + MetavoxelNode* node = _roots.value(attribute); + if (node == NULL) { + return AttributeValue(attribute); + } + for (int i = 0, n = path.getSize(); i < n; i++) { + MetavoxelNode* child = node->getChild(path[i]); + if (child == NULL) { + return node->getAttributeValue(attribute); + } + node = child; + } + return node->getAttributeValue(attribute); +} + +MetavoxelNode::MetavoxelNode(const AttributeValue& attributeValue) { + _attributeValue = attributeValue.copy(); + for (int i = 0; i < CHILD_COUNT; i++) { + _children[i] = NULL; + } +} + +bool MetavoxelNode::setAttributeValue(const MetavoxelPath& path, int index, const AttributeValue& attributeValue) { + if (index == path.getSize()) { + setAttributeValue(attributeValue); + return true; + } + int element = path[index]; + if (_children[element] == NULL) { + AttributeValue ownAttributeValue = getAttributeValue(attributeValue.getAttribute()); + for (int i = 0; i < CHILD_COUNT; i++) { + _children[i] = new MetavoxelNode(ownAttributeValue); + } + } + _children[element]->setAttributeValue(path, index + 1, attributeValue); + + void* childValues[CHILD_COUNT]; + bool allLeaves = true; + for (int i = 0; i < CHILD_COUNT; i++) { + childValues[i] = _children[i]->_attributeValue; + allLeaves &= _children[i]->isLeaf(); + } + if (attributeValue.getAttribute()->merge(_attributeValue, childValues) && allLeaves) { + clearChildren(attributeValue.getAttribute()); + return true; + } + + return false; +} + +void MetavoxelNode::setAttributeValue(const AttributeValue& attributeValue) { + attributeValue.getAttribute()->destroy(_attributeValue); + _attributeValue = attributeValue.copy(); + clearChildren(attributeValue.getAttribute()); +} + +AttributeValue MetavoxelNode::getAttributeValue(const AttributePointer& attribute) const { + return AttributeValue(attribute, _attributeValue); +} + +bool MetavoxelNode::isLeaf() const { + for (int i = 0; i < CHILD_COUNT; i++) { + if (_children[i]) { + return false; + } + } + return true; +} + +void MetavoxelNode::destroy(const AttributePointer& attribute) { + attribute->destroy(_attributeValue); + for (int i = 0; i < CHILD_COUNT; i++) { + if (_children[i]) { + _children[i]->destroy(attribute); + delete _children[i]; + } + } +} + +void MetavoxelNode::clearChildren(const AttributePointer& attribute) { + for (int i = 0; i < CHILD_COUNT; i++) { + if (_children[i]) { + _children[i]->destroy(attribute); + delete _children[i]; + _children[i] = NULL; + } + } +} + +int MetavoxelPath::operator[](int index) const { + return _array.at(index * BITS_PER_ELEMENT) | (_array.at(index * BITS_PER_ELEMENT + 1) << 1) | + (_array.at(index * BITS_PER_ELEMENT + 2) << 2); +} + +MetavoxelPath& MetavoxelPath::operator+=(int element) { + int offset = _array.size(); + _array.resize(offset + BITS_PER_ELEMENT); + _array.setBit(offset, element & 0x01); + _array.setBit(offset + 1, (element >> 1) & 0x01); + _array.setBit(offset + 2, element >> 2); + return *this; +} + +PolymorphicData* DefaultMetavoxelGuide::clone() const { + return new DefaultMetavoxelGuide(); +} + +const int X_MAXIMUM_FLAG = 1; +const int Y_MAXIMUM_FLAG = 2; +const int Z_MAXIMUM_FLAG = 4; + +void DefaultMetavoxelGuide::guide(MetavoxelVisitation& visitation) { + visitation.info.isLeaf = visitation.allNodesLeaves(); + if (!visitation.visitor.visit(visitation.info) || visitation.info.isLeaf) { + return; + } + MetavoxelVisitation nextVisitation = { visitation.visitor, QVector(visitation.nodes.size()), + { glm::vec3(), visitation.info.size * 0.5f, QVector(visitation.nodes.size()) } }; + for (int i = 0; i < MetavoxelNode::CHILD_COUNT; i++) { + for (int j = 0; j < visitation.nodes.size(); j++) { + MetavoxelNode* node = visitation.nodes.at(j); + MetavoxelNode* child = node ? node->getChild(i) : NULL; + nextVisitation.info.attributeValues[j] = ((nextVisitation.nodes[j] = child)) ? + child->getAttributeValue(visitation.info.attributeValues[j].getAttribute()) : + visitation.info.attributeValues[j]; + } + nextVisitation.info.minimum = visitation.info.minimum + glm::vec3( + (i & X_MAXIMUM_FLAG) ? nextVisitation.info.size : 0.0f, + (i & Y_MAXIMUM_FLAG) ? nextVisitation.info.size : 0.0f, + (i & Z_MAXIMUM_FLAG) ? nextVisitation.info.size : 0.0f); + static_cast(nextVisitation.info.attributeValues.last().getInlineValue< + PolymorphicDataPointer>().data())->guide(nextVisitation); + } +} + +QScriptValue ScriptedMetavoxelGuide::getAttributes(QScriptContext* context, QScriptEngine* engine) { + ScriptedMetavoxelGuide* guide = static_cast(context->callee().data().toVariant().value()); + + const QVector& attributes = guide->_visitation->visitor.getAttributes(); + QScriptValue attributesValue = engine->newArray(attributes.size()); + for (int i = 0; i < attributes.size(); i++) { + attributesValue.setProperty(i, engine->newQObject(attributes.at(i).data(), QScriptEngine::QtOwnership, + QScriptEngine::PreferExistingWrapperObject)); + } + + return attributesValue; +} + +QScriptValue ScriptedMetavoxelGuide::visit(QScriptContext* context, QScriptEngine* engine) { + ScriptedMetavoxelGuide* guide = static_cast(context->callee().data().toVariant().value()); + + // start with the basics, including inherited attribute values + QScriptValue infoValue = context->argument(0); + QScriptValue minimum = infoValue.property(guide->_minimumHandle); + MetavoxelInfo info = { + glm::vec3(minimum.property(0).toNumber(), minimum.property(1).toNumber(), minimum.property(2).toNumber()), + infoValue.property(guide->_sizeHandle).toNumber(), guide->_visitation->info.attributeValues, + infoValue.property(guide->_isLeafHandle).toBool() }; + + // extract and convert the values provided by the script + QScriptValue attributeValues = infoValue.property(guide->_attributeValuesHandle); + const QVector& attributes = guide->_visitation->visitor.getAttributes(); + for (int i = 0; i < attributes.size(); i++) { + QScriptValue attributeValue = attributeValues.property(i); + if (attributeValue.isValid()) { + info.attributeValues[i] = AttributeValue(attributes.at(i), + attributes.at(i)->createFromScript(attributeValue, engine)); + } + } + + QScriptValue result = guide->_visitation->visitor.visit(info); + + // destroy any created values + for (int i = 0; i < attributes.size(); i++) { + if (attributeValues.property(i).isValid()) { + info.attributeValues[i].getAttribute()->destroy(info.attributeValues[i].getValue()); + } + } + + return result; +} + +ScriptedMetavoxelGuide::ScriptedMetavoxelGuide(const QScriptValue& guideFunction) : + _guideFunction(guideFunction), + _minimumHandle(guideFunction.engine()->toStringHandle("minimum")), + _sizeHandle(guideFunction.engine()->toStringHandle("size")), + _attributeValuesHandle(guideFunction.engine()->toStringHandle("attributeValues")), + _isLeafHandle(guideFunction.engine()->toStringHandle("isLeaf")), + _getAttributesFunction(guideFunction.engine()->newFunction(getAttributes, 0)), + _visitFunction(guideFunction.engine()->newFunction(visit, 1)), + _info(guideFunction.engine()->newObject()), + _minimum(guideFunction.engine()->newArray(3)) { + + _arguments.append(guideFunction.engine()->newObject()); + QScriptValue visitor = guideFunction.engine()->newObject(); + visitor.setProperty("getAttributes", _getAttributesFunction); + visitor.setProperty("visit", _visitFunction); + _arguments[0].setProperty("visitor", visitor); + _arguments[0].setProperty("info", _info); + _info.setProperty(_minimumHandle, _minimum); +} + +PolymorphicData* ScriptedMetavoxelGuide::clone() const { + return new ScriptedMetavoxelGuide(_guideFunction); +} + +void ScriptedMetavoxelGuide::guide(MetavoxelVisitation& visitation) { + QScriptValue data = _guideFunction.engine()->newVariant(QVariant::fromValue(this)); + _getAttributesFunction.setData(data); + _visitFunction.setData(data); + _minimum.setProperty(0, visitation.info.minimum.x); + _minimum.setProperty(1, visitation.info.minimum.y); + _minimum.setProperty(2, visitation.info.minimum.z); + _info.setProperty(_sizeHandle, visitation.info.size); + _info.setProperty(_isLeafHandle, visitation.info.isLeaf); + _visitation = &visitation; + _guideFunction.call(QScriptValue(), _arguments); + if (_guideFunction.engine()->hasUncaughtException()) { + qDebug() << "Script error: " << _guideFunction.engine()->uncaughtException().toString() << "\n"; + } +} + +bool MetavoxelVisitation::allNodesLeaves() const { + foreach (MetavoxelNode* node, nodes) { + if (node != NULL && !node->isLeaf()) { + return false; + } + } + return true; +} diff --git a/libraries/metavoxels/src/MetavoxelData.h b/libraries/metavoxels/src/MetavoxelData.h new file mode 100644 index 0000000000..fc7045cff4 --- /dev/null +++ b/libraries/metavoxels/src/MetavoxelData.h @@ -0,0 +1,187 @@ +// +// MetavoxelData.h +// metavoxels +// +// Created by Andrzej Kapolka on 12/6/13. +// Copyright (c) 2013 High Fidelity, Inc. All rights reserved. +// + +#ifndef __interface__MetavoxelData__ +#define __interface__MetavoxelData__ + +#include +#include +#include +#include +#include + +#include + +#include "AttributeRegistry.h" + +class QScriptContext; + +class MetavoxelNode; +class MetavoxelPath; +class MetavoxelVisitation; +class MetavoxelVisitor; + +/// The base metavoxel representation shared between server and client. +class MetavoxelData { +public: + + ~MetavoxelData(); + + /// Applies the specified visitor to the contained voxels. + void guide(MetavoxelVisitor& visitor); + + /// Sets the attribute value corresponding to the specified path. + void setAttributeValue(const MetavoxelPath& path, const AttributeValue& attributeValue); + + /// Retrieves the attribute value corresponding to the specified path. + AttributeValue getAttributeValue(const MetavoxelPath& path, const AttributePointer& attribute) const; + +private: + + QHash _roots; +}; + +/// A single node within a metavoxel layer. +class MetavoxelNode { +public: + + static const int CHILD_COUNT = 8; + + MetavoxelNode(const AttributeValue& attributeValue); + + /// Descends the voxel tree in order to set the value of a node. + /// \param path the path to follow + /// \param index the position in the path + /// \return whether or not the node is entirely equal to the value + bool setAttributeValue(const MetavoxelPath& path, int index, const AttributeValue& attributeValue); + + void setAttributeValue(const AttributeValue& attributeValue); + + AttributeValue getAttributeValue(const AttributePointer& attribute) const; + + MetavoxelNode* getChild(int index) const { return _children[index]; } + void setChild(int index, MetavoxelNode* child) { _children[index] = child; } + + bool isLeaf() const; + + void destroy(const AttributePointer& attribute); + +private: + Q_DISABLE_COPY(MetavoxelNode) + + void clearChildren(const AttributePointer& attribute); + + void* _attributeValue; + MetavoxelNode* _children[CHILD_COUNT]; +}; + +/// A path down an octree. +class MetavoxelPath { +public: + + int getSize() const { return _array.size() / BITS_PER_ELEMENT; } + bool isEmpty() const { return _array.isEmpty(); } + + int operator[](int index) const; + + MetavoxelPath& operator+=(int element); + +private: + + static const int BITS_PER_ELEMENT = 3; + + QBitArray _array; +}; + +/// Contains information about a metavoxel (explicit or procedural). +class MetavoxelInfo { +public: + + glm::vec3 minimum; ///< the minimum extent of the area covered by the voxel + float size; ///< the size of the voxel in all dimensions + QVector attributeValues; + bool isLeaf; +}; + +/// Interface for visitors to metavoxels. +class MetavoxelVisitor { +public: + + MetavoxelVisitor(const QVector& attributes) : _attributes(attributes) { } + + /// Returns a reference to the list of attributes desired. + const QVector& getAttributes() const { return _attributes; } + + /// Visits a metavoxel. + /// \param info the metavoxel ata + /// \param if true, continue descending; if false, stop + virtual bool visit(const MetavoxelInfo& info) = 0; + +protected: + + QVector _attributes; +}; + +/// Interface for objects that guide metavoxel visitors. +class MetavoxelGuide : public PolymorphicData { +public: + + /// Guides the specified visitor to the contained voxels. + virtual void guide(MetavoxelVisitation& visitation) = 0; +}; + +/// Guides visitors through the explicit content of the system. +class DefaultMetavoxelGuide : public MetavoxelGuide { +public: + + virtual PolymorphicData* clone() const; + + virtual void guide(MetavoxelVisitation& visitation); +}; + +/// Represents a guide implemented in Javascript. +class ScriptedMetavoxelGuide : public MetavoxelGuide { +public: + + ScriptedMetavoxelGuide(const QScriptValue& guideFunction); + + virtual PolymorphicData* clone() const; + + virtual void guide(MetavoxelVisitation& visitation); + +private: + + static QScriptValue getAttributes(QScriptContext* context, QScriptEngine* engine); + static QScriptValue visit(QScriptContext* context, QScriptEngine* engine); + + QScriptValue _guideFunction; + QScriptString _minimumHandle; + QScriptString _sizeHandle; + QScriptString _attributeValuesHandle; + QScriptString _isLeafHandle; + QScriptValueList _arguments; + QScriptValue _getAttributesFunction; + QScriptValue _visitFunction; + QScriptValue _info; + QScriptValue _minimum; + + MetavoxelVisitation* _visitation; +}; + +/// Contains the state associated with a visit to a metavoxel system. +class MetavoxelVisitation { +public: + + MetavoxelVisitor& visitor; + QVector nodes; + MetavoxelInfo info; + + bool allNodesLeaves() const; +}; + +#endif /* defined(__interface__MetavoxelData__) */