diff --git a/interface/interface_en.ts b/interface/interface_en.ts index a1abb57b52..3f859c2cd1 100644 --- a/interface/interface_en.ts +++ b/interface/interface_en.ts @@ -4,22 +4,22 @@ Application - + Export Voxels - + Sparse Voxel Octree Files (*.svo) - + Open Script - + JavaScript Files (*.js) @@ -113,18 +113,18 @@ Menu - + Open .ini config file - - + + Text files (*.ini) - + Save .ini config file diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index a237e92e2b..bdbe0194e6 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -372,6 +372,9 @@ Application::~Application() { _nodeThread->quit(); _nodeThread->wait(); + // stop the audio process + QMetaObject::invokeMethod(&_audio, "stop"); + // ask the audio thread to quit and wait until it is done _audio.thread()->quit(); _audio.thread()->wait(); @@ -2531,14 +2534,22 @@ void Application::displayOverlay() { const float CLIPPING_INDICATOR_TIME = 1.0f; const float AUDIO_METER_AVERAGING = 0.5; const float LOG2 = log(2.f); - const float MAX_LOG2_SAMPLE = 15.f; + const float METER_LOUDNESS_SCALE = 2.8f / 5.f; + const float LOG2_LOUDNESS_FLOOR = 11.f; float audioLevel = 0.f; float loudness = _audio.getLastInputLoudness() + 1.f; + _trailingAudioLoudness = AUDIO_METER_AVERAGING * _trailingAudioLoudness + (1.f - AUDIO_METER_AVERAGING) * loudness; - float log2loudness = log(_trailingAudioLoudness) / LOG2; - audioLevel = log2loudness / MAX_LOG2_SAMPLE * AUDIO_METER_SCALE_WIDTH; + if (log2loudness <= LOG2_LOUDNESS_FLOOR) { + audioLevel = (log2loudness / LOG2_LOUDNESS_FLOOR) * METER_LOUDNESS_SCALE * AUDIO_METER_SCALE_WIDTH; + } else { + audioLevel = (log2loudness - (LOG2_LOUDNESS_FLOOR - 1.f)) * METER_LOUDNESS_SCALE * AUDIO_METER_SCALE_WIDTH; + } + if (audioLevel > AUDIO_METER_SCALE_WIDTH) { + audioLevel = AUDIO_METER_SCALE_WIDTH; + } bool isClipping = ((_audio.getTimeSinceLastClip() > 0.f) && (_audio.getTimeSinceLastClip() < CLIPPING_INDICATOR_TIME)); diff --git a/interface/src/Audio.cpp b/interface/src/Audio.cpp index 9f993e653d..63c683dbb0 100644 --- a/interface/src/Audio.cpp +++ b/interface/src/Audio.cpp @@ -96,6 +96,7 @@ void Audio::reset() { QAudioDeviceInfo getNamedAudioDeviceForMode(QAudio::Mode mode, const QString& deviceName) { QAudioDeviceInfo result; foreach(QAudioDeviceInfo audioDevice, QAudioDeviceInfo::availableDevices(mode)) { + qDebug() << audioDevice.deviceName() << " " << deviceName; if (audioDevice.deviceName().trimmed() == deviceName.trimmed()) { result = audioDevice; } @@ -163,6 +164,8 @@ QAudioDeviceInfo defaultAudioDeviceForMode(QAudio::Mode mode) { qDebug() << "output device:" << woc.szPname; deviceName = woc.szPname; } + qDebug() << "DEBUG [" << deviceName << "] [" << getNamedAudioDeviceForMode(mode, deviceName).deviceName() << "]"; + return getNamedAudioDeviceForMode(mode, deviceName); #endif @@ -280,8 +283,6 @@ void linearResampling(int16_t* sourceSamples, int16_t* destinationSamples, } } -const int CALLBACK_ACCELERATOR_RATIO = 2; - void Audio::start() { // set up the desired audio format @@ -303,9 +304,18 @@ void Audio::start() { qDebug() << "The default audio output device is" << outputDeviceInfo.deviceName(); bool outputFormatSupported = switchOutputToAudioDevice(outputDeviceInfo); - if (!inputFormatSupported || !outputFormatSupported) { - qDebug() << "Unable to set up audio I/O because of a problem with input or output formats."; + if (!inputFormatSupported) { + qDebug() << "Unable to set up audio input because of a problem with input format."; } + if (!outputFormatSupported) { + qDebug() << "Unable to set up audio output because of a problem with output format."; + } +} + +void Audio::stop() { + // "switch" to invalid devices in order to shut down the state + switchInputToAudioDevice(QAudioDeviceInfo()); + switchOutputToAudioDevice(QAudioDeviceInfo()); } QString Audio::getDefaultDeviceName(QAudio::Mode mode) { @@ -322,10 +332,12 @@ QVector Audio::getDeviceNames(QAudio::Mode mode) { } bool Audio::switchInputToAudioDevice(const QString& inputDeviceName) { + qDebug() << "DEBUG [" << inputDeviceName << "] [" << getNamedAudioDeviceForMode(QAudio::AudioInput, inputDeviceName).deviceName() << "]"; return switchInputToAudioDevice(getNamedAudioDeviceForMode(QAudio::AudioInput, inputDeviceName)); } bool Audio::switchOutputToAudioDevice(const QString& outputDeviceName) { + qDebug() << "DEBUG [" << outputDeviceName << "] [" << getNamedAudioDeviceForMode(QAudio::AudioOutput, outputDeviceName).deviceName() << "]"; return switchOutputToAudioDevice(getNamedAudioDeviceForMode(QAudio::AudioOutput, outputDeviceName)); } @@ -337,14 +349,13 @@ void Audio::handleAudioInput() { static int16_t* monoAudioSamples = (int16_t*) (monoAudioDataPacket + leadingBytes); - static float inputToNetworkInputRatio = _numInputCallbackBytes * CALLBACK_ACCELERATOR_RATIO - / NETWORK_BUFFER_LENGTH_BYTES_PER_CHANNEL; + float inputToNetworkInputRatio = calculateDeviceToNetworkInputRatio(_numInputCallbackBytes); - static unsigned int inputSamplesRequired = NETWORK_BUFFER_LENGTH_SAMPLES_PER_CHANNEL * inputToNetworkInputRatio; + unsigned int inputSamplesRequired = NETWORK_BUFFER_LENGTH_SAMPLES_PER_CHANNEL * inputToNetworkInputRatio; QByteArray inputByteArray = _inputDevice->readAll(); - if (Menu::getInstance()->isOptionChecked(MenuOption::EchoLocalAudio) && !_muted) { + if (Menu::getInstance()->isOptionChecked(MenuOption::EchoLocalAudio) && !_muted && _audioOutput) { // if this person wants local loopback add that to the locally injected audio if (!_loopbackOutputDevice && _loopbackAudioOutput) { @@ -357,7 +368,7 @@ void Audio::handleAudioInput() { _loopbackOutputDevice->write(inputByteArray); } } else { - static float loopbackOutputToInputRatio = (_outputFormat.sampleRate() / (float) _inputFormat.sampleRate()) + float loopbackOutputToInputRatio = (_outputFormat.sampleRate() / (float) _inputFormat.sampleRate()) * (_outputFormat.channelCount() / _inputFormat.channelCount()); QByteArray loopBackByteArray(inputByteArray.size() * loopbackOutputToInputRatio, 0); @@ -382,9 +393,6 @@ void Audio::handleAudioInput() { // zero out the monoAudioSamples array and the locally injected audio memset(monoAudioSamples, 0, NETWORK_BUFFER_LENGTH_BYTES_PER_CHANNEL); - // zero out the locally injected audio in preparation for audio procedural sounds - memset(_localProceduralSamples, 0, NETWORK_BUFFER_LENGTH_BYTES_PER_CHANNEL); - if (!_muted) { // we aren't muted, downsample the input audio linearResampling((int16_t*) inputAudioSamples, @@ -436,12 +444,12 @@ void Audio::handleAudioInput() { measuredDcOffset += monoAudioSamples[i]; monoAudioSamples[i] -= (int16_t) _dcOffset; thisSample = fabsf(monoAudioSamples[i]); - if (thisSample > (32767.f * CLIPPING_THRESHOLD)) { + if (thisSample >= (32767.f * CLIPPING_THRESHOLD)) { _timeSinceLastClip = 0.0f; } loudness += thisSample; // Noise Reduction: Count peaks above the average loudness - if (thisSample > (_noiseGateMeasuredFloor * NOISE_GATE_HEIGHT)) { + if (_noiseGateEnabled && (thisSample > (_noiseGateMeasuredFloor * NOISE_GATE_HEIGHT))) { samplesOverNoiseGate++; } } @@ -454,32 +462,41 @@ void Audio::handleAudioInput() { _dcOffset = DC_OFFSET_AVERAGING * _dcOffset + (1.f - DC_OFFSET_AVERAGING) * measuredDcOffset; } - // - _lastInputLoudness = fabs(loudness / NETWORK_BUFFER_LENGTH_SAMPLES_PER_CHANNEL); - - float averageOfAllSampleFrames = 0.f; - _noiseSampleFrames[_noiseGateSampleCounter++] = _lastInputLoudness; - if (_noiseGateSampleCounter == NUMBER_OF_NOISE_SAMPLE_FRAMES) { - float smallestSample = FLT_MAX; - for (int i = 0; i <= NUMBER_OF_NOISE_SAMPLE_FRAMES - NOISE_GATE_FRAMES_TO_AVERAGE; i+= NOISE_GATE_FRAMES_TO_AVERAGE) { - float thisAverage = 0.0f; - for (int j = i; j < i + NOISE_GATE_FRAMES_TO_AVERAGE; j++) { - thisAverage += _noiseSampleFrames[j]; - averageOfAllSampleFrames += _noiseSampleFrames[j]; - } - thisAverage /= NOISE_GATE_FRAMES_TO_AVERAGE; - - if (thisAverage < smallestSample) { - smallestSample = thisAverage; - } + // Add tone injection if enabled + const float TONE_FREQ = 220.f / SAMPLE_RATE * TWO_PI; + const float QUARTER_VOLUME = 8192.f; + if (_toneInjectionEnabled) { + loudness = 0.f; + for (int i = 0; i < NETWORK_BUFFER_LENGTH_SAMPLES_PER_CHANNEL; i++) { + monoAudioSamples[i] = QUARTER_VOLUME * sinf(TONE_FREQ * (float)(i + _proceduralEffectSample)); + loudness += fabsf(monoAudioSamples[i]); } - averageOfAllSampleFrames /= NUMBER_OF_NOISE_SAMPLE_FRAMES; - _noiseGateMeasuredFloor = smallestSample; - _noiseGateSampleCounter = 0; - } + _lastInputLoudness = fabs(loudness / NETWORK_BUFFER_LENGTH_SAMPLES_PER_CHANNEL); - if (_noiseGateEnabled) { + // If Noise Gate is enabled, check and turn the gate on and off + if (!_toneInjectionEnabled && _noiseGateEnabled) { + float averageOfAllSampleFrames = 0.f; + _noiseSampleFrames[_noiseGateSampleCounter++] = _lastInputLoudness; + if (_noiseGateSampleCounter == NUMBER_OF_NOISE_SAMPLE_FRAMES) { + float smallestSample = FLT_MAX; + for (int i = 0; i <= NUMBER_OF_NOISE_SAMPLE_FRAMES - NOISE_GATE_FRAMES_TO_AVERAGE; i+= NOISE_GATE_FRAMES_TO_AVERAGE) { + float thisAverage = 0.0f; + for (int j = i; j < i + NOISE_GATE_FRAMES_TO_AVERAGE; j++) { + thisAverage += _noiseSampleFrames[j]; + averageOfAllSampleFrames += _noiseSampleFrames[j]; + } + thisAverage /= NOISE_GATE_FRAMES_TO_AVERAGE; + + if (thisAverage < smallestSample) { + smallestSample = thisAverage; + } + } + averageOfAllSampleFrames /= NUMBER_OF_NOISE_SAMPLE_FRAMES; + _noiseGateMeasuredFloor = smallestSample; + _noiseGateSampleCounter = 0; + + } if (samplesOverNoiseGate > NOISE_GATE_WIDTH) { _noiseGateOpen = true; _noiseGateFramesToClose = NOISE_GATE_CLOSE_FRAME_DELAY; @@ -493,16 +510,6 @@ void Audio::handleAudioInput() { _lastInputLoudness = 0; } } - // - // Add tone injection if enabled - // - const float TONE_FREQ = 220.f / SAMPLE_RATE * TWO_PI; - const float QUARTER_VOLUME = 8192.f; - if (_toneInjectionEnabled) { - for (int i = 0; i < NETWORK_BUFFER_LENGTH_SAMPLES_PER_CHANNEL; i++) { - monoAudioSamples[i] = QUARTER_VOLUME * sinf(TONE_FREQ * (float)(i + _proceduralEffectSample)); - } - } // add input data just written to the scope QMetaObject::invokeMethod(_scope, "addSamples", Qt::QueuedConnection, @@ -514,28 +521,8 @@ void Audio::handleAudioInput() { _lastInputLoudness = 0; } - // add procedural effects to the appropriate input samples - addProceduralSounds(monoAudioSamples, - NETWORK_BUFFER_LENGTH_SAMPLES_PER_CHANNEL); - - if (!_proceduralOutputDevice && _proceduralAudioOutput) { - _proceduralOutputDevice = _proceduralAudioOutput->start(); - } - - // send whatever procedural sounds we want to locally loop back to the _proceduralOutputDevice - QByteArray proceduralOutput; - proceduralOutput.resize(NETWORK_BUFFER_LENGTH_SAMPLES_PER_CHANNEL * _outputFormat.sampleRate() * - _outputFormat.channelCount() * sizeof(int16_t) / (_desiredInputFormat.sampleRate() * - _desiredInputFormat.channelCount())); - - linearResampling(_localProceduralSamples, - reinterpret_cast(proceduralOutput.data()), - NETWORK_BUFFER_LENGTH_SAMPLES_PER_CHANNEL, - proceduralOutput.size() / sizeof(int16_t), - _desiredInputFormat, _outputFormat); - - if (_proceduralOutputDevice) { - _proceduralOutputDevice->write(proceduralOutput); + if (_proceduralAudioOutput) { + processProceduralAudio(monoAudioSamples, NETWORK_BUFFER_LENGTH_SAMPLES_PER_CHANNEL); } NodeList* nodeList = NodeList::getInstance(); @@ -615,9 +602,37 @@ void Audio::addReceivedAudioToBuffer(const QByteArray& audioByteArray) { } } + if (_audioOutput) { + // Audio output must exist and be correctly set up if we're going to process received audio + processReceivedAudio(audioByteArray); + } + + Application::getInstance()->getBandwidthMeter()->inputStream(BandwidthMeter::AUDIO).updateValue(audioByteArray.size()); + + _lastReceiveTime = currentReceiveTime; +} + +bool Audio::mousePressEvent(int x, int y) { + if (_iconBounds.contains(x, y)) { + toggleMute(); + return true; + } + return false; +} + +void Audio::toggleMute() { + _muted = !_muted; + muteToggled(); +} + +void Audio::toggleAudioNoiseReduction() { + _noiseGateEnabled = !_noiseGateEnabled; +} + +void Audio::processReceivedAudio(const QByteArray& audioByteArray) { _ringBuffer.parseData(audioByteArray); - static float networkOutputToOutputRatio = (_desiredOutputFormat.sampleRate() / (float) _outputFormat.sampleRate()) + float networkOutputToOutputRatio = (_desiredOutputFormat.sampleRate() / (float) _outputFormat.sampleRate()) * (_desiredOutputFormat.channelCount() / (float) _outputFormat.channelCount()); if (!_ringBuffer.isStarved() && _audioOutput && _audioOutput->bytesFree() == _audioOutput->bufferSize()) { @@ -672,29 +687,36 @@ void Audio::addReceivedAudioToBuffer(const QByteArray& audioByteArray) { } delete[] ringBufferSamples; } - } - - Application::getInstance()->getBandwidthMeter()->inputStream(BandwidthMeter::AUDIO).updateValue(audioByteArray.size()); - - _lastReceiveTime = currentReceiveTime; } -bool Audio::mousePressEvent(int x, int y) { - if (_iconBounds.contains(x, y)) { - toggleMute(); - return true; +void Audio::processProceduralAudio(int16_t* monoInput, int numSamples) { + + // zero out the locally injected audio in preparation for audio procedural sounds + // This is correlated to numSamples, so it really needs to be numSamples * sizeof(sample) + memset(_localProceduralSamples, 0, NETWORK_BUFFER_LENGTH_BYTES_PER_CHANNEL); + // add procedural effects to the appropriate input samples + addProceduralSounds(monoInput, NETWORK_BUFFER_LENGTH_SAMPLES_PER_CHANNEL); + + if (!_proceduralOutputDevice) { + _proceduralOutputDevice = _proceduralAudioOutput->start(); + } + + // send whatever procedural sounds we want to locally loop back to the _proceduralOutputDevice + QByteArray proceduralOutput; + proceduralOutput.resize(NETWORK_BUFFER_LENGTH_SAMPLES_PER_CHANNEL * _outputFormat.sampleRate() * + _outputFormat.channelCount() * sizeof(int16_t) / (_desiredInputFormat.sampleRate() * + _desiredInputFormat.channelCount())); + + linearResampling(_localProceduralSamples, + reinterpret_cast(proceduralOutput.data()), + NETWORK_BUFFER_LENGTH_SAMPLES_PER_CHANNEL, + proceduralOutput.size() / sizeof(int16_t), + _desiredInputFormat, _outputFormat); + + if (_proceduralOutputDevice) { + _proceduralOutputDevice->write(proceduralOutput); } - return false; -} - -void Audio::toggleMute() { - _muted = !_muted; - muteToggled(); -} - -void Audio::toggleAudioNoiseReduction() { - _noiseGateEnabled = !_noiseGateEnabled; } void Audio::toggleToneInjection() { @@ -846,7 +868,7 @@ bool Audio::switchInputToAudioDevice(const QAudioDeviceInfo& inputDeviceInfo) { // cleanup any previously initialized device if (_audioInput) { _audioInput->stop(); - disconnect(_inputDevice, 0, 0, 0); + disconnect(_inputDevice); _inputDevice = NULL; delete _audioInput; @@ -864,13 +886,12 @@ bool Audio::switchInputToAudioDevice(const QAudioDeviceInfo& inputDeviceInfo) { qDebug() << "The format to be used for audio input is" << _inputFormat; _audioInput = new QAudioInput(inputDeviceInfo, _inputFormat, this); - _numInputCallbackBytes = NETWORK_BUFFER_LENGTH_BYTES_PER_CHANNEL * _inputFormat.channelCount() - * (_inputFormat.sampleRate() / SAMPLE_RATE) - / CALLBACK_ACCELERATOR_RATIO; + _numInputCallbackBytes = calculateNumberOfInputCallbackBytes(_inputFormat); _audioInput->setBufferSize(_numInputCallbackBytes); // how do we want to handle input working, but output not working? - _inputRingBuffer.resizeForFrameSize(_numInputCallbackBytes * CALLBACK_ACCELERATOR_RATIO / sizeof(int16_t)); + int numFrameSamples = calculateNumberOfFrameSamples(_numInputCallbackBytes); + _inputRingBuffer.resizeForFrameSize(numFrameSamples); _inputDevice = _audioInput->start(); connect(_inputDevice, SIGNAL(readyRead()), this, SLOT(handleAudioInput())); @@ -927,3 +948,41 @@ bool Audio::switchOutputToAudioDevice(const QAudioDeviceInfo& outputDeviceInfo) } return supportedFormat; } + +// The following constant is operating system dependent due to differences in +// the way input audio is handled. The audio input buffer size is inversely +// proportional to the accelerator ratio. + +#ifdef Q_OS_WIN +const float Audio::CALLBACK_ACCELERATOR_RATIO = 0.4f; +#endif + +#ifdef Q_OS_MAC +const float Audio::CALLBACK_ACCELERATOR_RATIO = 2.0f; +#endif + +#ifdef Q_OS_LINUX +const float Audio::CALLBACK_ACCELERATOR_RATIO = 2.0f; +#endif + +int Audio::calculateNumberOfInputCallbackBytes(const QAudioFormat& format) { + int numInputCallbackBytes = (int)(((NETWORK_BUFFER_LENGTH_BYTES_PER_CHANNEL + * format.channelCount() + * (format.sampleRate() / SAMPLE_RATE)) + / CALLBACK_ACCELERATOR_RATIO) + 0.5f); + + return numInputCallbackBytes; +} + +float Audio::calculateDeviceToNetworkInputRatio(int numBytes) { + float inputToNetworkInputRatio = (int)((_numInputCallbackBytes + * CALLBACK_ACCELERATOR_RATIO + / NETWORK_BUFFER_LENGTH_BYTES_PER_CHANNEL) + 0.5f); + + return inputToNetworkInputRatio; +} + +int Audio::calculateNumberOfFrameSamples(int numBytes) { + int frameSamples = (int)(numBytes * CALLBACK_ACCELERATOR_RATIO + 0.5f) / sizeof(int16_t); + return frameSamples; +} diff --git a/interface/src/Audio.h b/interface/src/Audio.h index 170572a4d7..88488922f3 100644 --- a/interface/src/Audio.h +++ b/interface/src/Audio.h @@ -76,6 +76,7 @@ public: public slots: void start(); + void stop(); void addReceivedAudioToBuffer(const QByteArray& audioByteArray); void handleAudioInput(); void reset(); @@ -165,11 +166,26 @@ private: // Audio callback in class context. inline void performIO(int16_t* inputLeft, int16_t* outputLeft, int16_t* outputRight); + // Process procedural audio by + // 1. Echo to the local procedural output device + // 2. Mix with the audio input + void processProceduralAudio(int16_t* monoInput, int numSamples); + // Add sounds that we want the user to not hear themselves, by adding on top of mic input signal void addProceduralSounds(int16_t* monoInput, int numSamples); + // Process received audio + void processReceivedAudio(const QByteArray& audioByteArray); + bool switchInputToAudioDevice(const QAudioDeviceInfo& inputDeviceInfo); bool switchOutputToAudioDevice(const QAudioDeviceInfo& outputDeviceInfo); + + // Callback acceleration dependent calculations + static const float CALLBACK_ACCELERATOR_RATIO; + int calculateNumberOfInputCallbackBytes(const QAudioFormat& format); + int calculateNumberOfFrameSamples(int numBytes); + float calculateDeviceToNetworkInputRatio(int numBytes); + }; diff --git a/interface/src/MetavoxelSystem.cpp b/interface/src/MetavoxelSystem.cpp index 23e50176eb..042c9329f2 100644 --- a/interface/src/MetavoxelSystem.cpp +++ b/interface/src/MetavoxelSystem.cpp @@ -167,12 +167,14 @@ void MetavoxelSystem::maybeAttachClient(const SharedNodePointer& node) { MetavoxelSystem::SimulateVisitor::SimulateVisitor(QVector& points) : SpannerVisitor(QVector() << AttributeRegistry::getInstance()->getSpannersAttribute(), - QVector() << AttributeRegistry::getInstance()->getColorAttribute() << - AttributeRegistry::getInstance()->getNormalAttribute()), + QVector(), QVector() << AttributeRegistry::getInstance()->getColorAttribute() << + AttributeRegistry::getInstance()->getNormalAttribute() << + AttributeRegistry::getInstance()->getSpannerColorAttribute() << + AttributeRegistry::getInstance()->getSpannerNormalAttribute()), _points(points) { } -bool MetavoxelSystem::SimulateVisitor::visit(Spanner* spanner) { +bool MetavoxelSystem::SimulateVisitor::visit(Spanner* spanner, const glm::vec3& clipMinimum, float clipSize) { spanner->getRenderer()->simulate(_deltaTime); return true; } @@ -186,21 +188,47 @@ int MetavoxelSystem::SimulateVisitor::visit(MetavoxelInfo& info) { QRgb color = info.inputValues.at(0).getInlineValue(); QRgb normal = info.inputValues.at(1).getInlineValue(); quint8 alpha = qAlpha(color); - if (alpha > 0) { - Point point = { glm::vec4(info.minimum + glm::vec3(info.size, info.size, info.size) * 0.5f, info.size), - { quint8(qRed(color)), quint8(qGreen(color)), quint8(qBlue(color)), alpha }, - { quint8(qRed(normal)), quint8(qGreen(normal)), quint8(qBlue(normal)) } }; - _points.append(point); + if (!info.isLODLeaf) { + if (alpha > 0) { + Point point = { glm::vec4(info.minimum + glm::vec3(info.size, info.size, info.size) * 0.5f, info.size), + { quint8(qRed(color)), quint8(qGreen(color)), quint8(qBlue(color)), alpha }, + { quint8(qRed(normal)), quint8(qGreen(normal)), quint8(qBlue(normal)) } }; + _points.append(point); + } + } else { + QRgb spannerColor = info.inputValues.at(2).getInlineValue(); + QRgb spannerNormal = info.inputValues.at(3).getInlineValue(); + quint8 spannerAlpha = qAlpha(spannerColor); + if (spannerAlpha > 0) { + if (alpha > 0) { + Point point = { glm::vec4(info.minimum + glm::vec3(info.size, info.size, info.size) * 0.5f, info.size), + { quint8(qRed(spannerColor)), quint8(qGreen(spannerColor)), quint8(qBlue(spannerColor)), spannerAlpha }, + { quint8(qRed(spannerNormal)), quint8(qGreen(spannerNormal)), quint8(qBlue(spannerNormal)) } }; + _points.append(point); + + } else { + Point point = { glm::vec4(info.minimum + glm::vec3(info.size, info.size, info.size) * 0.5f, info.size), + { quint8(qRed(spannerColor)), quint8(qGreen(spannerColor)), quint8(qBlue(spannerColor)), spannerAlpha }, + { quint8(qRed(spannerNormal)), quint8(qGreen(spannerNormal)), quint8(qBlue(spannerNormal)) } }; + _points.append(point); + } + } else if (alpha > 0) { + Point point = { glm::vec4(info.minimum + glm::vec3(info.size, info.size, info.size) * 0.5f, info.size), + { quint8(qRed(color)), quint8(qGreen(color)), quint8(qBlue(color)), alpha }, + { quint8(qRed(normal)), quint8(qGreen(normal)), quint8(qBlue(normal)) } }; + _points.append(point); + } } return STOP_RECURSION; } MetavoxelSystem::RenderVisitor::RenderVisitor() : - SpannerVisitor(QVector() << AttributeRegistry::getInstance()->getSpannersAttribute()) { + SpannerVisitor(QVector() << AttributeRegistry::getInstance()->getSpannersAttribute(), + QVector() << AttributeRegistry::getInstance()->getSpannerMaskAttribute()) { } -bool MetavoxelSystem::RenderVisitor::visit(Spanner* spanner) { - spanner->getRenderer()->render(1.0f); +bool MetavoxelSystem::RenderVisitor::visit(Spanner* spanner, const glm::vec3& clipMinimum, float clipSize) { + spanner->getRenderer()->render(1.0f, clipMinimum, clipSize); return true; } @@ -306,10 +334,55 @@ void MetavoxelClient::handleMessage(const QVariant& message, Bitstream& in) { } } +static void enableClipPlane(GLenum plane, float x, float y, float z, float w) { + GLdouble coefficients[] = { x, y, z, w }; + glClipPlane(plane, coefficients); + glEnable(plane); +} + +void ClippedRenderer::render(float alpha, const glm::vec3& clipMinimum, float clipSize) { + if (clipSize == 0.0f) { + renderUnclipped(alpha); + return; + } + enableClipPlane(GL_CLIP_PLANE0, -1.0f, 0.0f, 0.0f, clipMinimum.x + clipSize); + enableClipPlane(GL_CLIP_PLANE1, 1.0f, 0.0f, 0.0f, -clipMinimum.x); + enableClipPlane(GL_CLIP_PLANE2, 0.0f, -1.0f, 0.0f, clipMinimum.y + clipSize); + enableClipPlane(GL_CLIP_PLANE3, 0.0f, 1.0f, 0.0f, -clipMinimum.y); + enableClipPlane(GL_CLIP_PLANE4, 0.0f, 0.0f, -1.0f, clipMinimum.z + clipSize); + enableClipPlane(GL_CLIP_PLANE5, 0.0f, 0.0f, 1.0f, -clipMinimum.z); + + renderUnclipped(alpha); + + glDisable(GL_CLIP_PLANE0); + glDisable(GL_CLIP_PLANE1); + glDisable(GL_CLIP_PLANE2); + glDisable(GL_CLIP_PLANE3); + glDisable(GL_CLIP_PLANE4); + glDisable(GL_CLIP_PLANE5); +} + SphereRenderer::SphereRenderer() { } -void SphereRenderer::render(float alpha) { +void SphereRenderer::render(float alpha, const glm::vec3& clipMinimum, float clipSize) { + if (clipSize == 0.0f) { + renderUnclipped(alpha); + return; + } + // slight performance optimization: don't render if clip bounds are entirely within sphere + Sphere* sphere = static_cast(parent()); + Box clipBox(clipMinimum, clipMinimum + glm::vec3(clipSize, clipSize, clipSize)); + for (int i = 0; i < Box::VERTEX_COUNT; i++) { + const float CLIP_PROPORTION = 0.95f; + if (glm::distance(sphere->getTranslation(), clipBox.getVertex(i)) >= sphere->getScale() * CLIP_PROPORTION) { + ClippedRenderer::render(alpha, clipMinimum, clipSize); + return; + } + } +} + +void SphereRenderer::renderUnclipped(float alpha) { Sphere* sphere = static_cast(parent()); const QColor& color = sphere->getColor(); glColor4f(color.redF(), color.greenF(), color.blueF(), color.alphaF() * alpha); @@ -357,11 +430,12 @@ void StaticModelRenderer::simulate(float deltaTime) { _model->simulate(deltaTime); } -void StaticModelRenderer::render(float alpha) { +void StaticModelRenderer::renderUnclipped(float alpha) { _model->render(alpha); } -bool StaticModelRenderer::findRayIntersection(const glm::vec3& origin, const glm::vec3& direction, float& distance) const { +bool StaticModelRenderer::findRayIntersection(const glm::vec3& origin, const glm::vec3& direction, + const glm::vec3& clipMinimum, float clipSize, float& distance) const { return _model->findRayIntersection(origin, direction, distance); } diff --git a/interface/src/MetavoxelSystem.h b/interface/src/MetavoxelSystem.h index ea3f4ed18f..58f40dad37 100644 --- a/interface/src/MetavoxelSystem.h +++ b/interface/src/MetavoxelSystem.h @@ -61,7 +61,7 @@ private: SimulateVisitor(QVector& points); void setDeltaTime(float deltaTime) { _deltaTime = deltaTime; } void setOrder(const glm::vec3& direction) { _order = encodeOrder(direction); } - virtual bool visit(Spanner* spanner); + virtual bool visit(Spanner* spanner, const glm::vec3& clipMinimum, float clipSize); virtual int visit(MetavoxelInfo& info); private: @@ -73,7 +73,7 @@ private: class RenderVisitor : public SpannerVisitor { public: RenderVisitor(); - virtual bool visit(Spanner* spanner); + virtual bool visit(Spanner* spanner, const glm::vec3& clipMinimum, float clipSize); }; static ProgramObject _program; @@ -141,19 +141,36 @@ private: QList _receiveRecords; }; +/// Base class for spanner renderers; provides clipping. +class ClippedRenderer : public SpannerRenderer { + Q_OBJECT + +public: + + virtual void render(float alpha, const glm::vec3& clipMinimum, float clipSize); + +protected: + + virtual void renderUnclipped(float alpha) = 0; +}; + /// Renders spheres. -class SphereRenderer : public SpannerRenderer { +class SphereRenderer : public ClippedRenderer { Q_OBJECT public: Q_INVOKABLE SphereRenderer(); - virtual void render(float alpha); + virtual void render(float alpha, const glm::vec3& clipMinimum, float clipSize); + +protected: + + virtual void renderUnclipped(float alpha); }; /// Renders static models. -class StaticModelRenderer : public SpannerRenderer { +class StaticModelRenderer : public ClippedRenderer { Q_OBJECT public: @@ -162,8 +179,12 @@ public: virtual void init(Spanner* spanner); virtual void simulate(float deltaTime); - virtual void render(float alpha); - virtual bool findRayIntersection(const glm::vec3& origin, const glm::vec3& direction, float& distance) const; + virtual bool findRayIntersection(const glm::vec3& origin, const glm::vec3& direction, + const glm::vec3& clipMinimum, float clipSize, float& distance) const; + +protected: + + virtual void renderUnclipped(float alpha); private slots: diff --git a/interface/src/avatar/SkeletonModel.cpp b/interface/src/avatar/SkeletonModel.cpp index c44d2ebdea..ca18c62718 100644 --- a/interface/src/avatar/SkeletonModel.cpp +++ b/interface/src/avatar/SkeletonModel.cpp @@ -133,12 +133,17 @@ void SkeletonModel::applyPalmData(int jointIndex, const QVector& fingerJoin return; } const FBXGeometry& geometry = _geometry->getFBXGeometry(); - setJointPosition(jointIndex, palm.getPosition()); float sign = (jointIndex == geometry.rightHandJointIndex) ? 1.0f : -1.0f; + int parentJointIndex = geometry.joints.at(jointIndex).parentIndex; + if (parentJointIndex == -1) { + return; + } + + // rotate forearm to align with palm direction glm::quat palmRotation; - getJointRotation(jointIndex, palmRotation, true); - applyRotationDelta(jointIndex, rotationBetween(palmRotation * geometry.palmDirection, palm.getNormal()), false); - getJointRotation(jointIndex, palmRotation, true); + getJointRotation(parentJointIndex, palmRotation, true); + applyRotationDelta(parentJointIndex, rotationBetween(palmRotation * geometry.palmDirection, palm.getNormal()), false); + getJointRotation(parentJointIndex, palmRotation, true); // sort the finger indices by raw x, get the average direction QVector fingerIndices; @@ -155,33 +160,21 @@ void SkeletonModel::applyPalmData(int jointIndex, const QVector& fingerJoin } qSort(fingerIndices.begin(), fingerIndices.end()); - // rotate palm according to average finger direction + // rotate forearm according to average finger direction float directionLength = glm::length(direction); const unsigned int MIN_ROTATION_FINGERS = 3; if (directionLength > EPSILON && palm.getNumFingers() >= MIN_ROTATION_FINGERS) { - applyRotationDelta(jointIndex, rotationBetween(palmRotation * glm::vec3(-sign, 0.0f, 0.0f), direction), false); - getJointRotation(jointIndex, palmRotation, true); + applyRotationDelta(parentJointIndex, rotationBetween(palmRotation * glm::vec3(-sign, 0.0f, 0.0f), direction), false); + getJointRotation(parentJointIndex, palmRotation, true); } - // no point in continuing if there are no fingers - if (palm.getNumFingers() == 0 || fingerJointIndices.isEmpty()) { - return; - } + // let wrist inherit forearm rotation + _jointStates[jointIndex].rotation = glm::quat(); - // match them up as best we can - float proportion = fingerIndices.size() / (float)fingerJointIndices.size(); - for (int i = 0; i < fingerJointIndices.size(); i++) { - int fingerIndex = fingerIndices.at(roundf(i * proportion)).index; - glm::vec3 fingerVector = palm.getFingers()[fingerIndex].getTipPosition() - - palm.getFingers()[fingerIndex].getRootPosition(); - - int fingerJointIndex = fingerJointIndices.at(i); - int fingertipJointIndex = fingertipJointIndices.at(i); - glm::vec3 jointVector = extractTranslation(geometry.joints.at(fingertipJointIndex).bindTransform) - - extractTranslation(geometry.joints.at(fingerJointIndex).bindTransform); - - setJointRotation(fingerJointIndex, rotationBetween(palmRotation * jointVector, fingerVector) * palmRotation, true); - } + // set elbow position from wrist position + glm::vec3 forearmVector = palmRotation * glm::vec3(sign, 0.0f, 0.0f); + setJointPosition(parentJointIndex, palm.getPosition() + forearmVector * + geometry.joints.at(jointIndex).distanceToParent * extractUniformScale(_scale)); } void SkeletonModel::updateJointState(int index) { diff --git a/interface/src/ui/MetavoxelEditor.cpp b/interface/src/ui/MetavoxelEditor.cpp index eed8a7b6f3..19f5902336 100644 --- a/interface/src/ui/MetavoxelEditor.cpp +++ b/interface/src/ui/MetavoxelEditor.cpp @@ -565,7 +565,7 @@ void PlaceSpannerTool::render() { } Spanner* spanner = static_cast(_editor->getValue().value().data()); const float SPANNER_ALPHA = 0.25f; - spanner->getRenderer()->render(SPANNER_ALPHA); + spanner->getRenderer()->render(SPANNER_ALPHA, glm::vec3(), 0.0f); } bool PlaceSpannerTool::appliesTo(const AttributePointer& attribute) const { @@ -655,6 +655,5 @@ bool SetSpannerTool::appliesTo(const AttributePointer& attribute) const { } QVariant SetSpannerTool::createEdit(const AttributePointer& attribute, const SharedObjectPointer& spanner) { - static_cast(spanner.data())->setGranularity(_editor->getGridSpacing()); return QVariant::fromValue(SetSpannerEdit(spanner)); } diff --git a/interface/src/ui/overlays/ImageOverlay.cpp b/interface/src/ui/overlays/ImageOverlay.cpp index 178383749b..ac5c8ecefa 100644 --- a/interface/src/ui/overlays/ImageOverlay.cpp +++ b/interface/src/ui/overlays/ImageOverlay.cpp @@ -16,6 +16,7 @@ #include "ImageOverlay.h" ImageOverlay::ImageOverlay() : + _manager(0), _textureID(0), _renderImage(false), _textureBound(false), @@ -33,9 +34,9 @@ ImageOverlay::~ImageOverlay() { // TODO: handle setting image multiple times, how do we manage releasing the bound texture? void ImageOverlay::setImageURL(const QUrl& url) { // TODO: are we creating too many QNetworkAccessManager() when multiple calls to setImageURL are made? - QNetworkAccessManager* manager = new QNetworkAccessManager(this); - connect(manager, SIGNAL(finished(QNetworkReply*)), this, SLOT(replyFinished(QNetworkReply*))); - manager->get(QNetworkRequest(url)); + _manager = new QNetworkAccessManager(); + connect(_manager, SIGNAL(finished(QNetworkReply*)), this, SLOT(replyFinished(QNetworkReply*))); + _manager->get(QNetworkRequest(url)); } void ImageOverlay::replyFinished(QNetworkReply* reply) { @@ -44,7 +45,7 @@ void ImageOverlay::replyFinished(QNetworkReply* reply) { QByteArray rawData = reply->readAll(); _textureImage.loadFromData(rawData); _renderImage = true; - + _manager->deleteLater(); } void ImageOverlay::render() { diff --git a/interface/src/ui/overlays/ImageOverlay.h b/interface/src/ui/overlays/ImageOverlay.h index 77cac3b3c6..d6165e388d 100644 --- a/interface/src/ui/overlays/ImageOverlay.h +++ b/interface/src/ui/overlays/ImageOverlay.h @@ -49,6 +49,8 @@ private: QUrl _imageURL; QImage _textureImage; + QNetworkAccessManager* _manager; + GLuint _textureID; QRect _fromImage; // where from in the image to sample bool _renderImage; // is there an image associated with this overlay, or is it just a colored rectangle diff --git a/interface/src/voxels/VoxelSystem.cpp b/interface/src/voxels/VoxelSystem.cpp index 5c68485436..bb907c8a9a 100644 --- a/interface/src/voxels/VoxelSystem.cpp +++ b/interface/src/voxels/VoxelSystem.cpp @@ -1508,7 +1508,9 @@ void VoxelSystem::killLocalVoxels() { PerformanceWarning warn(Menu::getInstance()->isOptionChecked(MenuOption::PipelineWarnings), "VoxelSystem::killLocalVoxels()"); _tree->lockForWrite(); + VoxelSystem* voxelSystem = _tree->getRoot()->getVoxelSystem(); _tree->eraseAllOctreeElements(); + _tree->getRoot()->setVoxelSystem(voxelSystem); _tree->unlock(); clearFreeBufferIndexes(); if (_usePrimitiveRenderer) { diff --git a/libraries/metavoxels/src/AttributeRegistry.cpp b/libraries/metavoxels/src/AttributeRegistry.cpp index ccb6d3970e..fc448613f9 100644 --- a/libraries/metavoxels/src/AttributeRegistry.cpp +++ b/libraries/metavoxels/src/AttributeRegistry.cpp @@ -11,12 +11,18 @@ #include "AttributeRegistry.h" #include "MetavoxelData.h" +REGISTER_META_OBJECT(FloatAttribute) REGISTER_META_OBJECT(QRgbAttribute) REGISTER_META_OBJECT(PackedNormalAttribute) +REGISTER_META_OBJECT(SpannerQRgbAttribute) +REGISTER_META_OBJECT(SpannerPackedNormalAttribute) REGISTER_META_OBJECT(SharedObjectAttribute) REGISTER_META_OBJECT(SharedObjectSetAttribute) REGISTER_META_OBJECT(SpannerSetAttribute) +static int attributePointerMetaTypeId = qRegisterMetaType(); +static int ownedAttributeValueMetaTypeId = qRegisterMetaType(); + AttributeRegistry* AttributeRegistry::getInstance() { static AttributeRegistry registry; return ®istry; @@ -27,10 +33,13 @@ AttributeRegistry::AttributeRegistry() : SharedObjectPointer(new DefaultMetavoxelGuide())))), _spannersAttribute(registerAttribute(new SpannerSetAttribute("spanners", &Spanner::staticMetaObject))), _colorAttribute(registerAttribute(new QRgbAttribute("color"))), - _normalAttribute(registerAttribute(new PackedNormalAttribute("normal", qRgb(0, 127, 0)))) { + _normalAttribute(registerAttribute(new PackedNormalAttribute("normal"))), + _spannerColorAttribute(registerAttribute(new SpannerQRgbAttribute("spannerColor"))), + _spannerNormalAttribute(registerAttribute(new SpannerPackedNormalAttribute("spannerNormal"))), + _spannerMaskAttribute(registerAttribute(new FloatAttribute("spannerMask"))) { // our baseline LOD threshold is for voxels; spanners are a different story - const float SPANNER_LOD_THRESHOLD_MULTIPLIER = 4.0f; + const float SPANNER_LOD_THRESHOLD_MULTIPLIER = 8.0f; _spannersAttribute->setLODThresholdMultiplier(SPANNER_LOD_THRESHOLD_MULTIPLIER); } @@ -127,6 +136,14 @@ OwnedAttributeValue::~OwnedAttributeValue() { } } +void OwnedAttributeValue::mix(const AttributeValue& first, const AttributeValue& second, float alpha) { + if (_attribute) { + _attribute->destroy(_value); + } + _attribute = first.getAttribute(); + _value = _attribute->mix(first.getValue(), second.getValue(), alpha); +} + OwnedAttributeValue& OwnedAttributeValue::operator=(const AttributeValue& other) { if (_attribute) { _attribute->destroy(_value); @@ -155,6 +172,10 @@ Attribute::Attribute(const QString& name) : Attribute::~Attribute() { } +MetavoxelNode* Attribute::createMetavoxelNode(const AttributeValue& value, const MetavoxelNode* original) const { + return new MetavoxelNode(value); +} + void Attribute::readMetavoxelRoot(MetavoxelData& data, MetavoxelStreamState& state) { data.createRoot(state.attribute)->read(state); } @@ -179,30 +200,49 @@ void Attribute::writeMetavoxelSubdivision(const MetavoxelNode& root, MetavoxelSt root.writeSubdivision(state); } +FloatAttribute::FloatAttribute(const QString& name, float defaultValue) : + SimpleInlineAttribute(name, defaultValue) { +} + QRgbAttribute::QRgbAttribute(const QString& name, QRgb defaultValue) : InlineAttribute(name, defaultValue) { } - -bool QRgbAttribute::merge(void*& parent, void* children[]) const { + +bool QRgbAttribute::merge(void*& parent, void* children[], bool postRead) const { QRgb firstValue = decodeInline(children[0]); - int totalRed = qRed(firstValue); - int totalGreen = qGreen(firstValue); - int totalBlue = qBlue(firstValue); int totalAlpha = qAlpha(firstValue); + int totalRed = qRed(firstValue) * totalAlpha; + int totalGreen = qGreen(firstValue) * totalAlpha; + int totalBlue = qBlue(firstValue) * totalAlpha; 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); + int alpha = qAlpha(value); + totalRed += qRed(value) * alpha; + totalGreen += qGreen(value) * alpha; + totalBlue += qBlue(value) * alpha; + totalAlpha += alpha; allChildrenEqual &= (firstValue == value); } - parent = encodeInline(qRgba(totalRed / MERGE_COUNT, totalGreen / MERGE_COUNT, - totalBlue / MERGE_COUNT, totalAlpha / MERGE_COUNT)); + if (totalAlpha == 0) { + parent = encodeInline(QRgb()); + } else { + parent = encodeInline(qRgba(totalRed / totalAlpha, totalGreen / totalAlpha, + totalBlue / totalAlpha, totalAlpha / MERGE_COUNT)); + } return allChildrenEqual; } +void* QRgbAttribute::mix(void* first, void* second, float alpha) const { + QRgb firstValue = decodeInline(first); + QRgb secondValue = decodeInline(second); + return encodeInline(qRgba( + glm::mix((float)qRed(firstValue), (float)qRed(secondValue), alpha), + glm::mix((float)qGreen(firstValue), (float)qGreen(secondValue), alpha), + glm::mix((float)qBlue(firstValue), (float)qBlue(secondValue), alpha), + glm::mix((float)qAlpha(firstValue), (float)qAlpha(secondValue), alpha))); +} + void* QRgbAttribute::createFromScript(const QScriptValue& value, QScriptEngine* engine) const { return encodeInline((QRgb)value.toUInt32()); } @@ -227,23 +267,26 @@ PackedNormalAttribute::PackedNormalAttribute(const QString& name, QRgb defaultVa QRgbAttribute(name, defaultValue) { } -bool PackedNormalAttribute::merge(void*& parent, void* children[]) const { +bool PackedNormalAttribute::merge(void*& parent, void* children[], bool postRead) const { QRgb firstValue = decodeInline(children[0]); - int totalRed = (char)qRed(firstValue); - int totalGreen = (char)qGreen(firstValue); - int totalBlue = (char)qBlue(firstValue); + glm::vec3 total = unpackNormal(firstValue) * (float)qAlpha(firstValue); bool allChildrenEqual = true; for (int i = 1; i < Attribute::MERGE_COUNT; i++) { QRgb value = decodeInline(children[i]); - totalRed += (char)qRed(value); - totalGreen += (char)qGreen(value); - totalBlue += (char)qBlue(value); + total += unpackNormal(value) * (float)qAlpha(value); allChildrenEqual &= (firstValue == value); } - parent = encodeInline(packNormal(glm::normalize(glm::vec3(totalRed, totalGreen, totalBlue)))); + float length = glm::length(total); + parent = encodeInline(length < EPSILON ? QRgb() : packNormal(total / length)); return allChildrenEqual; } +void* PackedNormalAttribute::mix(void* first, void* second, float alpha) const { + glm::vec3 firstNormal = unpackNormal(decodeInline(first)); + glm::vec3 secondNormal = unpackNormal(decodeInline(second)); + return encodeInline(packNormal(glm::normalize(glm::mix(firstNormal, secondNormal, alpha)))); +} + const float CHAR_SCALE = 127.0f; const float INVERSE_CHAR_SCALE = 1.0f / CHAR_SCALE; @@ -256,6 +299,106 @@ glm::vec3 unpackNormal(QRgb value) { (char)qBlue(value) * INVERSE_CHAR_SCALE); } +SpannerQRgbAttribute::SpannerQRgbAttribute(const QString& name, QRgb defaultValue) : + QRgbAttribute(name, defaultValue) { +} + +void SpannerQRgbAttribute::read(Bitstream& in, void*& value, bool isLeaf) const { + value = getDefaultValue(); + in.read(&value, 32); +} + +void SpannerQRgbAttribute::write(Bitstream& out, void* value, bool isLeaf) const { + out.write(&value, 32); +} + +MetavoxelNode* SpannerQRgbAttribute::createMetavoxelNode( + const AttributeValue& value, const MetavoxelNode* original) const { + return new MetavoxelNode(value, original); +} + +bool SpannerQRgbAttribute::merge(void*& parent, void* children[], bool postRead) const { + if (postRead) { + for (int i = 0; i < MERGE_COUNT; i++) { + if (qAlpha(decodeInline(children[i])) != 0) { + return false; + } + } + return true; + } + QRgb parentValue = decodeInline(parent); + int totalAlpha = qAlpha(parentValue) * Attribute::MERGE_COUNT; + int totalRed = qRed(parentValue) * totalAlpha; + int totalGreen = qGreen(parentValue) * totalAlpha; + int totalBlue = qBlue(parentValue) * totalAlpha; + bool allChildrenTransparent = true; + for (int i = 0; i < Attribute::MERGE_COUNT; i++) { + QRgb value = decodeInline(children[i]); + int alpha = qAlpha(value); + totalRed += qRed(value) * alpha; + totalGreen += qGreen(value) * alpha; + totalBlue += qBlue(value) * alpha; + totalAlpha += alpha; + allChildrenTransparent &= (alpha == 0); + } + if (totalAlpha == 0) { + parent = encodeInline(QRgb()); + } else { + parent = encodeInline(qRgba(totalRed / totalAlpha, totalGreen / totalAlpha, + totalBlue / totalAlpha, totalAlpha / MERGE_COUNT)); + } + return allChildrenTransparent; +} + +AttributeValue SpannerQRgbAttribute::inherit(const AttributeValue& parentValue) const { + return AttributeValue(parentValue.getAttribute()); +} + +SpannerPackedNormalAttribute::SpannerPackedNormalAttribute(const QString& name, QRgb defaultValue) : + PackedNormalAttribute(name, defaultValue) { +} + +void SpannerPackedNormalAttribute::read(Bitstream& in, void*& value, bool isLeaf) const { + value = getDefaultValue(); + in.read(&value, 32); +} + +void SpannerPackedNormalAttribute::write(Bitstream& out, void* value, bool isLeaf) const { + out.write(&value, 32); +} + +MetavoxelNode* SpannerPackedNormalAttribute::createMetavoxelNode( + const AttributeValue& value, const MetavoxelNode* original) const { + return new MetavoxelNode(value, original); +} + +bool SpannerPackedNormalAttribute::merge(void*& parent, void* children[], bool postRead) const { + if (postRead) { + for (int i = 0; i < MERGE_COUNT; i++) { + if (qAlpha(decodeInline(children[i])) != 0) { + return false; + } + } + return true; + } + QRgb parentValue = decodeInline(parent); + glm::vec3 total = unpackNormal(parentValue) * (float)(qAlpha(parentValue) * Attribute::MERGE_COUNT); + bool allChildrenTransparent = true; + for (int i = 0; i < Attribute::MERGE_COUNT; i++) { + QRgb value = decodeInline(children[i]); + int alpha = qAlpha(value); + total += unpackNormal(value) * (float)alpha; + allChildrenTransparent &= (alpha == 0); + } + float length = glm::length(total); + parent = encodeInline(length < EPSILON ? QRgb() : packNormal(total / length)); + return allChildrenTransparent; +} + +AttributeValue SpannerPackedNormalAttribute::inherit(const AttributeValue& parentValue) const { + return AttributeValue(parentValue.getAttribute()); +} + SharedObjectAttribute::SharedObjectAttribute(const QString& name, const QMetaObject* metaObject, const SharedObjectPointer& defaultValue) : InlineAttribute(name, defaultValue), @@ -275,7 +418,7 @@ void SharedObjectAttribute::write(Bitstream& out, void* value, bool isLeaf) cons } } -bool SharedObjectAttribute::merge(void*& parent, void* children[]) const { +bool SharedObjectAttribute::merge(void*& parent, void* children[], bool postRead) const { SharedObjectPointer firstChild = decodeInline(children[0]); for (int i = 1; i < MERGE_COUNT; i++) { if (firstChild != decodeInline(children[i])) { @@ -310,7 +453,12 @@ void SharedObjectSetAttribute::write(Bitstream& out, void* value, bool isLeaf) c out << decodeInline(value); } -bool SharedObjectSetAttribute::merge(void*& parent, void* children[]) const { +MetavoxelNode* SharedObjectSetAttribute::createMetavoxelNode( + const AttributeValue& value, const MetavoxelNode* original) const { + return new MetavoxelNode(value, original); +} + +bool SharedObjectSetAttribute::merge(void*& parent, void* children[], bool postRead) const { for (int i = 0; i < MERGE_COUNT; i++) { if (!decodeInline(children[i]).isEmpty()) { return false; @@ -319,6 +467,10 @@ bool SharedObjectSetAttribute::merge(void*& parent, void* children[]) const { return true; } +AttributeValue SharedObjectSetAttribute::inherit(const AttributeValue& parentValue) const { + return AttributeValue(parentValue.getAttribute()); +} + QWidget* SharedObjectSetAttribute::createEditor(QWidget* parent) const { return new SharedObjectEditor(_metaObject, parent); } diff --git a/libraries/metavoxels/src/AttributeRegistry.h b/libraries/metavoxels/src/AttributeRegistry.h index b6a415bef7..f7d8d955a5 100644 --- a/libraries/metavoxels/src/AttributeRegistry.h +++ b/libraries/metavoxels/src/AttributeRegistry.h @@ -29,6 +29,8 @@ class MetavoxelStreamState; typedef SharedObjectPointerTemplate AttributePointer; +Q_DECLARE_METATYPE(AttributePointer) + /// Maintains information about metavoxel attribute types. class AttributeRegistry { public: @@ -69,9 +71,18 @@ public: /// Returns a reference to the standard QRgb "color" attribute. const AttributePointer& getColorAttribute() const { return _colorAttribute; } - /// Returns a reference to the standard QRgb "normal" attribute. + /// Returns a reference to the standard packed normal "normal" attribute. const AttributePointer& getNormalAttribute() const { return _normalAttribute; } + /// Returns a reference to the standard QRgb "spannerColor" attribute. + const AttributePointer& getSpannerColorAttribute() const { return _spannerColorAttribute; } + + /// Returns a reference to the standard packed normal "spannerNormal" attribute. + const AttributePointer& getSpannerNormalAttribute() const { return _spannerNormalAttribute; } + + /// Returns a reference to the standard "spannerMask" attribute. + const AttributePointer& getSpannerMaskAttribute() const { return _spannerMaskAttribute; } + private: static QScriptValue getAttribute(QScriptContext* context, QScriptEngine* engine); @@ -81,6 +92,9 @@ private: AttributePointer _spannersAttribute; AttributePointer _colorAttribute; AttributePointer _normalAttribute; + AttributePointer _spannerColorAttribute; + AttributePointer _spannerNormalAttribute; + AttributePointer _spannerMaskAttribute; }; /// Converts a value to a void pointer. @@ -106,8 +120,6 @@ public: 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; @@ -143,6 +155,9 @@ public: /// Destroys the current value, if any. ~OwnedAttributeValue(); + /// Sets this attribute to a mix of the first and second provided. + void mix(const AttributeValue& first, const AttributeValue& second, float alpha); + /// Destroys the current value, if any, and copies the specified other value. OwnedAttributeValue& operator=(const AttributeValue& other); @@ -150,6 +165,8 @@ public: OwnedAttributeValue& operator=(const OwnedAttributeValue& other); }; +Q_DECLARE_METATYPE(OwnedAttributeValue) + /// Represents a registered attribute. class Attribute : public SharedObject { Q_OBJECT @@ -177,6 +194,8 @@ public: virtual void readDelta(Bitstream& in, void*& value, void* reference, bool isLeaf) const { read(in, value, isLeaf); } virtual void writeDelta(Bitstream& out, void* value, void* reference, bool isLeaf) const { write(out, value, isLeaf); } + virtual MetavoxelNode* createMetavoxelNode(const AttributeValue& value, const MetavoxelNode* original) const; + virtual void readMetavoxelRoot(MetavoxelData& data, MetavoxelStreamState& state); virtual void writeMetavoxelRoot(const MetavoxelNode& root, MetavoxelStreamState& state); @@ -189,8 +208,15 @@ public: virtual bool equal(void* first, void* second) const = 0; /// Merges the value of a parent and its children. + /// \param postRead whether or not the merge is happening after a read /// \return whether or not the children and parent values are all equal - virtual bool merge(void*& parent, void* children[]) const = 0; + virtual bool merge(void*& parent, void* children[], bool postRead = false) const = 0; + + /// Given the parent value, returns the value that children should inherit (either the parent value or the default). + virtual AttributeValue inherit(const AttributeValue& parentValue) const { return parentValue; } + + /// Mixes the first and the second, returning a new value with the result. + virtual void* mix(void* first, void* second, float alpha) const = 0; virtual void* getDefaultValue() const = 0; @@ -221,6 +247,8 @@ public: virtual bool equal(void* first, void* second) const { return decodeInline(first) == decodeInline(second); } + virtual void* mix(void* first, void* second, float alpha) const { return create(alpha < 0.5f ? first : second); } + virtual void* getDefaultValue() const { return encodeInline(_defaultValue); } protected: @@ -241,27 +269,39 @@ template inline void InlineAttribute::write(Bitstrea } } -/// Provides merging using the =, ==, += and /= operators. +/// Provides averaging using the +=, ==, and / operators. template class SimpleInlineAttribute : public InlineAttribute { public: - SimpleInlineAttribute(const QString& name, T defaultValue = T()) : InlineAttribute(name, defaultValue) { } + SimpleInlineAttribute(const QString& name, const T& defaultValue = T()) : InlineAttribute(name, defaultValue) { } - virtual bool merge(void*& parent, void* children[]) const; + virtual bool merge(void*& parent, void* children[], bool postRead = false) const; }; -template inline bool SimpleInlineAttribute::merge(void*& parent, void* children[]) const { - T& merged = *(T*)&parent; - merged = decodeInline(children[0]); +template inline bool SimpleInlineAttribute::merge( + void*& parent, void* children[], bool postRead) const { + T firstValue = decodeInline(children[0]); + T totalValue = firstValue; bool allChildrenEqual = true; for (int i = 1; i < Attribute::MERGE_COUNT; i++) { - merged += decodeInline(children[i]); - allChildrenEqual &= (decodeInline(children[0]) == decodeInline(children[i])); + T value = decodeInline(children[i]); + totalValue += value; + allChildrenEqual &= (firstValue == value); } - merged /= Attribute::MERGE_COUNT; + parent = encodeInline(totalValue / Attribute::MERGE_COUNT); return allChildrenEqual; } +/// Simple float attribute. +class FloatAttribute : public SimpleInlineAttribute { + Q_OBJECT + Q_PROPERTY(float defaultValue MEMBER _defaultValue) + +public: + + Q_INVOKABLE FloatAttribute(const QString& name = QString(), float defaultValue = 0.0f); +}; + /// Provides appropriate averaging for RGBA values. class QRgbAttribute : public InlineAttribute { Q_OBJECT @@ -271,7 +311,9 @@ public: Q_INVOKABLE QRgbAttribute(const QString& name = QString(), QRgb defaultValue = QRgb()); - virtual bool merge(void*& parent, void* children[]) const; + virtual bool merge(void*& parent, void* children[], bool postRead = false) const; + + virtual void* mix(void* first, void* second, float alpha) const; virtual void* createFromScript(const QScriptValue& value, QScriptEngine* engine) const; @@ -288,7 +330,9 @@ public: Q_INVOKABLE PackedNormalAttribute(const QString& name = QString(), QRgb defaultValue = QRgb()); - virtual bool merge(void*& parent, void* children[]) const; + virtual bool merge(void*& parent, void* children[], bool postRead = false) const; + + virtual void* mix(void* first, void* second, float alpha) const; }; /// Packs a normal into an RGB value. @@ -297,6 +341,42 @@ QRgb packNormal(const glm::vec3& normal); /// Unpacks a normal from an RGB value. glm::vec3 unpackNormal(QRgb value); +/// RGBA values for voxelized spanners. +class SpannerQRgbAttribute : public QRgbAttribute { + Q_OBJECT + +public: + + Q_INVOKABLE SpannerQRgbAttribute(const QString& name = QString(), QRgb defaultValue = QRgb()); + + virtual void read(Bitstream& in, void*& value, bool isLeaf) const; + virtual void write(Bitstream& out, void* value, bool isLeaf) const; + + virtual MetavoxelNode* createMetavoxelNode(const AttributeValue& value, const MetavoxelNode* original) const; + + virtual bool merge(void*& parent, void* children[], bool postRead = false) const; + + virtual AttributeValue inherit(const AttributeValue& parentValue) const; +}; + +/// Packed normals for voxelized spanners. +class SpannerPackedNormalAttribute : public PackedNormalAttribute { + Q_OBJECT + +public: + + Q_INVOKABLE SpannerPackedNormalAttribute(const QString& name = QString(), QRgb defaultValue = QRgb()); + + virtual void read(Bitstream& in, void*& value, bool isLeaf) const; + virtual void write(Bitstream& out, void* value, bool isLeaf) const; + + virtual MetavoxelNode* createMetavoxelNode(const AttributeValue& value, const MetavoxelNode* original) const; + + virtual bool merge(void*& parent, void* children[], bool postRead = false) const; + + virtual AttributeValue inherit(const AttributeValue& parentValue) const; +}; + /// An attribute that takes the form of QObjects of a given meta-type (a subclass of SharedObject). class SharedObjectAttribute : public InlineAttribute { Q_OBJECT @@ -311,7 +391,7 @@ public: virtual void read(Bitstream& in, void*& value, bool isLeaf) const; virtual void write(Bitstream& out, void* value, bool isLeaf) const; - virtual bool merge(void*& parent, void* children[]) const; + virtual bool merge(void*& parent, void* children[], bool postRead = false) const; virtual void* createFromVariant(const QVariant& value) const; @@ -337,7 +417,11 @@ public: virtual void read(Bitstream& in, void*& value, bool isLeaf) const; virtual void write(Bitstream& out, void* value, bool isLeaf) const; - virtual bool merge(void*& parent, void* children[]) const; + virtual MetavoxelNode* createMetavoxelNode(const AttributeValue& value, const MetavoxelNode* original) const; + + virtual bool merge(void*& parent, void* children[], bool postRead = false) const; + + virtual AttributeValue inherit(const AttributeValue& parentValue) const; virtual QWidget* createEditor(QWidget* parent = NULL) const; diff --git a/libraries/metavoxels/src/Bitstream.cpp b/libraries/metavoxels/src/Bitstream.cpp index f80ef3aa28..077e6c1c69 100644 --- a/libraries/metavoxels/src/Bitstream.cpp +++ b/libraries/metavoxels/src/Bitstream.cpp @@ -8,8 +8,8 @@ #include +#include #include -#include #include #include #include @@ -31,6 +31,7 @@ REGISTER_SIMPLE_TYPE_STREAMER(QString) REGISTER_SIMPLE_TYPE_STREAMER(QUrl) REGISTER_SIMPLE_TYPE_STREAMER(QVariantList) REGISTER_SIMPLE_TYPE_STREAMER(QVariantHash) +REGISTER_SIMPLE_TYPE_STREAMER(SharedObjectPointer) // some types don't quite work with our macro static int vec3Streamer = Bitstream::registerTypeStreamer(qMetaTypeId(), new SimpleTypeStreamer()); @@ -82,6 +83,10 @@ int Bitstream::registerTypeStreamer(int type, TypeStreamer* streamer) { return 0; } +const TypeStreamer* Bitstream::getTypeStreamer(int type) { + return getTypeStreamers().value(type); +} + const QMetaObject* Bitstream::getMetaObject(const QByteArray& className) { return getMetaObjects().value(className); } @@ -90,11 +95,12 @@ QList Bitstream::getMetaObjectSubClasses(const QMetaObject* return getMetaObjectSubClasses().values(metaObject); } -Bitstream::Bitstream(QDataStream& underlying, QObject* parent) : +Bitstream::Bitstream(QDataStream& underlying, MetadataType metadataType, QObject* parent) : QObject(parent), _underlying(underlying), _byte(0), _position(0), + _metadataType(metadataType), _metaObjectStreamer(*this), _typeStreamerStreamer(*this), _attributeStreamer(*this), @@ -102,6 +108,14 @@ Bitstream::Bitstream(QDataStream& underlying, QObject* parent) : _sharedObjectStreamer(*this) { } +void Bitstream::addMetaObjectSubstitution(const QByteArray& className, const QMetaObject* metaObject) { + _metaObjectSubstitutions.insert(className, metaObject); +} + +void Bitstream::addTypeSubstitution(const QByteArray& typeName, int type) { + _typeStreamerSubstitutions.insert(typeName, getTypeStreamers().value(type)); +} + const int LAST_BIT_POSITION = BITS_IN_BYTE - 1; Bitstream& Bitstream::write(const void* data, int bits, int offset) { @@ -171,9 +185,16 @@ void Bitstream::persistWriteMappings(const WriteMappings& mappings) { // find out when shared objects are deleted in order to clear their mappings for (QHash::const_iterator it = mappings.sharedObjectOffsets.constBegin(); it != mappings.sharedObjectOffsets.constEnd(); it++) { - if (it.key()) { - connect(it.key().data(), SIGNAL(destroyed(QObject*)), SLOT(clearSharedObject(QObject*))); + if (!it.key()) { + continue; } + connect(it.key().data(), SIGNAL(destroyed(QObject*)), SLOT(clearSharedObject(QObject*))); + QPointer& reference = _sharedObjectReferences[it.key()->getID()]; + if (reference) { + _sharedObjectStreamer.removePersistentID(reference); + reference->disconnect(this); + } + reference = it.key(); } } @@ -196,6 +217,19 @@ void Bitstream::persistReadMappings(const ReadMappings& mappings) { _attributeStreamer.persistTransientValues(mappings.attributeValues); _scriptStringStreamer.persistTransientValues(mappings.scriptStringValues); _sharedObjectStreamer.persistTransientValues(mappings.sharedObjectValues); + + for (QHash::const_iterator it = mappings.sharedObjectValues.constBegin(); + it != mappings.sharedObjectValues.constEnd(); it++) { + if (!it.value()) { + continue; + } + QPointer& reference = _sharedObjectReferences[it.value()->getRemoteID()]; + if (reference) { + _sharedObjectStreamer.removePersistentValue(reference.data()); + } + reference = it.value(); + _weakSharedObjectHash.remove(it.value()->getRemoteID()); + } } void Bitstream::persistAndResetReadMappings() { @@ -209,6 +243,58 @@ void Bitstream::clearSharedObject(int id) { } } +void Bitstream::writeDelta(bool value, bool reference) { + *this << value; +} + +void Bitstream::readDelta(bool& value, bool reference) { + *this >> value; +} + +void Bitstream::writeDelta(const QVariant& value, const QVariant& reference) { + // QVariant only handles == for built-in types; we need to use our custom operators + const TypeStreamer* streamer = getTypeStreamers().value(value.userType()); + if (value.userType() == reference.userType() && (!streamer || streamer->equal(value, reference))) { + *this << false; + return; + } + *this << true; + _typeStreamerStreamer << streamer; + streamer->writeRawDelta(*this, value, reference); +} + +void Bitstream::readRawDelta(QVariant& value, const QVariant& reference) { + TypeReader typeReader; + _typeStreamerStreamer >> typeReader; + typeReader.readRawDelta(*this, value, reference); +} + +void Bitstream::writeRawDelta(const QObject* value, const QObject* reference) { + if (!value) { + _metaObjectStreamer << NULL; + return; + } + const QMetaObject* metaObject = value->metaObject(); + _metaObjectStreamer << metaObject; + for (int i = 0; i < metaObject->propertyCount(); i++) { + QMetaProperty property = metaObject->property(i); + if (!property.isStored(value)) { + continue; + } + const TypeStreamer* streamer = getTypeStreamers().value(property.userType()); + if (streamer) { + streamer->writeDelta(*this, property.read(value), reference && metaObject == reference->metaObject() ? + property.read(reference) : QVariant()); + } + } +} + +void Bitstream::readRawDelta(QObject*& value, const QObject* reference) { + ObjectReader objectReader; + _metaObjectStreamer >> objectReader; + value = objectReader.readDelta(*this, reference); +} + Bitstream& Bitstream::operator<<(bool value) { if (value) { _byte |= (1 << _position); @@ -324,11 +410,9 @@ Bitstream& Bitstream::operator<<(const QVariant& value) { } Bitstream& Bitstream::operator>>(QVariant& value) { - const TypeStreamer* streamer; - _typeStreamerStreamer >> streamer; - if (streamer) { - value = streamer->read(*this); - } + TypeReader reader; + _typeStreamerStreamer >> reader; + value = reader.read(*this); return *this; } @@ -376,13 +460,9 @@ Bitstream& Bitstream::operator<<(const QObject* object) { } Bitstream& Bitstream::operator>>(QObject*& object) { - const QMetaObject* metaObject; - _metaObjectStreamer >> metaObject; - if (!metaObject) { - object = NULL; - return *this; - } - readProperties(object = metaObject->newInstance()); + ObjectReader objectReader; + _metaObjectStreamer >> objectReader; + object = objectReader.read(*this); return *this; } @@ -392,7 +472,14 @@ Bitstream& Bitstream::operator<<(const QMetaObject* metaObject) { } Bitstream& Bitstream::operator>>(const QMetaObject*& metaObject) { - _metaObjectStreamer >> metaObject; + ObjectReader objectReader; + _metaObjectStreamer >> objectReader; + metaObject = objectReader.getMetaObject(); + return *this; +} + +Bitstream& Bitstream::operator>>(ObjectReader& objectReader) { + _metaObjectStreamer >> objectReader; return *this; } @@ -402,7 +489,14 @@ Bitstream& Bitstream::operator<<(const TypeStreamer* streamer) { } Bitstream& Bitstream::operator>>(const TypeStreamer*& streamer) { - _typeStreamerStreamer >> streamer; + TypeReader typeReader; + _typeStreamerStreamer >> typeReader; + streamer = typeReader.getStreamer(); + return *this; +} + +Bitstream& Bitstream::operator>>(TypeReader& reader) { + _typeStreamerStreamer >> reader; return *this; } @@ -437,36 +531,280 @@ Bitstream& Bitstream::operator>>(SharedObjectPointer& object) { } Bitstream& Bitstream::operator<(const QMetaObject* metaObject) { - return *this << (metaObject ? QByteArray::fromRawData(metaObject->className(), - strlen(metaObject->className())) : QByteArray()); + if (!metaObject) { + return *this << QByteArray(); + } + *this << QByteArray::fromRawData(metaObject->className(), strlen(metaObject->className())); + if (_metadataType == NO_METADATA) { + return *this; + } + int storedPropertyCount = 0; + for (int i = 0; i < metaObject->propertyCount(); i++) { + QMetaProperty property = metaObject->property(i); + if (property.isStored() && getTypeStreamers().contains(property.userType())) { + storedPropertyCount++; + } + } + *this << storedPropertyCount; + QCryptographicHash hash(QCryptographicHash::Md5); + for (int i = 0; i < metaObject->propertyCount(); i++) { + QMetaProperty property = metaObject->property(i); + if (!property.isStored()) { + continue; + } + const TypeStreamer* typeStreamer = getTypeStreamers().value(property.userType()); + if (!typeStreamer) { + continue; + } + _typeStreamerStreamer << typeStreamer; + if (_metadataType == FULL_METADATA) { + *this << QByteArray::fromRawData(property.name(), strlen(property.name())); + } else { + hash.addData(property.name(), strlen(property.name()) + 1); + } + } + if (_metadataType == HASH_METADATA) { + QByteArray hashResult = hash.result(); + write(hashResult.constData(), hashResult.size() * BITS_IN_BYTE); + } + return *this; } -Bitstream& Bitstream::operator>(const QMetaObject*& metaObject) { +Bitstream& Bitstream::operator>(ObjectReader& objectReader) { QByteArray className; *this >> className; if (className.isEmpty()) { - metaObject = NULL; + objectReader = ObjectReader(); return *this; } - metaObject = getMetaObjects().value(className); + const QMetaObject* metaObject = _metaObjectSubstitutions.value(className); + if (!metaObject) { + metaObject = getMetaObjects().value(className); + } if (!metaObject) { qWarning() << "Unknown class name: " << className << "\n"; } + if (_metadataType == NO_METADATA) { + objectReader = ObjectReader(className, metaObject, getPropertyReaders(metaObject)); + return *this; + } + int storedPropertyCount; + *this >> storedPropertyCount; + QVector properties(storedPropertyCount); + for (int i = 0; i < storedPropertyCount; i++) { + TypeReader typeReader; + *this >> typeReader; + QMetaProperty property = QMetaProperty(); + if (_metadataType == FULL_METADATA) { + QByteArray propertyName; + *this >> propertyName; + if (metaObject) { + property = metaObject->property(metaObject->indexOfProperty(propertyName)); + } + } + properties[i] = PropertyReader(typeReader, property); + } + // for hash metadata, check the names/types of the properties as well as the name hash against our own class + if (_metadataType == HASH_METADATA) { + QCryptographicHash hash(QCryptographicHash::Md5); + bool matches = true; + if (metaObject) { + int propertyIndex = 0; + for (int i = 0; i < metaObject->propertyCount(); i++) { + QMetaProperty property = metaObject->property(i); + if (!property.isStored()) { + continue; + } + const TypeStreamer* typeStreamer = getTypeStreamers().value(property.userType()); + if (!typeStreamer) { + continue; + } + if (propertyIndex >= properties.size() || + !properties.at(propertyIndex).getReader().matchesExactly(typeStreamer)) { + matches = false; + break; + } + hash.addData(property.name(), strlen(property.name()) + 1); + propertyIndex++; + } + if (propertyIndex != properties.size()) { + matches = false; + } + } + QByteArray localHashResult = hash.result(); + QByteArray remoteHashResult(localHashResult.size(), 0); + read(remoteHashResult.data(), remoteHashResult.size() * BITS_IN_BYTE); + if (metaObject && matches && localHashResult == remoteHashResult) { + objectReader = ObjectReader(className, metaObject, getPropertyReaders(metaObject)); + return *this; + } + } + objectReader = ObjectReader(className, metaObject, properties); return *this; } Bitstream& Bitstream::operator<(const TypeStreamer* streamer) { const char* typeName = QMetaType::typeName(streamer->getType()); - return *this << QByteArray::fromRawData(typeName, strlen(typeName)); + *this << QByteArray::fromRawData(typeName, strlen(typeName)); + if (_metadataType == NO_METADATA) { + return *this; + } + TypeReader::Type type = streamer->getReaderType(); + *this << (int)type; + switch (type) { + case TypeReader::SIMPLE_TYPE: + return *this; + + case TypeReader::LIST_TYPE: + case TypeReader::SET_TYPE: + return *this << streamer->getValueStreamer(); + + case TypeReader::MAP_TYPE: + return *this << streamer->getKeyStreamer() << streamer->getValueStreamer(); + + default: + break; // fall through + } + // streamable type + const QVector& metaFields = streamer->getMetaFields(); + *this << metaFields.size(); + if (metaFields.isEmpty()) { + return *this; + } + QCryptographicHash hash(QCryptographicHash::Md5); + foreach (const MetaField& metaField, metaFields) { + _typeStreamerStreamer << metaField.getStreamer(); + if (_metadataType == FULL_METADATA) { + *this << metaField.getName(); + } else { + hash.addData(metaField.getName().constData(), metaField.getName().size() + 1); + } + } + if (_metadataType == HASH_METADATA) { + QByteArray hashResult = hash.result(); + write(hashResult.constData(), hashResult.size() * BITS_IN_BYTE); + } + return *this; } -Bitstream& Bitstream::operator>(const TypeStreamer*& streamer) { +Bitstream& Bitstream::operator>(TypeReader& reader) { QByteArray typeName; *this >> typeName; - streamer = getTypeStreamers().value(QMetaType::type(typeName.constData())); + const TypeStreamer* streamer = _typeStreamerSubstitutions.value(typeName); + if (!streamer) { + streamer = getTypeStreamers().value(QMetaType::type(typeName.constData())); + } if (!streamer) { qWarning() << "Unknown type name: " << typeName << "\n"; } + if (_metadataType == NO_METADATA) { + reader = TypeReader(typeName, streamer); + return *this; + } + int type; + *this >> type; + switch (type) { + case TypeReader::SIMPLE_TYPE: + reader = TypeReader(typeName, streamer); + return *this; + + case TypeReader::LIST_TYPE: + case TypeReader::SET_TYPE: { + TypeReader valueReader; + *this >> valueReader; + if (streamer && streamer->getReaderType() == type && + valueReader.matchesExactly(streamer->getValueStreamer())) { + reader = TypeReader(typeName, streamer); + } else { + reader = TypeReader(typeName, streamer, false, (TypeReader::Type)type, TypeReaderPointer(), + TypeReaderPointer(new TypeReader(valueReader))); + } + return *this; + } + case TypeReader::MAP_TYPE: { + TypeReader keyReader, valueReader; + *this >> keyReader >> valueReader; + if (streamer && streamer->getReaderType() == TypeReader::MAP_TYPE && + keyReader.matchesExactly(streamer->getKeyStreamer()) && + valueReader.matchesExactly(streamer->getValueStreamer())) { + reader = TypeReader(typeName, streamer); + } else { + reader = TypeReader(typeName, streamer, false, TypeReader::MAP_TYPE, + TypeReaderPointer(new TypeReader(keyReader)), TypeReaderPointer(new TypeReader(valueReader))); + } + return *this; + } + } + // streamable type + int fieldCount; + *this >> fieldCount; + QVector fields(fieldCount); + for (int i = 0; i < fieldCount; i++) { + TypeReader typeReader; + *this >> typeReader; + int index = -1; + if (_metadataType == FULL_METADATA) { + QByteArray fieldName; + *this >> fieldName; + if (streamer) { + index = streamer->getFieldIndex(fieldName); + } + } + fields[i] = FieldReader(typeReader, index); + } + // for hash metadata, check the names/types of the fields as well as the name hash against our own class + if (_metadataType == HASH_METADATA) { + QCryptographicHash hash(QCryptographicHash::Md5); + bool matches = true; + if (streamer) { + const QVector& localFields = streamer->getMetaFields(); + if (fieldCount != localFields.size()) { + matches = false; + + } else { + if (fieldCount == 0) { + reader = TypeReader(typeName, streamer); + return *this; + } + for (int i = 0; i < fieldCount; i++) { + const MetaField& localField = localFields.at(i); + if (!fields.at(i).getReader().matchesExactly(localField.getStreamer())) { + matches = false; + break; + } + hash.addData(localField.getName().constData(), localField.getName().size() + 1); + } + } + } + QByteArray localHashResult = hash.result(); + QByteArray remoteHashResult(localHashResult.size(), 0); + read(remoteHashResult.data(), remoteHashResult.size() * BITS_IN_BYTE); + if (streamer && matches && localHashResult == remoteHashResult) { + // since everything is the same, we can use the default streamer + reader = TypeReader(typeName, streamer); + return *this; + } + } else if (streamer) { + // if all fields are the same type and in the right order, we can use the (more efficient) default streamer + const QVector& localFields = streamer->getMetaFields(); + if (fieldCount != localFields.size()) { + reader = TypeReader(typeName, streamer, false, TypeReader::STREAMABLE_TYPE, + TypeReaderPointer(), TypeReaderPointer(), fields); + return *this; + } + for (int i = 0; i < fieldCount; i++) { + const FieldReader& fieldReader = fields.at(i); + if (!fieldReader.getReader().matchesExactly(localFields.at(i).getStreamer()) || fieldReader.getIndex() != i) { + reader = TypeReader(typeName, streamer, false, TypeReader::STREAMABLE_TYPE, + TypeReaderPointer(), TypeReaderPointer(), fields); + return *this; + } + } + reader = TypeReader(typeName, streamer); + return *this; + } + reader = TypeReader(typeName, streamer, false, TypeReader::STREAMABLE_TYPE, + TypeReaderPointer(), TypeReaderPointer(), fields); return *this; } @@ -496,7 +834,14 @@ Bitstream& Bitstream::operator<(const SharedObjectPointer& object) { if (!object) { return *this << (int)0; } - return *this << object->getID() << (QObject*)object.data(); + *this << object->getID(); + QPointer reference = _sharedObjectReferences.value(object->getID()); + if (reference) { + writeRawDelta((QObject*)object.data(), (QObject*)reference.data()); + } else { + *this << (QObject*)object.data(); + } + return *this; } Bitstream& Bitstream::operator>(SharedObjectPointer& object) { @@ -506,18 +851,23 @@ Bitstream& Bitstream::operator>(SharedObjectPointer& object) { object = SharedObjectPointer(); return *this; } + QPointer reference = _sharedObjectReferences.value(id); QPointer& pointer = _weakSharedObjectHash[id]; if (pointer) { - const QMetaObject* metaObject; - _metaObjectStreamer >> metaObject; - if (metaObject != pointer->metaObject()) { - qWarning() << "Class mismatch: " << pointer->metaObject()->className() << metaObject->className(); + ObjectReader objectReader; + _metaObjectStreamer >> objectReader; + if (reference) { + objectReader.readDelta(*this, reference.data(), pointer.data()); + } else { + objectReader.read(*this, pointer.data()); } - readProperties(pointer.data()); - } else { - QObject* rawObject; - *this >> rawObject; + QObject* rawObject; + if (reference) { + readRawDelta(rawObject, (QObject*)reference.data()); + } else { + *this >> rawObject; + } pointer = static_cast(rawObject); pointer->setRemoteID(id); } @@ -526,26 +876,14 @@ Bitstream& Bitstream::operator>(SharedObjectPointer& object) { } void Bitstream::clearSharedObject(QObject* object) { - int id = _sharedObjectStreamer.takePersistentID(static_cast(object)); + SharedObject* sharedObject = static_cast(object); + _sharedObjectReferences.remove(sharedObject->getID()); + int id = _sharedObjectStreamer.takePersistentID(sharedObject); if (id != 0) { emit sharedObjectCleared(id); } } -void Bitstream::readProperties(QObject* object) { - const QMetaObject* metaObject = object->metaObject(); - for (int i = 0; i < metaObject->propertyCount(); i++) { - QMetaProperty property = metaObject->property(i); - if (!property.isStored(object)) { - continue; - } - const TypeStreamer* streamer = getTypeStreamers().value(property.userType()); - if (streamer) { - property.write(object, streamer->read(*this)); - } - } -} - QHash& Bitstream::getMetaObjects() { static QHash metaObjects; return metaObjects; @@ -561,3 +899,324 @@ QHash& Bitstream::getTypeStreamers() { return typeStreamers; } +QVector Bitstream::getPropertyReaders(const QMetaObject* metaObject) { + QVector propertyReaders; + if (!metaObject) { + return propertyReaders; + } + for (int i = 0; i < metaObject->propertyCount(); i++) { + QMetaProperty property = metaObject->property(i); + if (!property.isStored()) { + continue; + } + const TypeStreamer* typeStreamer = getTypeStreamers().value(property.userType()); + if (typeStreamer) { + propertyReaders.append(PropertyReader(TypeReader(QByteArray(), typeStreamer), property)); + } + } + return propertyReaders; +} + +TypeReader::TypeReader(const QByteArray& typeName, const TypeStreamer* streamer, bool exactMatch, Type type, + const TypeReaderPointer& keyReader, const TypeReaderPointer& valueReader, const QVector& fields) : + _typeName(typeName), + _streamer(streamer), + _exactMatch(exactMatch), + _type(type), + _keyReader(keyReader), + _valueReader(valueReader), + _fields(fields) { +} + +QVariant TypeReader::read(Bitstream& in) const { + if (_exactMatch) { + return _streamer->read(in); + } + QVariant object = _streamer ? QVariant(_streamer->getType(), 0) : QVariant(); + switch (_type) { + case STREAMABLE_TYPE: { + foreach (const FieldReader& field, _fields) { + field.read(in, _streamer, object); + } + break; + } + case LIST_TYPE: + case SET_TYPE: { + int size; + in >> size; + for (int i = 0; i < size; i++) { + QVariant value = _valueReader->read(in); + if (_streamer) { + _streamer->insert(object, value); + } + } + break; + } + case MAP_TYPE: { + int size; + in >> size; + for (int i = 0; i < size; i++) { + QVariant key = _keyReader->read(in); + QVariant value = _valueReader->read(in); + if (_streamer) { + _streamer->insert(object, key, value); + } + } + break; + } + default: + break; + } + return object; +} + +void TypeReader::readDelta(Bitstream& in, QVariant& object, const QVariant& reference) const { + if (_exactMatch) { + _streamer->readDelta(in, object, reference); + return; + } + bool changed; + in >> changed; + if (changed) { + readRawDelta(in, object, reference); + } else { + object = reference; + } +} + +void TypeReader::readRawDelta(Bitstream& in, QVariant& object, const QVariant& reference) const { + if (_exactMatch) { + _streamer->readRawDelta(in, object, reference); + return; + } + switch (_type) { + case STREAMABLE_TYPE: { + foreach (const FieldReader& field, _fields) { + field.readDelta(in, _streamer, object, reference); + } + break; + } + case LIST_TYPE: { + object = reference; + int size, referenceSize; + in >> size >> referenceSize; + if (_streamer) { + if (size < referenceSize) { + _streamer->prune(object, size); + } + for (int i = 0; i < size; i++) { + if (i < referenceSize) { + QVariant value; + _valueReader->readDelta(in, value, _streamer->getValue(reference, i)); + _streamer->setValue(object, i, value); + } else { + _streamer->insert(object, _valueReader->read(in)); + } + } + } else { + for (int i = 0; i < size; i++) { + if (i < referenceSize) { + QVariant value; + _valueReader->readDelta(in, value, QVariant()); + } else { + _valueReader->read(in); + } + } + } + break; + } + case SET_TYPE: { + object = reference; + int addedOrRemoved; + in >> addedOrRemoved; + for (int i = 0; i < addedOrRemoved; i++) { + QVariant value = _valueReader->read(in); + if (_streamer && !_streamer->remove(object, value)) { + _streamer->insert(object, value); + } + } + break; + } + case MAP_TYPE: { + object = reference; + int added; + in >> added; + for (int i = 0; i < added; i++) { + QVariant key = _keyReader->read(in); + QVariant value = _valueReader->read(in); + if (_streamer) { + _streamer->insert(object, key, value); + } + } + int modified; + in >> modified; + for (int i = 0; i < modified; i++) { + QVariant key = _keyReader->read(in); + QVariant value; + if (_streamer) { + _valueReader->readDelta(in, value, _streamer->getValue(reference, key)); + _streamer->insert(object, key, value); + } else { + _valueReader->readDelta(in, value, QVariant()); + } + } + int removed; + in >> removed; + for (int i = 0; i < removed; i++) { + QVariant key = _keyReader->read(in); + if (_streamer) { + _streamer->remove(object, key); + } + } + break; + } + default: + break; + } +} + +bool TypeReader::matchesExactly(const TypeStreamer* streamer) const { + return _exactMatch && _streamer == streamer; +} + +uint qHash(const TypeReader& typeReader, uint seed) { + return qHash(typeReader.getTypeName(), seed); +} + +FieldReader::FieldReader(const TypeReader& reader, int index) : + _reader(reader), + _index(index) { +} + +void FieldReader::read(Bitstream& in, const TypeStreamer* streamer, QVariant& object) const { + QVariant value = _reader.read(in); + if (_index != -1 && streamer) { + streamer->setField(object, _index, value); + } +} + +void FieldReader::readDelta(Bitstream& in, const TypeStreamer* streamer, QVariant& object, const QVariant& reference) const { + QVariant value; + if (_index != -1 && streamer) { + _reader.readDelta(in, value, streamer->getField(reference, _index)); + streamer->setField(object, _index, value); + } else { + _reader.readDelta(in, value, QVariant()); + } +} + +ObjectReader::ObjectReader(const QByteArray& className, const QMetaObject* metaObject, + const QVector& properties) : + _className(className), + _metaObject(metaObject), + _properties(properties) { +} + +QObject* ObjectReader::read(Bitstream& in, QObject* object) const { + if (!object && _metaObject) { + object = _metaObject->newInstance(); + } + foreach (const PropertyReader& property, _properties) { + property.read(in, object); + } + return object; +} + +QObject* ObjectReader::readDelta(Bitstream& in, const QObject* reference, QObject* object) const { + if (!object && _metaObject) { + object = _metaObject->newInstance(); + } + foreach (const PropertyReader& property, _properties) { + property.readDelta(in, object, reference); + } + return object; +} + +uint qHash(const ObjectReader& objectReader, uint seed) { + return qHash(objectReader.getClassName(), seed); +} + +PropertyReader::PropertyReader(const TypeReader& reader, const QMetaProperty& property) : + _reader(reader), + _property(property) { +} + +void PropertyReader::read(Bitstream& in, QObject* object) const { + QVariant value = _reader.read(in); + if (_property.isValid() && object) { + _property.write(object, value); + } +} + +void PropertyReader::readDelta(Bitstream& in, QObject* object, const QObject* reference) const { + QVariant value; + _reader.readDelta(in, value, (_property.isValid() && reference) ? _property.read(reference) : QVariant()); + if (_property.isValid() && object) { + _property.write(object, value); + } +} + +MetaField::MetaField(const QByteArray& name, const TypeStreamer* streamer) : + _name(name), + _streamer(streamer) { +} + +TypeStreamer::~TypeStreamer() { +} + +const QVector& TypeStreamer::getMetaFields() const { + static QVector emptyMetaFields; + return emptyMetaFields; +} + +int TypeStreamer::getFieldIndex(const QByteArray& name) const { + return -1; +} + +void TypeStreamer::setField(QVariant& object, int index, const QVariant& value) const { + // nothing by default +} + +QVariant TypeStreamer::getField(const QVariant& object, int index) const { + return QVariant(); +} + +TypeReader::Type TypeStreamer::getReaderType() const { + return TypeReader::SIMPLE_TYPE; +} + +const TypeStreamer* TypeStreamer::getKeyStreamer() const { + return NULL; +} + +const TypeStreamer* TypeStreamer::getValueStreamer() const { + return NULL; +} + +void TypeStreamer::insert(QVariant& object, const QVariant& element) const { + // nothing by default +} + +void TypeStreamer::insert(QVariant& object, const QVariant& key, const QVariant& value) const { + // nothing by default +} + +bool TypeStreamer::remove(QVariant& object, const QVariant& key) const { + return false; +} + +QVariant TypeStreamer::getValue(const QVariant& object, const QVariant& key) const { + return QVariant(); +} + +void TypeStreamer::prune(QVariant& object, int size) const { + // nothing by default +} + +QVariant TypeStreamer::getValue(const QVariant& object, int index) const { + return QVariant(); +} + +void TypeStreamer::setValue(QVariant& object, int index, const QVariant& value) const { + // nothing by default +} diff --git a/libraries/metavoxels/src/Bitstream.h b/libraries/metavoxels/src/Bitstream.h index ca26509d60..760154a928 100644 --- a/libraries/metavoxels/src/Bitstream.h +++ b/libraries/metavoxels/src/Bitstream.h @@ -10,11 +10,13 @@ #define __interface__Bitstream__ #include +#include #include #include #include #include #include +#include #include #include @@ -29,7 +31,11 @@ class QUrl; class Attribute; class AttributeValue; class Bitstream; +class FieldReader; +class ObjectReader; class OwnedAttributeValue; +class PropertyReader; +class TypeReader; class TypeStreamer; typedef SharedObjectPointerTemplate AttributePointer; @@ -55,26 +61,30 @@ private: /// Provides a means to stream repeated values efficiently. The value is first streamed along with a unique ID. When /// subsequently streamed, only the ID is sent. -template class RepeatedValueStreamer { +template class RepeatedValueStreamer { public: RepeatedValueStreamer(Bitstream& stream) : _stream(stream), _idStreamer(stream), _lastPersistentID(0), _lastTransientOffset(0) { } - QHash getAndResetTransientOffsets(); + QHash getAndResetTransientOffsets(); - void persistTransientOffsets(const QHash& transientOffsets); + void persistTransientOffsets(const QHash& transientOffsets); - QHash getAndResetTransientValues(); + QHash getAndResetTransientValues(); - void persistTransientValues(const QHash& transientValues); + void persistTransientValues(const QHash& transientValues); + + void removePersistentID(P value) { _persistentIDs.remove(value); } int takePersistentID(P value) { return _persistentIDs.take(value); } - T takePersistentValue(int id) { T value = _persistentValues.take(id); _persistentIDs.remove(value); return value; } + void removePersistentValue(V value) { int id = _valueIDs.take(value); _persistentValues.remove(id); } - RepeatedValueStreamer& operator<<(T value); - RepeatedValueStreamer& operator>>(T& value); + V takePersistentValue(int id) { V value = _persistentValues.take(id); _valueIDs.remove(value); return value; } + + RepeatedValueStreamer& operator<<(K value); + RepeatedValueStreamer& operator>>(V& value); private: @@ -83,23 +93,24 @@ private: int _lastPersistentID; int _lastTransientOffset; QHash _persistentIDs; - QHash _transientOffsets; - QHash _persistentValues; - QHash _transientValues; + QHash _transientOffsets; + QHash _persistentValues; + QHash _transientValues; + QHash _valueIDs; }; -template inline QHash RepeatedValueStreamer::getAndResetTransientOffsets() { - QHash transientOffsets; +template inline QHash RepeatedValueStreamer::getAndResetTransientOffsets() { + QHash transientOffsets; _transientOffsets.swap(transientOffsets); _lastTransientOffset = 0; _idStreamer.setBitsFromValue(_lastPersistentID); return transientOffsets; } -template inline void RepeatedValueStreamer::persistTransientOffsets( - const QHash& transientOffsets) { +template inline void RepeatedValueStreamer::persistTransientOffsets( + const QHash& transientOffsets) { int oldLastPersistentID = _lastPersistentID; - for (typename QHash::const_iterator it = transientOffsets.constBegin(); it != transientOffsets.constEnd(); it++) { + for (typename QHash::const_iterator it = transientOffsets.constBegin(); it != transientOffsets.constEnd(); it++) { int& id = _persistentIDs[it.key()]; if (id == 0) { id = oldLastPersistentID + it.value(); @@ -109,18 +120,18 @@ template inline void RepeatedValueStreamer::persistTrans _idStreamer.setBitsFromValue(_lastPersistentID); } -template inline QHash RepeatedValueStreamer::getAndResetTransientValues() { - QHash transientValues; +template inline QHash RepeatedValueStreamer::getAndResetTransientValues() { + QHash transientValues; _transientValues.swap(transientValues); _idStreamer.setBitsFromValue(_lastPersistentID); return transientValues; } -template inline void RepeatedValueStreamer::persistTransientValues( - const QHash& transientValues) { +template inline void RepeatedValueStreamer::persistTransientValues( + const QHash& transientValues) { int oldLastPersistentID = _lastPersistentID; - for (typename QHash::const_iterator it = transientValues.constBegin(); it != transientValues.constEnd(); it++) { - int& id = _persistentIDs[it.value()]; + for (typename QHash::const_iterator it = transientValues.constBegin(); it != transientValues.constEnd(); it++) { + int& id = _valueIDs[it.value()]; if (id == 0) { id = oldLastPersistentID + it.key(); _lastPersistentID = qMax(_lastPersistentID, id); @@ -130,7 +141,8 @@ template inline void RepeatedValueStreamer::persistTrans _idStreamer.setBitsFromValue(_lastPersistentID); } -template inline RepeatedValueStreamer& RepeatedValueStreamer::operator<<(T value) { +template inline RepeatedValueStreamer& + RepeatedValueStreamer::operator<<(K value) { int id = _persistentIDs.value(value); if (id == 0) { int& offset = _transientOffsets[value]; @@ -147,7 +159,8 @@ template inline RepeatedValueStreamer& RepeatedValueStre return *this; } -template inline RepeatedValueStreamer& RepeatedValueStreamer::operator>>(T& value) { +template inline RepeatedValueStreamer& + RepeatedValueStreamer::operator>>(V& value) { int id; _idStreamer >> id; if (id <= _lastPersistentID) { @@ -155,7 +168,7 @@ template inline RepeatedValueStreamer& RepeatedValueStre } else { int offset = id - _lastPersistentID; - typename QHash::iterator it = _transientValues.find(offset); + typename QHash::iterator it = _transientValues.find(offset); if (it == _transientValues.end()) { _stream > value; _transientValues.insert(offset, value); @@ -184,8 +197,8 @@ public: class ReadMappings { public: - QHash metaObjectValues; - QHash typeStreamerValues; + QHash metaObjectValues; + QHash typeStreamerValues; QHash attributeValues; QHash scriptStringValues; QHash sharedObjectValues; @@ -199,14 +212,25 @@ public: /// \return zero; the function only returns a value so that it can be used in static initialization static int registerTypeStreamer(int type, TypeStreamer* streamer); + /// Returns the streamer registered for the supplied type, if any. + static const TypeStreamer* getTypeStreamer(int type); + /// Returns the meta-object registered under the supplied class name, if any. static const QMetaObject* getMetaObject(const QByteArray& className); /// Returns the list of registered subclasses for the supplied meta-object. static QList getMetaObjectSubClasses(const QMetaObject* metaObject); + enum MetadataType { NO_METADATA, HASH_METADATA, FULL_METADATA }; + /// Creates a new bitstream. Note: the stream may be used for reading or writing, but not both. - Bitstream(QDataStream& underlying, QObject* parent = NULL); + Bitstream(QDataStream& underlying, MetadataType metadataType = NO_METADATA, QObject* parent = NULL); + + /// Substitutes the supplied metaobject for the given class name's default mapping. + void addMetaObjectSubstitution(const QByteArray& className, const QMetaObject* metaObject); + + /// Substitutes the supplied type for the given type name's default mapping. + void addTypeSubstitution(const QByteArray& typeName, int type); /// Writes a set of bits to the underlying stream. /// \param bits the number of bits to write @@ -248,6 +272,31 @@ public: /// Removes a shared object from the read mappings. void clearSharedObject(int id); + void writeDelta(bool value, bool reference); + void readDelta(bool& value, bool reference); + + void writeDelta(const QVariant& value, const QVariant& reference); + + template void writeDelta(const T& value, const T& reference); + template void readDelta(T& value, const T& reference); + + void readRawDelta(QVariant& value, const QVariant& reference); + + void writeRawDelta(const QObject* value, const QObject* reference); + void readRawDelta(QObject*& value, const QObject* reference); + + template void writeRawDelta(const T& value, const T& reference); + template void readRawDelta(T& value, const T& reference); + + template void writeRawDelta(const QList& value, const QList& reference); + template void readRawDelta(QList& value, const QList& reference); + + template void writeRawDelta(const QSet& value, const QSet& reference); + template void readRawDelta(QSet& value, const QSet& reference); + + template void writeRawDelta(const QHash& value, const QHash& reference); + template void readRawDelta(QHash& value, const QHash& reference); + Bitstream& operator<<(bool value); Bitstream& operator>>(bool& value); @@ -295,9 +344,11 @@ public: Bitstream& operator<<(const QMetaObject* metaObject); Bitstream& operator>>(const QMetaObject*& metaObject); + Bitstream& operator>>(ObjectReader& objectReader); Bitstream& operator<<(const TypeStreamer* streamer); Bitstream& operator>>(const TypeStreamer*& streamer); + Bitstream& operator>>(TypeReader& reader); Bitstream& operator<<(const AttributePointer& attribute); Bitstream& operator>>(AttributePointer& attribute); @@ -309,10 +360,10 @@ public: Bitstream& operator>>(SharedObjectPointer& object); Bitstream& operator<(const QMetaObject* metaObject); - Bitstream& operator>(const QMetaObject*& metaObject); + Bitstream& operator>(ObjectReader& objectReader); Bitstream& operator<(const TypeStreamer* streamer); - Bitstream& operator>(const TypeStreamer*& streamer); + Bitstream& operator>(TypeReader& reader); Bitstream& operator<(const AttributePointer& attribute); Bitstream& operator>(AttributePointer& attribute); @@ -333,25 +384,195 @@ private slots: private: - void readProperties(QObject* object); - QDataStream& _underlying; quint8 _byte; int _position; - RepeatedValueStreamer _metaObjectStreamer; - RepeatedValueStreamer _typeStreamerStreamer; + MetadataType _metadataType; + + RepeatedValueStreamer _metaObjectStreamer; + RepeatedValueStreamer _typeStreamerStreamer; RepeatedValueStreamer _attributeStreamer; RepeatedValueStreamer _scriptStringStreamer; RepeatedValueStreamer _sharedObjectStreamer; + + WeakSharedObjectHash _sharedObjectReferences; WeakSharedObjectHash _weakSharedObjectHash; + QHash _metaObjectSubstitutions; + QHash _typeStreamerSubstitutions; + static QHash& getMetaObjects(); static QMultiHash& getMetaObjectSubClasses(); static QHash& getTypeStreamers(); + static QVector getPropertyReaders(const QMetaObject* metaObject); }; +template inline void Bitstream::writeDelta(const T& value, const T& reference) { + if (value == reference) { + *this << false; + } else { + *this << true; + writeRawDelta(value, reference); + } +} + +template inline void Bitstream::readDelta(T& value, const T& reference) { + bool changed; + *this >> changed; + if (changed) { + readRawDelta(value, reference); + } else { + value = reference; + } +} + +template inline void Bitstream::writeRawDelta(const T& value, const T& reference) { + *this << value; +} + +template inline void Bitstream::readRawDelta(T& value, const T& reference) { + *this >> value; +} + +template inline void Bitstream::writeRawDelta(const QList& value, const QList& reference) { + *this << value.size(); + *this << reference.size(); + for (int i = 0; i < value.size(); i++) { + if (i < reference.size()) { + writeDelta(value.at(i), reference.at(i)); + } else { + *this << value.at(i); + } + } +} + +template inline void Bitstream::readRawDelta(QList& value, const QList& reference) { + value = reference; + int size, referenceSize; + *this >> size >> referenceSize; + if (size < value.size()) { + value.erase(value.begin() + size, value.end()); + } + for (int i = 0; i < size; i++) { + if (i < referenceSize) { + readDelta(value[i], reference.at(i)); + } else { + T element; + *this >> element; + value.append(element); + } + } +} + +template inline void Bitstream::writeRawDelta(const QSet& value, const QSet& reference) { + int addedOrRemoved = 0; + foreach (const T& element, value) { + if (!reference.contains(element)) { + addedOrRemoved++; + } + } + foreach (const T& element, reference) { + if (!value.contains(element)) { + addedOrRemoved++; + } + } + *this << addedOrRemoved; + foreach (const T& element, value) { + if (!reference.contains(element)) { + *this << element; + } + } + foreach (const T& element, reference) { + if (!value.contains(element)) { + *this << element; + } + } +} + +template inline void Bitstream::readRawDelta(QSet& value, const QSet& reference) { + value = reference; + int addedOrRemoved; + *this >> addedOrRemoved; + for (int i = 0; i < addedOrRemoved; i++) { + T element; + *this >> element; + if (!value.remove(element)) { + value.insert(element); + } + } +} + +template inline void Bitstream::writeRawDelta(const QHash& value, const QHash& reference) { + int added = 0; + int modified = 0; + for (typename QHash::const_iterator it = value.constBegin(); it != value.constEnd(); it++) { + typename QHash::const_iterator previous = reference.find(it.key()); + if (previous == reference.constEnd()) { + added++; + } else if (previous.value() != it.value()) { + modified++; + } + } + *this << added; + for (typename QHash::const_iterator it = value.constBegin(); it != value.constEnd(); it++) { + if (!reference.contains(it.key())) { + *this << it.key(); + *this << it.value(); + } + } + *this << modified; + for (typename QHash::const_iterator it = value.constBegin(); it != value.constEnd(); it++) { + typename QHash::const_iterator previous = reference.find(it.key()); + if (previous != reference.constEnd() && previous.value() != it.value()) { + *this << it.key(); + writeDelta(it.value(), previous.value()); + } + } + int removed = 0; + for (typename QHash::const_iterator it = reference.constBegin(); it != reference.constEnd(); it++) { + if (!value.contains(it.key())) { + removed++; + } + } + *this << removed; + for (typename QHash::const_iterator it = reference.constBegin(); it != reference.constEnd(); it++) { + if (!value.contains(it.key())) { + *this << it.key(); + } + } +} + +template inline void Bitstream::readRawDelta(QHash& value, const QHash& reference) { + value = reference; + int added; + *this >> added; + for (int i = 0; i < added; i++) { + K key; + V mapping; + *this >> key >> mapping; + value.insert(key, mapping); + } + int modified; + *this >> modified; + for (int i = 0; i < modified; i++) { + K key; + *this >> key; + V& mapping = value[key]; + V newMapping; + readDelta(newMapping, mapping); + mapping = newMapping; + } + int removed; + *this >> removed; + for (int i = 0; i < removed; i++) { + K key; + *this >> key; + value.remove(key); + } +} + template inline Bitstream& Bitstream::operator<<(const QList& list) { *this << list.size(); foreach (const T& entry, list) { @@ -418,6 +639,119 @@ template inline Bitstream& Bitstream::operator>>(QHash& return *this; } +typedef QSharedPointer TypeReaderPointer; + +/// Contains the information required to read a type from the stream. +class TypeReader { +public: + + enum Type { SIMPLE_TYPE, STREAMABLE_TYPE, LIST_TYPE, SET_TYPE, MAP_TYPE }; + + TypeReader(const QByteArray& typeName = QByteArray(), const TypeStreamer* streamer = NULL, bool exactMatch = true, + Type type = SIMPLE_TYPE, const TypeReaderPointer& keyReader = TypeReaderPointer(), + const TypeReaderPointer& valueReader = TypeReaderPointer(), + const QVector& fields = QVector()); + + const QByteArray& getTypeName() const { return _typeName; } + const TypeStreamer* getStreamer() const { return _streamer; } + + QVariant read(Bitstream& in) const; + void readDelta(Bitstream& in, QVariant& object, const QVariant& reference) const; + void readRawDelta(Bitstream& in, QVariant& object, const QVariant& reference) const; + + bool matchesExactly(const TypeStreamer* streamer) const; + + bool operator==(const TypeReader& other) const { return _typeName == other._typeName; } + bool operator!=(const TypeReader& other) const { return _typeName != other._typeName; } + +private: + + QByteArray _typeName; + const TypeStreamer* _streamer; + bool _exactMatch; + Type _type; + TypeReaderPointer _keyReader; + TypeReaderPointer _valueReader; + QVector _fields; +}; + +uint qHash(const TypeReader& typeReader, uint seed = 0); + +/// Contains the information required to read a metatype field from the stream and apply it. +class FieldReader { +public: + + FieldReader(const TypeReader& reader = TypeReader(), int index = -1); + + const TypeReader& getReader() const { return _reader; } + int getIndex() const { return _index; } + + void read(Bitstream& in, const TypeStreamer* streamer, QVariant& object) const; + void readDelta(Bitstream& in, const TypeStreamer* streamer, QVariant& object, const QVariant& reference) const; + +private: + + TypeReader _reader; + int _index; +}; + +/// Contains the information required to read an object from the stream. +class ObjectReader { +public: + + ObjectReader(const QByteArray& className = QByteArray(), const QMetaObject* metaObject = NULL, + const QVector& properties = QVector()); + + const QByteArray& getClassName() const { return _className; } + const QMetaObject* getMetaObject() const { return _metaObject; } + + QObject* read(Bitstream& in, QObject* object = NULL) const; + QObject* readDelta(Bitstream& in, const QObject* reference, QObject* object = NULL) const; + + bool operator==(const ObjectReader& other) const { return _className == other._className; } + bool operator!=(const ObjectReader& other) const { return _className != other._className; } + +private: + + QByteArray _className; + const QMetaObject* _metaObject; + QVector _properties; +}; + +uint qHash(const ObjectReader& objectReader, uint seed = 0); + +/// Contains the information required to read an object property from the stream and apply it. +class PropertyReader { +public: + + PropertyReader(const TypeReader& reader = TypeReader(), const QMetaProperty& property = QMetaProperty()); + + const TypeReader& getReader() const { return _reader; } + + void read(Bitstream& in, QObject* object) const; + void readDelta(Bitstream& in, QObject* object, const QObject* reference) const; + +private: + + TypeReader _reader; + QMetaProperty _property; +}; + +/// Describes a metatype field. +class MetaField { +public: + + MetaField(const QByteArray& name = QByteArray(), const TypeStreamer* streamer = NULL); + + const QByteArray& getName() const { return _name; } + const TypeStreamer* getStreamer() const { return _streamer; } + +private: + + QByteArray _name; + const TypeStreamer* _streamer; +}; + Q_DECLARE_METATYPE(const QMetaObject*) /// Macro for registering streamable meta-objects. @@ -427,12 +761,42 @@ Q_DECLARE_METATYPE(const QMetaObject*) class TypeStreamer { public: + virtual ~TypeStreamer(); + void setType(int type) { _type = type; } int getType() const { return _type; } + virtual bool equal(const QVariant& first, const QVariant& second) const = 0; + virtual void write(Bitstream& out, const QVariant& value) const = 0; virtual QVariant read(Bitstream& in) const = 0; + virtual void writeDelta(Bitstream& out, const QVariant& value, const QVariant& reference) const = 0; + virtual void readDelta(Bitstream& in, QVariant& value, const QVariant& reference) const = 0; + + virtual void writeRawDelta(Bitstream& out, const QVariant& value, const QVariant& reference) const = 0; + virtual void readRawDelta(Bitstream& in, QVariant& value, const QVariant& reference) const = 0; + + virtual const QVector& getMetaFields() const; + virtual int getFieldIndex(const QByteArray& name) const; + virtual void setField(QVariant& object, int index, const QVariant& value) const; + virtual QVariant getField(const QVariant& object, int index) const; + + virtual TypeReader::Type getReaderType() const; + + virtual const TypeStreamer* getKeyStreamer() const; + virtual const TypeStreamer* getValueStreamer() const; + + virtual void insert(QVariant& object, const QVariant& value) const; + virtual void insert(QVariant& object, const QVariant& key, const QVariant& value) const; + virtual bool remove(QVariant& object, const QVariant& key) const; + + virtual QVariant getValue(const QVariant& object, const QVariant& key) const; + + virtual void prune(QVariant& object, int size) const; + virtual QVariant getValue(const QVariant& object, int index) const; + virtual void setValue(QVariant& object, int index, const QVariant& value) const; + private: int _type; @@ -442,25 +806,95 @@ private: template class SimpleTypeStreamer : public TypeStreamer { public: + virtual bool equal(const QVariant& first, const QVariant& second) const { return first.value() == second.value(); } virtual void write(Bitstream& out, const QVariant& value) const { out << value.value(); } virtual QVariant read(Bitstream& in) const { T value; in >> value; return QVariant::fromValue(value); } + virtual void writeDelta(Bitstream& out, const QVariant& value, const QVariant& reference) const { + out.writeDelta(value.value(), reference.value()); } + virtual void readDelta(Bitstream& in, QVariant& value, const QVariant& reference) const { + in.readDelta(*static_cast(value.data()), reference.value()); } + virtual void writeRawDelta(Bitstream& out, const QVariant& value, const QVariant& reference) const { + out.writeRawDelta(value.value(), reference.value()); } + virtual void readRawDelta(Bitstream& in, QVariant& value, const QVariant& reference) const { + in.readRawDelta(*static_cast(value.data()), reference.value()); } +}; + +/// A streamer for types compiled by mtc. +template class StreamableTypeStreamer : public SimpleTypeStreamer { +public: + + virtual TypeReader::Type getReaderType() const { return TypeReader::STREAMABLE_TYPE; } + virtual const QVector& getMetaFields() const { return T::getMetaFields(); } + virtual int getFieldIndex(const QByteArray& name) const { return T::getFieldIndex(name); } + virtual void setField(QVariant& object, int index, const QVariant& value) const { + static_cast(object.data())->setField(index, value); } + virtual QVariant getField(const QVariant& object, int index) const { + return static_cast(object.constData())->getField(index); } +}; + +/// Base template for collection streamers. +template class CollectionTypeStreamer : public SimpleTypeStreamer { +}; + +/// A streamer for list types. +template class CollectionTypeStreamer > : public SimpleTypeStreamer > { +public: + + virtual TypeReader::Type getReaderType() const { return TypeReader::LIST_TYPE; } + virtual const TypeStreamer* getValueStreamer() const { return Bitstream::getTypeStreamer(qMetaTypeId()); } + virtual void insert(QVariant& object, const QVariant& value) const { + static_cast*>(object.data())->append(value.value()); } + virtual void prune(QVariant& object, int size) const { + QList* list = static_cast*>(object.data()); list->erase(list->begin() + size, list->end()); } + virtual QVariant getValue(const QVariant& object, int index) const { + return QVariant::fromValue(static_cast*>(object.constData())->at(index)); } + virtual void setValue(QVariant& object, int index, const QVariant& value) const { + static_cast*>(object.data())->replace(index, value.value()); } +}; + +/// A streamer for set types. +template class CollectionTypeStreamer > : public SimpleTypeStreamer > { +public: + + virtual TypeReader::Type getReaderType() const { return TypeReader::SET_TYPE; } + virtual const TypeStreamer* getValueStreamer() const { return Bitstream::getTypeStreamer(qMetaTypeId()); } + virtual void insert(QVariant& object, const QVariant& value) const { + static_cast*>(object.data())->insert(value.value()); } + virtual bool remove(QVariant& object, const QVariant& key) const { + return static_cast*>(object.data())->remove(key.value()); } +}; + +/// A streamer for hash types. +template class CollectionTypeStreamer > : public SimpleTypeStreamer > { +public: + + virtual TypeReader::Type getReaderType() const { return TypeReader::MAP_TYPE; } + virtual const TypeStreamer* getKeyStreamer() const { return Bitstream::getTypeStreamer(qMetaTypeId()); } + virtual const TypeStreamer* getValueStreamer() const { return Bitstream::getTypeStreamer(qMetaTypeId()); } + virtual void insert(QVariant& object, const QVariant& key, const QVariant& value) const { + static_cast*>(object.data())->insert(key.value(), value.value()); } + virtual bool remove(QVariant& object, const QVariant& key) const { + return static_cast*>(object.data())->remove(key.value()); } + virtual QVariant getValue(const QVariant& object, const QVariant& key) const { + return QVariant::fromValue(static_cast*>(object.constData())->value(key.value())); } }; /// Macro for registering simple type streamers. #define REGISTER_SIMPLE_TYPE_STREAMER(x) static int x##Streamer = \ - Bitstream::registerTypeStreamer(QMetaType::type(#x), new SimpleTypeStreamer()); + Bitstream::registerTypeStreamer(qMetaTypeId(), new SimpleTypeStreamer()); -#ifdef WIN32 -#define _Pragma __pragma -#endif +/// Macro for registering collection type streamers. +#define REGISTER_COLLECTION_TYPE_STREAMER(x) static int x##Streamer = \ + Bitstream::registerTypeStreamer(qMetaTypeId(), new CollectionTypeStreamer()); /// Declares the metatype and the streaming operators. The last lines /// ensure that the generated file will be included in the link phase. -#define STRINGIFY(x) #x #ifdef _WIN32 #define DECLARE_STREAMABLE_METATYPE(X) Q_DECLARE_METATYPE(X) \ Bitstream& operator<<(Bitstream& out, const X& obj); \ Bitstream& operator>>(Bitstream& in, X& obj); \ + template<> void Bitstream::writeRawDelta(const X& value, const X& reference); \ + template<> void Bitstream::readRawDelta(X& value, const X& reference); \ bool operator==(const X& first, const X& second); \ bool operator!=(const X& first, const X& second); \ static const int* _TypePtr##X = &X::Type; @@ -468,6 +902,8 @@ public: #define DECLARE_STREAMABLE_METATYPE(X) Q_DECLARE_METATYPE(X) \ Bitstream& operator<<(Bitstream& out, const X& obj); \ Bitstream& operator>>(Bitstream& in, X& obj); \ + template<> void Bitstream::writeRawDelta(const X& value, const X& reference); \ + template<> void Bitstream::readRawDelta(X& value, const X& reference); \ bool operator==(const X& first, const X& second); \ bool operator!=(const X& first, const X& second); \ __attribute__((unused)) static const int* _TypePtr##X = &X::Type; @@ -476,6 +912,8 @@ public: #define DECLARE_STREAMABLE_METATYPE(X) Q_DECLARE_METATYPE(X) \ Bitstream& operator<<(Bitstream& out, const X& obj); \ Bitstream& operator>>(Bitstream& in, X& obj); \ + template<> void Bitstream::writeRawDelta(const X& value, const X& reference); \ + template<> void Bitstream::readRawDelta(X& value, const X& reference); \ bool operator==(const X& first, const X& second); \ bool operator!=(const X& first, const X& second); \ static const int* _TypePtr##X = &X::Type; \ @@ -485,12 +923,19 @@ public: /// Registers a streamable type and its streamer. template int registerStreamableMetaType() { int type = qRegisterMetaType(); - Bitstream::registerTypeStreamer(type, new SimpleTypeStreamer()); + Bitstream::registerTypeStreamer(type, new StreamableTypeStreamer()); return type; } /// Flags a class as streamable (use as you would Q_OBJECT). -#define STREAMABLE public: static const int Type; private: +#define STREAMABLE public: \ + static const int Type; \ + static const QVector& getMetaFields(); \ + static int getFieldIndex(const QByteArray& name); \ + void setField(int index, const QVariant& value); \ + QVariant getField(int index) const; \ + private: \ + static QHash createFieldIndices(); /// Flags a field or base class as streaming. #define STREAM diff --git a/libraries/metavoxels/src/DatagramSequencer.cpp b/libraries/metavoxels/src/DatagramSequencer.cpp index 052f480fcf..5c605dc0f4 100644 --- a/libraries/metavoxels/src/DatagramSequencer.cpp +++ b/libraries/metavoxels/src/DatagramSequencer.cpp @@ -420,7 +420,7 @@ bool CircularBuffer::atEnd() const { } qint64 CircularBuffer::bytesAvailable() const { - return _size - _offset + QIODevice::bytesAvailable(); + return _size - _offset; } bool CircularBuffer::canReadLine() const { diff --git a/libraries/metavoxels/src/MetavoxelData.cpp b/libraries/metavoxels/src/MetavoxelData.cpp index cab5a73076..e548de46c7 100644 --- a/libraries/metavoxels/src/MetavoxelData.cpp +++ b/libraries/metavoxels/src/MetavoxelData.cpp @@ -85,7 +85,7 @@ void MetavoxelData::guide(MetavoxelVisitor& visitor) { const QVector& inputs = visitor.getInputs(); const QVector& outputs = visitor.getOutputs(); MetavoxelVisitation firstVisitation = { NULL, visitor, QVector(inputs.size() + 1), - QVector(outputs.size()), { getMinimum(), _size, + QVector(outputs.size()), { NULL, getMinimum(), _size, QVector(inputs.size() + 1), QVector(outputs.size()) } }; for (int i = 0; i < inputs.size(); i++) { MetavoxelNode* node = _roots.value(inputs.at(i)); @@ -177,7 +177,7 @@ template int SpannerUpdateVisitor::visit(MetavoxelIn void MetavoxelData::insert(const AttributePointer& attribute, const SharedObjectPointer& object) { Spanner* spanner = static_cast(object.data()); - insert(attribute, spanner->getBounds(), spanner->getGranularity(), object); + insert(attribute, spanner->getBounds(), spanner->getPlacementGranularity(), object); } void MetavoxelData::insert(const AttributePointer& attribute, const Box& bounds, @@ -192,7 +192,7 @@ void MetavoxelData::insert(const AttributePointer& attribute, const Box& bounds, void MetavoxelData::remove(const AttributePointer& attribute, const SharedObjectPointer& object) { Spanner* spanner = static_cast(object.data()); - remove(attribute, spanner->getBounds(), spanner->getGranularity(), object); + remove(attribute, spanner->getBounds(), spanner->getPlacementGranularity(), object); } void MetavoxelData::remove(const AttributePointer& attribute, const Box& bounds, @@ -203,7 +203,7 @@ void MetavoxelData::remove(const AttributePointer& attribute, const Box& bounds, void MetavoxelData::toggle(const AttributePointer& attribute, const SharedObjectPointer& object) { Spanner* spanner = static_cast(object.data()); - toggle(attribute, spanner->getBounds(), spanner->getGranularity(), object); + toggle(attribute, spanner->getBounds(), spanner->getPlacementGranularity(), object); } void MetavoxelData::toggle(const AttributePointer& attribute, const Box& bounds, @@ -212,6 +212,67 @@ void MetavoxelData::toggle(const AttributePointer& attribute, const Box& bounds, guide(visitor); } +void MetavoxelData::replace(const AttributePointer& attribute, const SharedObjectPointer& oldObject, + const SharedObjectPointer& newObject) { + Spanner* spanner = static_cast(oldObject.data()); + replace(attribute, spanner->getBounds(), spanner->getPlacementGranularity(), oldObject, newObject); +} + +class SpannerReplaceVisitor : public MetavoxelVisitor { +public: + + SpannerReplaceVisitor(const AttributePointer& attribute, const Box& bounds, + float granularity, const SharedObjectPointer& oldObject, const SharedObjectPointer& newObject); + + virtual int visit(MetavoxelInfo& info); + +private: + + const AttributePointer& _attribute; + const Box& _bounds; + float _longestSide; + const SharedObjectPointer& _oldObject; + const SharedObjectPointer& _newObject; +}; + +SpannerReplaceVisitor::SpannerReplaceVisitor(const AttributePointer& attribute, const Box& bounds, float granularity, + const SharedObjectPointer& oldObject, const SharedObjectPointer& newObject) : + MetavoxelVisitor(QVector() << attribute, QVector() << attribute), + _attribute(attribute), + _bounds(bounds), + _longestSide(qMax(bounds.getLongestSide(), granularity)), + _oldObject(oldObject), + _newObject(newObject) { +} + +int SpannerReplaceVisitor::visit(MetavoxelInfo& info) { + if (!info.getBounds().intersects(_bounds)) { + return STOP_RECURSION; + } + if (info.size > _longestSide) { + return DEFAULT_ORDER; + } + SharedObjectSet set = info.inputValues.at(0).getInlineValue(); + if (set.remove(_oldObject)) { + set.insert(_newObject); + } + info.outputValues[0] = AttributeValue(_attribute, encodeInline(set)); + return STOP_RECURSION; +} + +void MetavoxelData::replace(const AttributePointer& attribute, const Box& bounds, float granularity, + const SharedObjectPointer& oldObject, const SharedObjectPointer& newObject) { + Spanner* newSpanner = static_cast(newObject.data()); + if (bounds != newSpanner->getBounds() || granularity != newSpanner->getPlacementGranularity()) { + // if the bounds have changed, we must remove and reinsert + remove(attribute, bounds, granularity, oldObject); + insert(attribute, newSpanner->getBounds(), newSpanner->getPlacementGranularity(), newObject); + return; + } + SpannerReplaceVisitor visitor(attribute, bounds, granularity, oldObject, newObject); + guide(visitor); +} + void MetavoxelData::clear(const AttributePointer& attribute) { MetavoxelNode* node = _roots.take(attribute); if (node) { @@ -239,7 +300,7 @@ private: FirstRaySpannerIntersectionVisitor::FirstRaySpannerIntersectionVisitor( const glm::vec3& origin, const glm::vec3& direction, const AttributePointer& attribute, const MetavoxelLOD& lod) : RaySpannerIntersectionVisitor(origin, direction, QVector() << attribute, - QVector(), QVector(), lod), + QVector(), QVector(), QVector(), lod), _spanner(NULL) { } @@ -485,14 +546,26 @@ void MetavoxelStreamState::setMinimum(const glm::vec3& lastMinimum, int index) { minimum = getNextMinimum(lastMinimum, size, index); } -MetavoxelNode::MetavoxelNode(const AttributeValue& attributeValue) : _referenceCount(1) { +MetavoxelNode::MetavoxelNode(const AttributeValue& attributeValue, const MetavoxelNode* copyChildren) : + _referenceCount(1) { + _attributeValue = attributeValue.copy(); - for (int i = 0; i < CHILD_COUNT; i++) { - _children[i] = NULL; + if (copyChildren) { + for (int i = 0; i < CHILD_COUNT; i++) { + if ((_children[i] = copyChildren->_children[i])) { + _children[i]->incrementReferenceCount(); + } + } + } else { + for (int i = 0; i < CHILD_COUNT; i++) { + _children[i] = NULL; + } } } -MetavoxelNode::MetavoxelNode(const AttributePointer& attribute, const MetavoxelNode* copy) : _referenceCount(1) { +MetavoxelNode::MetavoxelNode(const AttributePointer& attribute, const MetavoxelNode* copy) : + _referenceCount(1) { + _attributeValue = attribute->create(copy->_attributeValue); for (int i = 0; i < CHILD_COUNT; i++) { if ((_children[i] = copy->_children[i])) { @@ -511,14 +584,17 @@ AttributeValue MetavoxelNode::getAttributeValue(const AttributePointer& attribut return AttributeValue(attribute, _attributeValue); } -void MetavoxelNode::mergeChildren(const AttributePointer& attribute) { +void MetavoxelNode::mergeChildren(const AttributePointer& attribute, bool postRead) { + if (isLeaf()) { + return; + } 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 (attribute->merge(_attributeValue, childValues) && allLeaves) { + if (attribute->merge(_attributeValue, childValues, postRead) && allLeaves) { clearChildren(attribute); } } @@ -550,7 +626,7 @@ void MetavoxelNode::read(MetavoxelStreamState& state) { _children[i] = new MetavoxelNode(state.attribute); _children[i]->read(nextState); } - mergeChildren(state.attribute); + mergeChildren(state.attribute, true); } } @@ -608,7 +684,7 @@ void MetavoxelNode::readDelta(const MetavoxelNode& reference, MetavoxelStreamSta } } } - mergeChildren(state.attribute); + mergeChildren(state.attribute, true); } } @@ -863,10 +939,11 @@ void MetavoxelVisitor::prepare() { // nothing by default } -SpannerVisitor::SpannerVisitor(const QVector& spannerInputs, const QVector& inputs, - const QVector& outputs, const MetavoxelLOD& lod) : - MetavoxelVisitor(inputs + spannerInputs, outputs, lod), - _spannerInputCount(spannerInputs.size()) { +SpannerVisitor::SpannerVisitor(const QVector& spannerInputs, const QVector& spannerMasks, + const QVector& inputs, const QVector& outputs, const MetavoxelLOD& lod) : + MetavoxelVisitor(inputs + spannerInputs + spannerMasks, outputs, lod), + _spannerInputCount(spannerInputs.size()), + _spannerMaskCount(spannerMasks.size()) { } void SpannerVisitor::prepare() { @@ -874,17 +951,34 @@ void SpannerVisitor::prepare() { } int SpannerVisitor::visit(MetavoxelInfo& info) { - for (int i = _inputs.size() - _spannerInputCount; i < _inputs.size(); i++) { + for (int end = _inputs.size() - _spannerMaskCount, i = end - _spannerInputCount, j = end; i < end; i++, j++) { foreach (const SharedObjectPointer& object, info.inputValues.at(i).getInlineValue()) { Spanner* spanner = static_cast(object.data()); - if (spanner->testAndSetVisited()) { - if (!visit(spanner)) { - return SHORT_CIRCUIT; - } + if (!(spanner->isMasked() && j < _inputs.size()) && spanner->testAndSetVisited() && + !visit(spanner, glm::vec3(), 0.0f)) { + return SHORT_CIRCUIT; } } } - return info.isLeaf ? STOP_RECURSION : DEFAULT_ORDER; + if (!info.isLeaf) { + return DEFAULT_ORDER; + } + for (int i = _inputs.size() - _spannerMaskCount; i < _inputs.size(); i++) { + float maskValue = info.inputValues.at(i).getInlineValue(); + if (maskValue < 0.5f) { + const MetavoxelInfo* nextInfo = &info; + do { + foreach (const SharedObjectPointer& object, nextInfo->inputValues.at( + i - _spannerInputCount).getInlineValue()) { + Spanner* spanner = static_cast(object.data()); + if (spanner->isMasked() && !visit(spanner, info.minimum, info.size)) { + return SHORT_CIRCUIT; + } + } + } while ((nextInfo = nextInfo->parentInfo)); + } + } + return STOP_RECURSION; } RayIntersectionVisitor::RayIntersectionVisitor(const glm::vec3& origin, const glm::vec3& direction, @@ -904,10 +998,11 @@ int RayIntersectionVisitor::visit(MetavoxelInfo& info) { } RaySpannerIntersectionVisitor::RaySpannerIntersectionVisitor(const glm::vec3& origin, const glm::vec3& direction, - const QVector& spannerInputs, const QVector& inputs, - const QVector& outputs, const MetavoxelLOD& lod) : - RayIntersectionVisitor(origin, direction, inputs + spannerInputs, outputs, lod), - _spannerInputCount(spannerInputs.size()) { + const QVector& spannerInputs, const QVector& spannerMasks, + const QVector& inputs, const QVector& outputs, const MetavoxelLOD& lod) : + RayIntersectionVisitor(origin, direction, inputs + spannerInputs + spannerMasks, outputs, lod), + _spannerInputCount(spannerInputs.size()), + _spannerMaskCount(spannerMasks.size()) { } void RaySpannerIntersectionVisitor::prepare() { @@ -926,12 +1021,12 @@ bool operator<(const SpannerDistance& first, const SpannerDistance& second) { int RaySpannerIntersectionVisitor::visit(MetavoxelInfo& info, float distance) { QVarLengthArray spannerDistances; - for (int i = _inputs.size() - _spannerInputCount; i < _inputs.size(); i++) { + for (int end = _inputs.size() - _spannerMaskCount, i = end - _spannerInputCount, j = end; i < end; i++, j++) { foreach (const SharedObjectPointer& object, info.inputValues.at(i).getInlineValue()) { Spanner* spanner = static_cast(object.data()); - if (spanner->testAndSetVisited()) { + if (!(spanner->isMasked() && j < _inputs.size()) && spanner->testAndSetVisited()) { SpannerDistance spannerDistance = { spanner }; - if (spanner->findRayIntersection(_origin, _direction, spannerDistance.distance)) { + if (spanner->findRayIntersection(_origin, _direction, glm::vec3(), 0.0f, spannerDistance.distance)) { spannerDistances.append(spannerDistance); } } @@ -943,7 +1038,36 @@ int RaySpannerIntersectionVisitor::visit(MetavoxelInfo& info, float distance) { } } } - return info.isLeaf ? STOP_RECURSION : _order; + if (!info.isLeaf) { + return _order; + } + for (int i = _inputs.size() - _spannerMaskCount; i < _inputs.size(); i++) { + float maskValue = info.inputValues.at(i).getInlineValue(); + if (maskValue < 0.5f) { + const MetavoxelInfo* nextInfo = &info; + do { + foreach (const SharedObjectPointer& object, nextInfo->inputValues.at( + i - _spannerInputCount).getInlineValue()) { + Spanner* spanner = static_cast(object.data()); + if (spanner->isMasked()) { + SpannerDistance spannerDistance = { spanner }; + if (spanner->findRayIntersection(_origin, _direction, + info.minimum, info.size, spannerDistance.distance)) { + spannerDistances.append(spannerDistance); + } + } + } + } while ((nextInfo = nextInfo->parentInfo)); + + qStableSort(spannerDistances); + foreach (const SpannerDistance& spannerDistance, spannerDistances) { + if (!visitSpanner(spannerDistance.spanner, spannerDistance.distance)) { + return SHORT_CIRCUIT; + } + } + } + } + return STOP_RECURSION; } DefaultMetavoxelGuide::DefaultMetavoxelGuide() { @@ -953,8 +1077,8 @@ bool DefaultMetavoxelGuide::guide(MetavoxelVisitation& visitation) { // save the core of the LOD calculation; we'll reuse it to determine whether to subdivide each attribute float lodBase = glm::distance(visitation.visitor.getLOD().position, visitation.info.getCenter()) * visitation.visitor.getLOD().threshold; - visitation.info.isLeaf = (visitation.info.size < lodBase * visitation.visitor.getMinimumLODThresholdMultiplier()) || - visitation.allInputNodesLeaves(); + visitation.info.isLODLeaf = (visitation.info.size < lodBase * visitation.visitor.getMinimumLODThresholdMultiplier()); + visitation.info.isLeaf = visitation.info.isLODLeaf || visitation.allInputNodesLeaves(); int encodedOrder = visitation.visitor.visit(visitation.info); if (encodedOrder == MetavoxelVisitor::SHORT_CIRCUIT) { return false; @@ -969,7 +1093,7 @@ bool DefaultMetavoxelGuide::guide(MetavoxelVisitation& visitation) { // "set" to same value; disregard value = AttributeValue(); } else { - node = new MetavoxelNode(value); + node = value.getAttribute()->createMetavoxelNode(value, node); } } if (encodedOrder == MetavoxelVisitor::STOP_RECURSION) { @@ -977,7 +1101,7 @@ bool DefaultMetavoxelGuide::guide(MetavoxelVisitation& visitation) { } MetavoxelVisitation nextVisitation = { &visitation, visitation.visitor, QVector(visitation.inputNodes.size()), QVector(visitation.outputNodes.size()), - { glm::vec3(), visitation.info.size * 0.5f, QVector(visitation.inputNodes.size()), + { &visitation.info, glm::vec3(), visitation.info.size * 0.5f, QVector(visitation.inputNodes.size()), QVector(visitation.outputNodes.size()) } }; for (int i = 0; i < MetavoxelNode::CHILD_COUNT; i++) { // the encoded order tells us the child indices for each iteration @@ -991,7 +1115,7 @@ bool DefaultMetavoxelGuide::guide(MetavoxelVisitation& visitation) { MetavoxelNode* child = (node && (visitation.info.size >= lodBase * parentValue.getAttribute()->getLODThresholdMultiplier())) ? node->getChild(index) : NULL; nextVisitation.info.inputValues[j] = ((nextVisitation.inputNodes[j] = child)) ? - child->getAttributeValue(parentValue.getAttribute()) : parentValue; + child->getAttributeValue(parentValue.getAttribute()) : parentValue.getAttribute()->inherit(parentValue); } for (int j = 0; j < visitation.outputNodes.size(); j++) { MetavoxelNode* node = visitation.outputNodes.at(j); @@ -1019,7 +1143,7 @@ bool DefaultMetavoxelGuide::guide(MetavoxelVisitation& visitation) { node = new MetavoxelNode(value.getAttribute(), node); } else { // create leaf with inherited value - node = new MetavoxelNode(visitation.getInheritedOutputValue(j)); + node = new MetavoxelNode(value.getAttribute()->inherit(visitation.getInheritedOutputValue(j))); } } MetavoxelNode* node = visitation.outputNodes.at(j); @@ -1028,7 +1152,7 @@ bool DefaultMetavoxelGuide::guide(MetavoxelVisitation& visitation) { child->decrementReferenceCount(value.getAttribute()); } else { // it's a leaf; we need to split it up - AttributeValue nodeValue = node->getAttributeValue(value.getAttribute()); + AttributeValue nodeValue = value.getAttribute()->inherit(node->getAttributeValue(value.getAttribute())); for (int k = 1; k < MetavoxelNode::CHILD_COUNT; k++) { node->setChild((index + k) % MetavoxelNode::CHILD_COUNT, new MetavoxelNode(nodeValue)); } @@ -1095,7 +1219,7 @@ QScriptValue ScriptedMetavoxelGuide::visit(QScriptContext* context, QScriptEngin 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()), + NULL, glm::vec3(minimum.property(0).toNumber(), minimum.property(1).toNumber(), minimum.property(2).toNumber()), (float)infoValue.property(guide->_sizeHandle).toNumber(), guide->_visitation->info.inputValues, guide->_visitation->info.outputValues, infoValue.property(guide->_isLeafHandle).toBool() }; @@ -1202,11 +1326,14 @@ AttributeValue MetavoxelVisitation::getInheritedOutputValue(int index) const { return AttributeValue(visitor.getOutputs().at(index)); } -const float DEFAULT_GRANULARITY = 0.01f; +const float DEFAULT_PLACEMENT_GRANULARITY = 0.01f; +const float DEFAULT_VOXELIZATION_GRANULARITY = powf(2.0f, -3.0f); Spanner::Spanner() : _renderer(NULL), - _granularity(DEFAULT_GRANULARITY), + _placementGranularity(DEFAULT_PLACEMENT_GRANULARITY), + _voxelizationGranularity(DEFAULT_VOXELIZATION_GRANULARITY), + _masked(false), _lastVisit(0) { } @@ -1223,7 +1350,16 @@ const QVector& Spanner::getAttributes() const { return emptyVector; } -bool Spanner::getAttributeValues(MetavoxelInfo& info) const { +const QVector& Spanner::getVoxelizedAttributes() const { + static QVector emptyVector; + return emptyVector; +} + +bool Spanner::getAttributeValues(MetavoxelInfo& info, bool force) const { + return false; +} + +bool Spanner::blendAttributeValues(MetavoxelInfo& info, bool force) const { return false; } @@ -1250,7 +1386,8 @@ SpannerRenderer* Spanner::getRenderer() { return _renderer; } -bool Spanner::findRayIntersection(const glm::vec3& origin, const glm::vec3& direction, float& distance) const { +bool Spanner::findRayIntersection(const glm::vec3& origin, const glm::vec3& direction, + const glm::vec3& clipMinimum, float clipSize, float& distance) const { return _bounds.findRayIntersection(origin, direction, distance); } @@ -1271,11 +1408,12 @@ void SpannerRenderer::simulate(float deltaTime) { // nothing by default } -void SpannerRenderer::render(float alpha) { +void SpannerRenderer::render(float alpha, const glm::vec3& clipMinimum, float clipSize) { // nothing by default } -bool SpannerRenderer::findRayIntersection(const glm::vec3& origin, const glm::vec3& direction, float& distance) const { +bool SpannerRenderer::findRayIntersection(const glm::vec3& origin, const glm::vec3& direction, + const glm::vec3& clipMinimum, float clipSize, float& distance) const { return false; } @@ -1320,10 +1458,17 @@ const QVector& Sphere::getAttributes() const { return attributes; } -bool Sphere::getAttributeValues(MetavoxelInfo& info) const { +const QVector& Sphere::getVoxelizedAttributes() const { + static QVector attributes = QVector() << + AttributeRegistry::getInstance()->getSpannerColorAttribute() << + AttributeRegistry::getInstance()->getSpannerNormalAttribute(); + return attributes; +} + +bool Sphere::getAttributeValues(MetavoxelInfo& info, bool force) const { // bounds check Box bounds = info.getBounds(); - if (!getBounds().intersects(bounds)) { + if (!(force || getBounds().intersects(bounds))) { return false; } // count the points inside the sphere @@ -1336,22 +1481,63 @@ bool Sphere::getAttributeValues(MetavoxelInfo& info) const { if (pointsWithin == Box::VERTEX_COUNT) { // entirely contained info.outputValues[0] = AttributeValue(getAttributes().at(0), encodeInline(_color.rgba())); - getNormal(info); + info.outputValues[1] = getNormal(info, _color.alpha()); return false; } - if (info.size <= getGranularity()) { + if (force || info.size <= getVoxelizationGranularity()) { // best guess if (pointsWithin > 0) { + int alpha = _color.alpha() * pointsWithin / Box::VERTEX_COUNT; info.outputValues[0] = AttributeValue(getAttributes().at(0), encodeInline(qRgba( - _color.red(), _color.green(), _color.blue(), _color.alpha() * pointsWithin / Box::VERTEX_COUNT))); - getNormal(info); + _color.red(), _color.green(), _color.blue(), alpha))); + info.outputValues[1] = getNormal(info, alpha); } return false; } return true; } -bool Sphere::findRayIntersection(const glm::vec3& origin, const glm::vec3& direction, float& distance) const { +bool Sphere::blendAttributeValues(MetavoxelInfo& info, bool force) const { + // bounds check + Box bounds = info.getBounds(); + if (!(force || getBounds().intersects(bounds))) { + return false; + } + // count the points inside the sphere + int pointsWithin = 0; + for (int i = 0; i < Box::VERTEX_COUNT; i++) { + if (glm::distance(bounds.getVertex(i), getTranslation()) <= getScale()) { + pointsWithin++; + } + } + if (pointsWithin == Box::VERTEX_COUNT) { + // entirely contained + info.outputValues[0] = AttributeValue(getAttributes().at(0), encodeInline(_color.rgba())); + info.outputValues[1] = getNormal(info, _color.alpha()); + return false; + } + if (force || info.size <= getVoxelizationGranularity()) { + // best guess + if (pointsWithin > 0) { + const AttributeValue& oldColor = info.outputValues.at(0).getAttribute() ? + info.outputValues.at(0) : info.inputValues.at(0); + const AttributeValue& oldNormal = info.outputValues.at(1).getAttribute() ? + info.outputValues.at(1) : info.inputValues.at(1); + int oldAlpha = qAlpha(oldColor.getInlineValue()); + int newAlpha = _color.alpha() * pointsWithin / Box::VERTEX_COUNT; + float combinedAlpha = (float)newAlpha / (oldAlpha + newAlpha); + int baseAlpha = _color.alpha() * pointsWithin / Box::VERTEX_COUNT; + info.outputValues[0].mix(oldColor, AttributeValue(getAttributes().at(0), + encodeInline(qRgba(_color.red(), _color.green(), _color.blue(), baseAlpha))), combinedAlpha); + info.outputValues[1].mix(oldNormal, getNormal(info, baseAlpha), combinedAlpha); + } + return false; + } + return true; +} + +bool Sphere::findRayIntersection(const glm::vec3& origin, const glm::vec3& direction, + const glm::vec3& clipMinimum, float clipSize, float& distance) const { return findRaySphereIntersection(origin, direction, getTranslation(), getScale(), distance); } @@ -1364,22 +1550,21 @@ void Sphere::updateBounds() { setBounds(Box(getTranslation() - extent, getTranslation() + extent)); } -void Sphere::getNormal(MetavoxelInfo& info) const { +AttributeValue Sphere::getNormal(MetavoxelInfo& info, int alpha) const { glm::vec3 normal = info.getCenter() - getTranslation(); float length = glm::length(normal); QRgb color; - if (length > EPSILON) { + if (alpha != 0 && length > EPSILON) { const float NORMAL_SCALE = 127.0f; float scale = NORMAL_SCALE / length; const int BYTE_MASK = 0xFF; - color = qRgb((int)(normal.x * scale) & BYTE_MASK, (int)(normal.y * scale) & BYTE_MASK, - (int)(normal.z * scale) & BYTE_MASK); + color = qRgba((int)(normal.x * scale) & BYTE_MASK, (int)(normal.y * scale) & BYTE_MASK, + (int)(normal.z * scale) & BYTE_MASK, alpha); } else { - const QRgb DEFAULT_NORMAL = 0x007F00; - color = DEFAULT_NORMAL; + color = QRgb(); } - info.outputValues[1] = AttributeValue(getAttributes().at(1), encodeInline(color)); + return AttributeValue(getAttributes().at(1), encodeInline(color)); } StaticModel::StaticModel() { @@ -1391,10 +1576,11 @@ void StaticModel::setURL(const QUrl& url) { } } -bool StaticModel::findRayIntersection(const glm::vec3& origin, const glm::vec3& direction, float& distance) const { +bool StaticModel::findRayIntersection(const glm::vec3& origin, const glm::vec3& direction, + const glm::vec3& clipMinimum, float clipSize, float& distance) const { // delegate to renderer, if we have one - return _renderer ? _renderer->findRayIntersection(origin, direction, distance) : - Spanner::findRayIntersection(origin, direction, distance); + return _renderer ? _renderer->findRayIntersection(origin, direction, clipMinimum, clipSize, distance) : + Spanner::findRayIntersection(origin, direction, clipMinimum, clipSize, distance); } QByteArray StaticModel::getRendererClassName() const { diff --git a/libraries/metavoxels/src/MetavoxelData.h b/libraries/metavoxels/src/MetavoxelData.h index 9d642f8f34..fa408aafb7 100644 --- a/libraries/metavoxels/src/MetavoxelData.h +++ b/libraries/metavoxels/src/MetavoxelData.h @@ -79,6 +79,11 @@ public: void toggle(const AttributePointer& attribute, const SharedObjectPointer& object); void toggle(const AttributePointer& attribute, const Box& bounds, float granularity, const SharedObjectPointer& object); + void replace(const AttributePointer& attribute, const SharedObjectPointer& oldObject, + const SharedObjectPointer& newObject); + void replace(const AttributePointer& attribute, const Box& bounds, float granularity, const SharedObjectPointer& oldObject, + const SharedObjectPointer& newObject); + void clear(const AttributePointer& attribute); /// Convenience function that finds the first spanner intersecting the provided ray. @@ -132,7 +137,7 @@ public: static const int CHILD_COUNT = 8; - MetavoxelNode(const AttributeValue& attributeValue); + MetavoxelNode(const AttributeValue& attributeValue, const MetavoxelNode* copyChildren = NULL); MetavoxelNode(const AttributePointer& attribute, const MetavoxelNode* copy); void setAttributeValue(const AttributeValue& attributeValue); @@ -140,7 +145,7 @@ public: AttributeValue getAttributeValue(const AttributePointer& attribute) const; void* getAttributeValue() const { return _attributeValue; } - void mergeChildren(const AttributePointer& attribute); + void mergeChildren(const AttributePointer& attribute, bool postRead = false); MetavoxelNode* getChild(int index) const { return _children[index]; } void setChild(int index, MetavoxelNode* child) { _children[index] = child; } @@ -185,10 +190,12 @@ private: class MetavoxelInfo { public: + MetavoxelInfo* parentInfo; glm::vec3 minimum; ///< the minimum extent of the area covered by the voxel float size; ///< the size of the voxel in all dimensions QVector inputValues; QVector outputValues; + bool isLODLeaf; bool isLeaf; Box getBounds() const { return Box(minimum, minimum + glm::vec3(size, size, size)); } @@ -253,13 +260,15 @@ class SpannerVisitor : public MetavoxelVisitor { public: SpannerVisitor(const QVector& spannerInputs, + const QVector& spannerMasks = QVector(), const QVector& inputs = QVector(), const QVector& outputs = QVector(), const MetavoxelLOD& lod = MetavoxelLOD()); - /// Visits a spanner. + /// Visits a spanner (or part thereof). + /// \param clipSize the size of the clip volume, or zero if unclipped /// \return true to continue, false to short-circuit the tour - virtual bool visit(Spanner* spanner) = 0; + virtual bool visit(Spanner* spanner, const glm::vec3& clipMinimum, float clipSize) = 0; virtual void prepare(); virtual int visit(MetavoxelInfo& info); @@ -267,6 +276,7 @@ public: protected: int _spannerInputCount; + int _spannerMaskCount; }; /// Base class for ray intersection visitors. @@ -296,11 +306,12 @@ public: RaySpannerIntersectionVisitor(const glm::vec3& origin, const glm::vec3& direction, const QVector& spannerInputs, + const QVector& spannerMasks = QVector(), const QVector& inputs = QVector(), const QVector& outputs = QVector(), const MetavoxelLOD& lod = MetavoxelLOD()); - /// Visits a spanner that the ray intersects. + /// Visits a spannerthat the ray intersects. /// \return true to continue, false to short-circuit the tour virtual bool visitSpanner(Spanner* spanner, float distance) = 0; @@ -310,6 +321,7 @@ public: protected: int _spannerInputCount; + int _spannerMaskCount; }; /// Interface for objects that guide metavoxel visitors. @@ -408,8 +420,10 @@ public: class Spanner : public SharedObject { Q_OBJECT Q_PROPERTY(Box bounds MEMBER _bounds WRITE setBounds NOTIFY boundsChanged DESIGNABLE false) - Q_PROPERTY(float granularity MEMBER _granularity DESIGNABLE false) - + Q_PROPERTY(float placementGranularity MEMBER _placementGranularity DESIGNABLE false) + Q_PROPERTY(float voxelizationGranularity MEMBER _voxelizationGranularity DESIGNABLE false) + Q_PROPERTY(float masked MEMBER _masked DESIGNABLE false) + public: /// Increments the value of the global visit counter. @@ -420,15 +434,29 @@ public: void setBounds(const Box& bounds); const Box& getBounds() const { return _bounds; } - void setGranularity(float granularity) { _granularity = granularity; } - float getGranularity() const { return _granularity; } + void setPlacementGranularity(float granularity) { _placementGranularity = granularity; } + float getPlacementGranularity() const { return _placementGranularity; } + + void setVoxelizationGranularity(float granularity) { _voxelizationGranularity = granularity; } + float getVoxelizationGranularity() const { return _voxelizationGranularity; } + + void setMasked(bool masked) { _masked = masked; } + bool isMasked() const { return _masked; } /// Returns a reference to the list of attributes associated with this spanner. virtual const QVector& getAttributes() const; + /// Returns a reference to the list of corresponding attributes that we voxelize the spanner into. + virtual const QVector& getVoxelizedAttributes() const; + /// Sets the attribute values associated with this spanner in the supplied info. /// \return true to recurse, false to stop - virtual bool getAttributeValues(MetavoxelInfo& info) const; + virtual bool getAttributeValues(MetavoxelInfo& info, bool force = false) const; + + /// Blends the attribute values associated with this spanner into the supplied info. + /// \param force if true, blend even if we would normally subdivide + /// \return true to recurse, false to stop + virtual bool blendAttributeValues(MetavoxelInfo& info, bool force = false) const; /// Checks whether we've visited this object on the current traversal. If we have, returns false. /// If we haven't, sets the last visit identifier and returns true. @@ -438,7 +466,9 @@ public: SpannerRenderer* getRenderer(); /// Finds the intersection between the described ray and this spanner. - virtual bool findRayIntersection(const glm::vec3& origin, const glm::vec3& direction, float& distance) const; + /// \param clipSize the size of the clip region, or zero if unclipped + virtual bool findRayIntersection(const glm::vec3& origin, const glm::vec3& direction, + const glm::vec3& clipMinimum, float clipSize, float& distance) const; signals: @@ -455,7 +485,9 @@ protected: private: Box _bounds; - float _granularity; + float _placementGranularity; + float _voxelizationGranularity; + bool _masked; int _lastVisit; ///< the identifier of the last visit static int _visit; ///< the global visit counter @@ -471,8 +503,9 @@ public: virtual void init(Spanner* spanner); virtual void simulate(float deltaTime); - virtual void render(float alpha); - virtual bool findRayIntersection(const glm::vec3& origin, const glm::vec3& direction, float& distance) const; + virtual void render(float alpha, const glm::vec3& clipMinimum, float clipSize); + virtual bool findRayIntersection(const glm::vec3& origin, const glm::vec3& direction, + const glm::vec3& clipMinimum, float clipSize, float& distance) const; }; /// An object with a 3D transform. @@ -521,8 +554,11 @@ public: const QColor& getColor() const { return _color; } virtual const QVector& getAttributes() const; - virtual bool getAttributeValues(MetavoxelInfo& info) const; - virtual bool findRayIntersection(const glm::vec3& origin, const glm::vec3& direction, float& distance) const; + virtual const QVector& getVoxelizedAttributes() const; + virtual bool getAttributeValues(MetavoxelInfo& info, bool force = false) const; + virtual bool blendAttributeValues(MetavoxelInfo& info, bool force = false) const; + virtual bool findRayIntersection(const glm::vec3& origin, const glm::vec3& direction, + const glm::vec3& clipMinimum, float clipSize, float& distance) const; signals: @@ -538,7 +574,7 @@ private slots: private: - void getNormal(MetavoxelInfo& info) const; + AttributeValue getNormal(MetavoxelInfo& info, int alpha) const; QColor _color; }; @@ -555,7 +591,8 @@ public: void setURL(const QUrl& url); const QUrl& getURL() const { return _url; } - virtual bool findRayIntersection(const glm::vec3& origin, const glm::vec3& direction, float& distance) const; + virtual bool findRayIntersection(const glm::vec3& origin, const glm::vec3& direction, + const glm::vec3& clipMinimum, float clipSize,float& distance) const; signals: diff --git a/libraries/metavoxels/src/MetavoxelMessages.cpp b/libraries/metavoxels/src/MetavoxelMessages.cpp index ca37746780..ce0d01ccf2 100644 --- a/libraries/metavoxels/src/MetavoxelMessages.cpp +++ b/libraries/metavoxels/src/MetavoxelMessages.cpp @@ -32,7 +32,8 @@ private: }; BoxSetEditVisitor::BoxSetEditVisitor(const BoxSetEdit& edit) : - MetavoxelVisitor(QVector(), QVector() << edit.value.getAttribute()), + MetavoxelVisitor(QVector(), QVector() << edit.value.getAttribute() << + AttributeRegistry::getInstance()->getSpannerMaskAttribute()), _edit(edit) { } @@ -47,25 +48,68 @@ int BoxSetEditVisitor::visit(MetavoxelInfo& info) { float volume = (size.x * size.y * size.z) / (info.size * info.size * info.size); if (volume >= 1.0f) { info.outputValues[0] = _edit.value; + info.outputValues[1] = AttributeValue(_outputs.at(1), encodeInline(1.0f)); return STOP_RECURSION; // entirely contained } if (info.size <= _edit.granularity) { if (volume >= 0.5f) { info.outputValues[0] = _edit.value; + info.outputValues[1] = AttributeValue(_outputs.at(1), encodeInline(1.0f)); } return STOP_RECURSION; // reached granularity limit; take best guess } return DEFAULT_ORDER; // subdivide } +class GatherUnmaskedSpannersVisitor : public SpannerVisitor { +public: + + GatherUnmaskedSpannersVisitor(const Box& bounds); + + const QList& getUnmaskedSpanners() const { return _unmaskedSpanners; } + + virtual bool visit(Spanner* spanner, const glm::vec3& clipMinimum, float clipSize); + +private: + + Box _bounds; + QList _unmaskedSpanners; +}; + +GatherUnmaskedSpannersVisitor::GatherUnmaskedSpannersVisitor(const Box& bounds) : + SpannerVisitor(QVector() << AttributeRegistry::getInstance()->getSpannersAttribute()), + _bounds(bounds) { +} + +bool GatherUnmaskedSpannersVisitor::visit(Spanner* spanner, const glm::vec3& clipMinimum, float clipSize) { + if (!spanner->isMasked() && spanner->getBounds().intersects(_bounds)) { + _unmaskedSpanners.append(spanner); + } + return true; +} + +static void setIntersectingMasked(const Box& bounds, MetavoxelData& data) { + GatherUnmaskedSpannersVisitor visitor(bounds); + data.guide(visitor); + + foreach (const SharedObjectPointer& object, visitor.getUnmaskedSpanners()) { + Spanner* newSpanner = static_cast(object->clone(true)); + newSpanner->setMasked(true); + data.replace(AttributeRegistry::getInstance()->getSpannersAttribute(), object, newSpanner); + } +} + void BoxSetEdit::apply(MetavoxelData& data, const WeakSharedObjectHash& objects) const { // expand to fit the entire edit while (!data.getBounds().contains(region)) { data.expand(); } - BoxSetEditVisitor visitor(*this); - data.guide(visitor); + BoxSetEditVisitor setVisitor(*this); + data.guide(setVisitor); + + // flip the mask flag of all intersecting spanners + setIntersectingMasked(region, data); } GlobalSetEdit::GlobalSetEdit(const OwnedAttributeValue& value) : @@ -104,8 +148,56 @@ InsertSpannerEdit::InsertSpannerEdit(const AttributePointer& attribute, const Sh spanner(spanner) { } +class UpdateSpannerVisitor : public MetavoxelVisitor { +public: + + UpdateSpannerVisitor(const QVector& attributes, Spanner* spanner); + + virtual int visit(MetavoxelInfo& info); + +private: + + Spanner* _spanner; + float _voxelizationSize; + int _steps; +}; + +UpdateSpannerVisitor::UpdateSpannerVisitor(const QVector& attributes, Spanner* spanner) : + MetavoxelVisitor(QVector() << attributes << AttributeRegistry::getInstance()->getSpannersAttribute(), + attributes), + _spanner(spanner), + _voxelizationSize(qMax(spanner->getBounds().getLongestSide(), spanner->getPlacementGranularity()) * 2.0f / + AttributeRegistry::getInstance()->getSpannersAttribute()->getLODThresholdMultiplier()), + _steps(glm::round(logf(AttributeRegistry::getInstance()->getSpannersAttribute()->getLODThresholdMultiplier()) / + logf(2.0f) - 2.0f)) { +} + +int UpdateSpannerVisitor::visit(MetavoxelInfo& info) { + if (!info.getBounds().intersects(_spanner->getBounds())) { + return STOP_RECURSION; + } + MetavoxelInfo* parentInfo = info.parentInfo; + for (int i = 0; i < _steps && parentInfo; i++) { + parentInfo = parentInfo->parentInfo; + } + for (int i = 0; i < _outputs.size(); i++) { + info.outputValues[i] = AttributeValue(_outputs.at(i)); + } + if (parentInfo) { + foreach (const SharedObjectPointer& object, + parentInfo->inputValues.at(_outputs.size()).getInlineValue()) { + static_cast(object.data())->blendAttributeValues(info, true); + } + } + return (info.size > _voxelizationSize) ? DEFAULT_ORDER : STOP_RECURSION; +} + void InsertSpannerEdit::apply(MetavoxelData& data, const WeakSharedObjectHash& objects) const { - data.insert(attribute, spanner); + data.insert(attribute, this->spanner); + + Spanner* spanner = static_cast(this->spanner.data()); + UpdateSpannerVisitor visitor(spanner->getVoxelizedAttributes(), spanner); + data.guide(visitor); } RemoveSpannerEdit::RemoveSpannerEdit(const AttributePointer& attribute, int id) : @@ -119,21 +211,63 @@ void RemoveSpannerEdit::apply(MetavoxelData& data, const WeakSharedObjectHash& o qDebug() << "Missing object to remove" << id; return; } + // keep a strong reference to the object + SharedObjectPointer sharedPointer = object; data.remove(attribute, object); + + Spanner* spanner = static_cast(object); + UpdateSpannerVisitor visitor(spanner->getVoxelizedAttributes(), spanner); + data.guide(visitor); } ClearSpannersEdit::ClearSpannersEdit(const AttributePointer& attribute) : attribute(attribute) { } +class GatherSpannerAttributesVisitor : public SpannerVisitor { +public: + + GatherSpannerAttributesVisitor(const AttributePointer& attribute); + + const QSet& getAttributes() const { return _attributes; } + + virtual bool visit(Spanner* spanner, const glm::vec3& clipMinimum, float clipSize); + +protected: + + QSet _attributes; +}; + +GatherSpannerAttributesVisitor::GatherSpannerAttributesVisitor(const AttributePointer& attribute) : + SpannerVisitor(QVector() << attribute) { +} + +bool GatherSpannerAttributesVisitor::visit(Spanner* spanner, const glm::vec3& clipMinimum, float clipSize) { + foreach (const AttributePointer& attribute, spanner->getVoxelizedAttributes()) { + _attributes.insert(attribute); + } + return true; +} + void ClearSpannersEdit::apply(MetavoxelData& data, const WeakSharedObjectHash& objects) const { + // find all the spanner attributes + GatherSpannerAttributesVisitor visitor(attribute); + data.guide(visitor); + data.clear(attribute); + foreach (const AttributePointer& attribute, visitor.getAttributes()) { + data.clear(attribute); + } +} + +SetSpannerEdit::SetSpannerEdit(const SharedObjectPointer& spanner) : + spanner(spanner) { } class SetSpannerEditVisitor : public MetavoxelVisitor { public: - SetSpannerEditVisitor(Spanner* spanner); + SetSpannerEditVisitor(const QVector& attributes, Spanner* spanner); virtual int visit(MetavoxelInfo& info); @@ -142,17 +276,20 @@ private: Spanner* _spanner; }; -SetSpannerEditVisitor::SetSpannerEditVisitor(Spanner* spanner) : - MetavoxelVisitor(QVector(), spanner->getAttributes()), +SetSpannerEditVisitor::SetSpannerEditVisitor(const QVector& attributes, Spanner* spanner) : + MetavoxelVisitor(attributes, QVector() << attributes << + AttributeRegistry::getInstance()->getSpannerMaskAttribute()), _spanner(spanner) { } int SetSpannerEditVisitor::visit(MetavoxelInfo& info) { - return _spanner->getAttributeValues(info) ? DEFAULT_ORDER : STOP_RECURSION; -} - -SetSpannerEdit::SetSpannerEdit(const SharedObjectPointer& spanner) : - spanner(spanner) { + if (_spanner->blendAttributeValues(info)) { + return DEFAULT_ORDER; + } + if (info.outputValues.at(0).getAttribute()) { + info.outputValues.last() = AttributeValue(_outputs.last(), encodeInline(1.0f)); + } + return STOP_RECURSION; } void SetSpannerEdit::apply(MetavoxelData& data, const WeakSharedObjectHash& objects) const { @@ -163,6 +300,8 @@ void SetSpannerEdit::apply(MetavoxelData& data, const WeakSharedObjectHash& obje data.expand(); } - SetSpannerEditVisitor visitor(spanner); + SetSpannerEditVisitor visitor(spanner->getAttributes(), spanner); data.guide(visitor); + + setIntersectingMasked(spanner->getBounds(), data); } diff --git a/libraries/metavoxels/src/MetavoxelUtil.cpp b/libraries/metavoxels/src/MetavoxelUtil.cpp index 7993b3bcb6..7995809f1c 100644 --- a/libraries/metavoxels/src/MetavoxelUtil.cpp +++ b/libraries/metavoxels/src/MetavoxelUtil.cpp @@ -299,6 +299,11 @@ Box operator*(const glm::mat4& matrix, const Box& box) { return newBox; } +QDebug& operator<<(QDebug& out, const Box& box) { + return out << '(' << box.minimum.x << box.minimum.y << box.minimum.z << ") (" << + box.maximum.x << box.maximum.y << box.maximum.z << ')'; +} + QMetaObjectEditor::QMetaObjectEditor(QWidget* parent) : QWidget(parent) { QVBoxLayout* layout = new QVBoxLayout(); layout->setContentsMargins(QMargins()); diff --git a/libraries/metavoxels/src/MetavoxelUtil.h b/libraries/metavoxels/src/MetavoxelUtil.h index 3f450212b1..9972981bc7 100644 --- a/libraries/metavoxels/src/MetavoxelUtil.h +++ b/libraries/metavoxels/src/MetavoxelUtil.h @@ -60,6 +60,8 @@ DECLARE_STREAMABLE_METATYPE(Box) Box operator*(const glm::mat4& matrix, const Box& box); +QDebug& operator<<(QDebug& out, const Box& box); + /// Editor for meta-object values. class QMetaObjectEditor : public QWidget { Q_OBJECT diff --git a/libraries/metavoxels/src/SharedObject.cpp b/libraries/metavoxels/src/SharedObject.cpp index e95958ac76..36257a740f 100644 --- a/libraries/metavoxels/src/SharedObject.cpp +++ b/libraries/metavoxels/src/SharedObject.cpp @@ -37,7 +37,7 @@ void SharedObject::decrementReferenceCount() { } } -SharedObject* SharedObject::clone() const { +SharedObject* SharedObject::clone(bool withID) const { // default behavior is to make a copy using the no-arg constructor and copy the stored properties const QMetaObject* metaObject = this->metaObject(); SharedObject* newObject = static_cast(metaObject->newInstance()); @@ -50,6 +50,9 @@ SharedObject* SharedObject::clone() const { foreach (const QByteArray& propertyName, dynamicPropertyNames()) { newObject->setProperty(propertyName, property(propertyName)); } + if (withID) { + newObject->setID(_id); + } return newObject; } @@ -91,6 +94,11 @@ void SharedObject::dump(QDebug debug) const { } } +void SharedObject::setID(int id) { + _weakHash.remove(_id); + _weakHash.insert(_id = id, this); +} + int SharedObject::_lastID = 0; WeakSharedObjectHash SharedObject::_weakHash; diff --git a/libraries/metavoxels/src/SharedObject.h b/libraries/metavoxels/src/SharedObject.h index 66e6474972..435127fffd 100644 --- a/libraries/metavoxels/src/SharedObject.h +++ b/libraries/metavoxels/src/SharedObject.h @@ -47,7 +47,8 @@ public: void decrementReferenceCount(); /// Creates a new clone of this object. - virtual SharedObject* clone() const; + /// \param withID if true, give the clone the same ID as this object + virtual SharedObject* clone(bool withID = false) const; /// Tests this object for equality with another. virtual bool equals(const SharedObject* other) const; @@ -57,6 +58,8 @@ public: private: + void setID(int id); + int _id; int _remoteID; int _referenceCount; diff --git a/libraries/shared/src/FileDownloader.cpp b/libraries/shared/src/FileDownloader.cpp index 69cf599fae..8b13b129fe 100644 --- a/libraries/shared/src/FileDownloader.cpp +++ b/libraries/shared/src/FileDownloader.cpp @@ -18,7 +18,7 @@ FileDownloader::FileDownloader(QObject* parent) : QObject(parent) { connect(&_networkAccessManager, SIGNAL(finished(QNetworkReply*)), SLOT(processReply(QNetworkReply*))); } -void FileDownloader::download(const QUrl dataURL, QNetworkAccessManager::Operation operation) { +void FileDownloader::download(const QUrl& dataURL, QNetworkAccessManager::Operation operation) { QNetworkRequest request(dataURL); _downloadedData.clear(); diff --git a/libraries/shared/src/FileDownloader.h b/libraries/shared/src/FileDownloader.h index 7c5a416600..ad1351a575 100644 --- a/libraries/shared/src/FileDownloader.h +++ b/libraries/shared/src/FileDownloader.h @@ -27,7 +27,7 @@ signals: void done(QNetworkReply::NetworkError error); public slots: - void download(const QUrl dataURL, QNetworkAccessManager::Operation operation = QNetworkAccessManager::GetOperation); + void download(const QUrl& dataURL, QNetworkAccessManager::Operation operation = QNetworkAccessManager::GetOperation); private slots: void processReply(QNetworkReply* reply); diff --git a/tests/metavoxels/src/MetavoxelTests.cpp b/tests/metavoxels/src/MetavoxelTests.cpp index b1f3315bc0..a1b79319b5 100644 --- a/tests/metavoxels/src/MetavoxelTests.cpp +++ b/tests/metavoxels/src/MetavoxelTests.cpp @@ -34,9 +34,103 @@ static int streamedBytesReceived = 0; static int sharedObjectsCreated = 0; static int sharedObjectsDestroyed = 0; +static QByteArray createRandomBytes(int minimumSize, int maximumSize) { + QByteArray bytes(randIntInRange(minimumSize, maximumSize), 0); + for (int i = 0; i < bytes.size(); i++) { + bytes[i] = rand(); + } + return bytes; +} + +static QByteArray createRandomBytes() { + const int MIN_BYTES = 4; + const int MAX_BYTES = 16; + return createRandomBytes(MIN_BYTES, MAX_BYTES); +} + +static TestMessageC createRandomMessageC() { + TestMessageC message; + message.foo = randomBoolean(); + message.bar = rand(); + message.baz = randFloat(); + message.bong.foo = createRandomBytes(); + return message; +} + +static bool testSerialization(Bitstream::MetadataType metadataType) { + QByteArray array; + QDataStream outStream(&array, QIODevice::WriteOnly); + Bitstream out(outStream, metadataType); + SharedObjectPointer testObjectWrittenA = new TestSharedObjectA(randFloat()); + out << testObjectWrittenA; + SharedObjectPointer testObjectWrittenB = new TestSharedObjectB(randFloat(), createRandomBytes()); + out << testObjectWrittenB; + TestMessageC messageWritten = createRandomMessageC(); + out << QVariant::fromValue(messageWritten); + QByteArray endWritten = "end"; + out << endWritten; + out.flush(); + + QDataStream inStream(array); + Bitstream in(inStream, metadataType); + in.addMetaObjectSubstitution("TestSharedObjectA", &TestSharedObjectB::staticMetaObject); + in.addMetaObjectSubstitution("TestSharedObjectB", &TestSharedObjectA::staticMetaObject); + in.addTypeSubstitution("TestMessageC", TestMessageA::Type); + SharedObjectPointer testObjectReadA; + in >> testObjectReadA; + + if (!testObjectReadA || testObjectReadA->metaObject() != &TestSharedObjectB::staticMetaObject) { + qDebug() << "Wrong class for A" << testObjectReadA << metadataType; + return true; + } + if (metadataType == Bitstream::FULL_METADATA && static_cast(testObjectWrittenA.data())->getFoo() != + static_cast(testObjectReadA.data())->getFoo()) { + QDebug debug = qDebug() << "Failed to transfer shared field from A to B"; + testObjectWrittenA->dump(debug); + testObjectReadA->dump(debug); + return true; + } + + SharedObjectPointer testObjectReadB; + in >> testObjectReadB; + if (!testObjectReadB || testObjectReadB->metaObject() != &TestSharedObjectA::staticMetaObject) { + qDebug() << "Wrong class for B" << testObjectReadB << metadataType; + return true; + } + if (metadataType == Bitstream::FULL_METADATA && static_cast(testObjectWrittenB.data())->getFoo() != + static_cast(testObjectReadB.data())->getFoo()) { + QDebug debug = qDebug() << "Failed to transfer shared field from B to A"; + testObjectWrittenB->dump(debug); + testObjectReadB->dump(debug); + return true; + } + + QVariant messageRead; + in >> messageRead; + if (!messageRead.isValid() || messageRead.userType() != TestMessageA::Type) { + qDebug() << "Wrong type for message" << messageRead; + return true; + } + if (metadataType == Bitstream::FULL_METADATA && messageWritten.foo != messageRead.value().foo) { + QDebug debug = qDebug() << "Failed to transfer shared field between messages" << + messageWritten.foo << messageRead.value().foo; + return true; + } + + QByteArray endRead; + in >> endRead; + if (endWritten != endRead) { + qDebug() << "End tag mismatch." << endRead; + return true; + } + + return false; +} + bool MetavoxelTests::run() { - qDebug() << "Running metavoxel tests..."; + qDebug() << "Running transmission tests..."; + qDebug(); // seed the random number generator so that our tests are reproducible srand(0xBAAAAABE); @@ -62,26 +156,20 @@ bool MetavoxelTests::run() { qDebug() << "Sent" << streamedBytesSent << "streamed bytes, received" << streamedBytesReceived; qDebug() << "Sent" << datagramsSent << "datagrams, received" << datagramsReceived; qDebug() << "Created" << sharedObjectsCreated << "shared objects, destroyed" << sharedObjectsDestroyed; + qDebug(); + + qDebug() << "Running serialization tests..."; + qDebug(); + + if (testSerialization(Bitstream::HASH_METADATA) || testSerialization(Bitstream::FULL_METADATA)) { + return true; + } qDebug() << "All tests passed!"; return false; } -static QByteArray createRandomBytes(int minimumSize, int maximumSize) { - QByteArray bytes(randIntInRange(minimumSize, maximumSize), 0); - for (int i = 0; i < bytes.size(); i++) { - bytes[i] = rand(); - } - return bytes; -} - -static QByteArray createRandomBytes() { - const int MIN_BYTES = 4; - const int MAX_BYTES = 16; - return createRandomBytes(MIN_BYTES, MAX_BYTES); -} - static SharedObjectPointer createRandomSharedObject() { switch (randIntInRange(0, 2)) { case 0: return new TestSharedObjectA(randFloat()); @@ -132,12 +220,7 @@ static QVariant createRandomMessage() { } case 2: default: { - TestMessageC message; - message.foo = randomBoolean(); - message.bar = rand(); - message.baz = randFloat(); - message.bong.foo = createRandomBytes(); - return QVariant::fromValue(message); + return QVariant::fromValue(createRandomMessageC()); } } } @@ -322,7 +405,9 @@ void TestSharedObjectA::setFoo(float foo) { } } -TestSharedObjectB::TestSharedObjectB() { +TestSharedObjectB::TestSharedObjectB(float foo, const QByteArray& bar) : + _foo(foo), + _bar(bar) { sharedObjectsCreated++; } diff --git a/tests/metavoxels/src/MetavoxelTests.h b/tests/metavoxels/src/MetavoxelTests.h index 3b69c28f79..22a680acbb 100644 --- a/tests/metavoxels/src/MetavoxelTests.h +++ b/tests/metavoxels/src/MetavoxelTests.h @@ -89,11 +89,24 @@ private: /// Another simple shared object. class TestSharedObjectB : public SharedObject { Q_OBJECT + Q_PROPERTY(float foo READ getFoo WRITE setFoo) + Q_PROPERTY(QByteArray bar READ getBar WRITE setBar) public: - Q_INVOKABLE TestSharedObjectB(); + Q_INVOKABLE TestSharedObjectB(float foo = 0.0f, const QByteArray& bar = QByteArray()); virtual ~TestSharedObjectB(); + + void setFoo(float foo) { _foo = foo; } + float getFoo() const { return _foo; } + + void setBar(const QByteArray& bar) { _bar = bar; } + const QByteArray& getBar() const { return _bar; } + +private: + + float _foo; + QByteArray _bar; }; /// A simple test message. diff --git a/tools/mtc/src/main.cpp b/tools/mtc/src/main.cpp index 248c2ddd2d..9c77851961 100644 --- a/tools/mtc/src/main.cpp +++ b/tools/mtc/src/main.cpp @@ -24,10 +24,16 @@ public: QStringList bases; }; +class Field { +public: + QString type; + QString name; +}; + class Streamable { public: Class clazz; - QStringList fields; + QList fields; }; void processInput(QTextStream& in, QList* streamables) { @@ -66,8 +72,10 @@ void processInput(QTextStream& in, QList* streamables) { } else if (match.startsWith("STREAM")) { match.chop(1); // get rid of the semicolon - match = match.trimmed(); // and any space before it - currentStreamable.fields.append(match.mid(match.lastIndexOf(' ') + 1)); + match = match.mid(match.indexOf(' ') + 1).trimmed(); // and STREAM, and any space before it + int index = match.lastIndexOf(' '); + Field field = { match.left(index).simplified(), match.mid(index + 1) }; + currentStreamable.fields.append(field); } else { // match.startsWith("class") classExp.exactMatch(match); @@ -90,12 +98,85 @@ void generateOutput (QTextStream& out, const QList& streamables) { foreach (const Streamable& str, streamables) { const QString& name = str.clazz.name; + out << "const int " << name << "::Type = registerStreamableMetaType<" << name << ">();\n"; + + out << "const QVector& " << name << "::getMetaFields() {\n"; + out << " static QVector metaFields = QVector()"; + foreach (const QString& base, str.clazz.bases) { + out << " << " << base << "::getMetaFields()"; + } + foreach (const Field& field, str.fields) { + out << "\n << MetaField(\"" << field.name << "\", Bitstream::getTypeStreamer(qMetaTypeId<" << + field.type << ">()))"; + } + out << ";\n"; + out << " return metaFields;\n"; + out << "}\n"; + + out << "int " << name << "::getFieldIndex(const QByteArray& name) {\n"; + out << " static QHash fieldIndices = createFieldIndices();\n"; + out << " return fieldIndices.value(name) - 1;\n"; + out << "}\n"; + + out << "QHash " << name << "::createFieldIndices() {\n"; + out << " QHash indices;\n"; + out << " int index = 1;\n"; + foreach (const QString& base, str.clazz.bases) { + out << " foreach (const MetaField& field, " << base << "::getMetaFields()) {\n"; + out << " indices.insert(field.getName(), index++);\n"; + out << " }\n"; + } + out << " foreach (const MetaField& field, getMetaFields()) {\n"; + out << " indices.insert(field.getName(), index++);\n"; + out << " }\n"; + out << " return indices;\n"; + out << "}\n"; + + out << "void " << name << "::setField(int index, const QVariant& value) {\n"; + if (!str.clazz.bases.isEmpty()) { + out << " int nextIndex;\n"; + } + foreach (const QString& base, str.clazz.bases) { + out << " if ((nextIndex = index - " << base << "::getMetaFields().size()) < 0) {\n"; + out << " " << base << "::setField(index, value);\n"; + out << " return;\n"; + out << " }\n"; + out << " index = nextIndex;\n"; + } + out << " switch (index) {\n"; + for (int i = 0; i < str.fields.size(); i++) { + out << " case " << i << ":\n"; + out << " this->" << str.fields.at(i).name << " = value.value<" << str.fields.at(i).type << ">();\n"; + out << " break;\n"; + } + out << " }\n"; + out << "}\n"; + + out << "QVariant " << name << "::getField(int index) const {\n"; + if (!str.clazz.bases.isEmpty()) { + out << " int nextIndex;\n"; + } + foreach (const QString& base, str.clazz.bases) { + out << " if ((nextIndex = index - " << base << "::getMetaFields().size()) < 0) {\n"; + out << " return " << base << "::getField(index);\n"; + out << " }\n"; + out << " index = nextIndex;\n"; + } + out << " switch (index) {\n"; + for (int i = 0; i < str.fields.size(); i++) { + out << " case " << i << ":\n"; + out << " return QVariant::fromValue(this->" << str.fields.at(i).name << ");\n"; + } + out << " }\n"; + out << " return QVariant();\n"; + out << "}\n"; + out << "Bitstream& operator<<(Bitstream& out, const " << name << "& obj) {\n"; foreach (const QString& base, str.clazz.bases) { out << " out << static_cast(obj);\n"; } - foreach (const QString& field, str.fields) { - out << " out << obj." << field << ";\n"; + foreach (const Field& field, str.fields) { + out << " out << obj." << field.name << ";\n"; } out << " return out;\n"; out << "}\n"; @@ -104,12 +185,32 @@ void generateOutput (QTextStream& out, const QList& streamables) { foreach (const QString& base, str.clazz.bases) { out << " in >> static_cast<" << base << "&>(obj);\n"; } - foreach (const QString& field, str.fields) { - out << " in >> obj." << field << ";\n"; + foreach (const Field& field, str.fields) { + out << " in >> obj." << field.name << ";\n"; } out << " return in;\n"; out << "}\n"; + out << "template<> void Bitstream::writeRawDelta(const " << name << "& value, const " << name << "& reference) {\n"; + foreach (const QString& base, str.clazz.bases) { + out << " writeRawDelta(static_cast(value), static_cast(reference));\n"; + } + foreach (const Field& field, str.fields) { + out << " writeDelta(value." << field.name << ", reference." << field.name << ");\n"; + } + out << "}\n"; + + out << "template<> void Bitstream::readRawDelta(" << name << "& value, const " << name << "& reference) {\n"; + foreach (const QString& base, str.clazz.bases) { + out << " readRawDelta(static_cast<" << base << "&>(value), static_cast(reference));\n"; + } + foreach (const Field& field, str.fields) { + out << " readDelta(value." << field.name << ", reference." << field.name << ");\n"; + } + out << "}\n"; + out << "bool operator==(const " << name << "& first, const " << name << "& second) {\n"; if (str.clazz.bases.isEmpty() && str.fields.isEmpty()) { out << " return true"; @@ -124,12 +225,12 @@ void generateOutput (QTextStream& out, const QList& streamables) { out << "static_cast(first) == static_cast(second)"; first = false; } - foreach (const QString& field, str.fields) { + foreach (const Field& field, str.fields) { if (!first) { out << " &&\n"; out << " "; } - out << "first." << field << " == second." << field; + out << "first." << field.name << " == second." << field.name; first = false; } } @@ -150,19 +251,17 @@ void generateOutput (QTextStream& out, const QList& streamables) { out << "static_cast(first) != static_cast(second)"; first = false; } - foreach (const QString& field, str.fields) { + foreach (const Field& field, str.fields) { if (!first) { out << " ||\n"; out << " "; } - out << "first." << field << " != second." << field; + out << "first." << field.name << " != second." << field.name; first = false; } } out << ";\n"; - out << "}\n"; - - out << "const int " << name << "::Type = registerStreamableMetaType<" << name << ">();\n\n"; + out << "}\n\n"; } }