From e505554f1bb1008aa7b6483b04a1f72f2966c44b Mon Sep 17 00:00:00 2001 From: Valentin Ackva Date: Mon, 17 Jun 2024 01:57:56 +0200 Subject: [PATCH] Included second opengl panner visualisation --- CMakeLists.txt | 20 +- source/PluginEditor.cpp | 46 +--- source/PluginEditor.h | 10 +- source/ui/PannerComponent.cpp | 82 +++++++ source/ui/PannerComponent.h | 66 +---- source/ui/ParameterComponent.cpp | 30 +++ source/ui/ParameterComponent.h | 31 +-- source/ui/Test.h | 73 ------ source/ui/opengl/Panner3dOpenGL.h | 395 ++++++++++++++++++++++++++++++ 9 files changed, 538 insertions(+), 215 deletions(-) create mode 100644 source/ui/PannerComponent.cpp create mode 100644 source/ui/ParameterComponent.cpp delete mode 100644 source/ui/Test.h create mode 100644 source/ui/opengl/Panner3dOpenGL.h diff --git a/CMakeLists.txt b/CMakeLists.txt index 34a2b14..38c20e1 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -40,7 +40,7 @@ juce_add_plugin(${TARGET_NAME} PLUGIN_CODE Dem0 # A unique four-character plugin id with exactly one upper-case character # GarageBand 10.3 requires the first letter to be upper-case, and the remaining letters to be lower-case FORMATS ${FORMATS_TO_BUILD} # The formats to build. Other valid formats are: AAX Unity VST AU AUv3 - PRODUCT_NAME "Binaural Panner" # The name of the final executable, which can differ from the target name + PRODUCT_NAME "Orbe" # The name of the final executable, which can differ from the target name ) juce_generate_juce_header(${TARGET_NAME}) @@ -51,11 +51,13 @@ target_sources(${TARGET_NAME} source/PluginProcessor.cpp source/PluginParameters.cpp - source/dsp/HRIRLoader.cpp - source/dsp/SofaReader.cpp - source/ui/PannerVisualisation.cpp source/ui/BackgroundComponent.cpp + source/ui/PannerComponent.cpp + source/ui/ParameterComponent.cpp + + source/dsp/HRIRLoader.cpp + source/dsp/SofaReader.cpp source/dsp/convolution/custom_juce_Convolution.cpp ) @@ -64,11 +66,11 @@ set(SOFA_TEST_FILE "${CMAKE_CURRENT_LIST_DIR}/assets/pp2_HRIRs_measured_time_ali target_compile_definitions(${TARGET_NAME} PUBLIC - JUCE_WEB_BROWSER=0 # If you remove this, add `NEEDS_WEB_BROWSER TRUE` to the `juce_add_plugin` call - JUCE_USE_CURL=0 # If you remove this, add `NEEDS_CURL TRUE` to the `juce_add_plugin` call + JUCE_WEB_BROWSER=0 + JUCE_USE_CURL=0 JUCE_VST3_CAN_REPLACE_VST2=0 - SOFA_TEST_FILE_PATH="${SOFA_TEST_FILE}" JUCE_SILENCE_XCODE_15_LINKER_WARNING=1 + JUCE_DISPLAY_SPLASH_SCREEN=0 ) juce_add_binary_data(AudioPluginData SOURCES @@ -83,6 +85,10 @@ target_link_libraries(${TARGET_NAME} AudioPluginData juce::juce_audio_utils juce::juce_dsp + juce::juce_opengl + juce::juce_graphics + juce::juce_gui_basics + juce::juce_gui_extra mysofa-static PUBLIC juce::juce_recommended_config_flags diff --git a/source/PluginEditor.cpp b/source/PluginEditor.cpp index 9060f9a..93324c4 100644 --- a/source/PluginEditor.cpp +++ b/source/PluginEditor.cpp @@ -6,30 +6,18 @@ AudioPluginAudioProcessorEditor::AudioPluginAudioProcessorEditor (AudioPluginAud : AudioProcessorEditor (&p), processorRef (p), parameterComponent(p), pannerComponent(p) { juce::ignoreUnused (processorRef); -// addAndMakeVisible(backgroundComponent); -// addAndMakeVisible(pannerVisualisation); -// -// genericParameter = std::make_unique(p); -// addAndMakeVisible(*genericParameter); - addAndMakeVisible(parameterComponent); addAndMakeVisible(pannerComponent); setEditorDimensions(); - - startTimerHz(30); - - pannerVisualisation.addListener(this); - } AudioPluginAudioProcessorEditor::~AudioPluginAudioProcessorEditor() { - stopTimer(); - pannerVisualisation.removeListener(this); + } //============================================================================== @@ -41,15 +29,6 @@ void AudioPluginAudioProcessorEditor::paint (juce::Graphics& g) void AudioPluginAudioProcessorEditor::resized() { -// const auto backgroundBounds = getLocalBounds(); -// backgroundComponent.setBounds(backgroundBounds); -// const auto pannerVisualisationBounds = getLocalBounds().removeFromTop(getWidth()); -// pannerVisualisation.setBounds(pannerVisualisationBounds); -// const auto parameterBounds = getLocalBounds().removeFromBottom(getHeight() - pannerVisualisationBounds.getHeight()); -// if (genericParameter != nullptr) { -// genericParameter->setBounds(parameterBounds); -// } - auto bounds = getLocalBounds().reduced(getWidth() / 60); auto pannerBounds = bounds.removeFromRight(bounds.getHeight()); @@ -72,26 +51,3 @@ void AudioPluginAudioProcessorEditor::setEditorDimensions() { setSize(1200,static_cast(1200.0 / ratio)); } - -void AudioPluginAudioProcessorEditor::timerCallback() { -// float normalizedX = processorRef.getValueTreeState().getParameter("param_x")->getValue(); -// float normalizedY = processorRef.getValueTreeState().getParameter("param_y")->getValue(); -// float normalizedZ = processorRef.getValueTreeState().getParameter("param_z")->getValue(); -// -// float x = PluginParameters::xRange.convertFrom0to1(normalizedX); -// float y = PluginParameters::yRange.convertFrom0to1(normalizedY); -// float z = PluginParameters::zRange.convertFrom0to1(normalizedZ); -// -// pannerVisualisation.setVisualPosition(x, y, z); -} - -void AudioPluginAudioProcessorEditor::pannerChanged(float x, float y) { - auto& processorParams = processorRef.getValueTreeState(); - - auto paramX = processorParams.getParameter(PluginParameters::X_ID.getParamID()); - auto paramY = processorParams.getParameter(PluginParameters::Y_ID.getParamID()); - - paramX->setValueNotifyingHost(paramX->convertTo0to1(x)); - paramY->setValueNotifyingHost(paramY->convertTo0to1(y)); -} - diff --git a/source/PluginEditor.h b/source/PluginEditor.h index a7f3322..dae3d24 100644 --- a/source/PluginEditor.h +++ b/source/PluginEditor.h @@ -9,7 +9,7 @@ #include "ui/ParameterComponent.h" //============================================================================== -class AudioPluginAudioProcessorEditor final : public juce::AudioProcessorEditor, juce::Timer, PannerVisualisation::Listener +class AudioPluginAudioProcessorEditor final : public juce::AudioProcessorEditor { public: explicit AudioPluginAudioProcessorEditor (AudioPluginAudioProcessor&); @@ -21,19 +21,11 @@ class AudioPluginAudioProcessorEditor final : public juce::AudioProcessorEditor, private: void setEditorDimensions(); - void timerCallback() override; - void pannerChanged(float x, float y); - private: AudioPluginAudioProcessor& processorRef; - BackgroundComponent backgroundComponent; - ParameterComponent parameterComponent; PannerComponent pannerComponent; - - PannerVisualisation pannerVisualisation; - JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (AudioPluginAudioProcessorEditor) }; diff --git a/source/ui/PannerComponent.cpp b/source/ui/PannerComponent.cpp new file mode 100644 index 0000000..6584901 --- /dev/null +++ b/source/ui/PannerComponent.cpp @@ -0,0 +1,82 @@ +// +// Created by Valentin Ackva on 17/06/2024. +// + +#include "PannerComponent.h" + +PannerComponent::PannerComponent(AudioPluginAudioProcessor &processor) : processorRef(processor), viewButton("viewButton", juce::DrawableButton::ButtonStyle::ImageFitted) { + addAndMakeVisible(pannerVisualisation); + addAndMakeVisible(openGLVisualisation); + pannerVisualisation.addListener(this); + startTimerHz(30); + + viewButton.setClickingTogglesState(true); + viewButton.setImages(button2d.get(), + button2d.get(), + button3d.get(), + button2d.get(), + button3d.get(), + button3d.get(), + button3d.get(), + button3d.get()); + + viewButton.onClick = [this]() { + if (show2D) { + show2D = false; + pannerVisualisation.setVisible(false); + openGLVisualisation.setVisible(true); + } else { + show2D = true; + pannerVisualisation.setVisible(true); + openGLVisualisation.setVisible(false); + } + }; + + openGLVisualisation.setVisible(false); + + addAndMakeVisible(viewButton); +} + +PannerComponent::~PannerComponent() { + pannerVisualisation.removeListener(this); + stopTimer(); +} + +void PannerComponent::paint(Graphics &g) { + g.setColour(juce::Colour {0xff151517}); + g.fillRoundedRectangle(getLocalBounds().toFloat(), 10.f); +} + +void PannerComponent::resized() { + pannerVisualisation.setBounds(getLocalBounds().reduced(10)); + openGLVisualisation.setBounds(getLocalBounds().reduced(20)); + + juce::Rectangle button (getWidth() - 68, getHeight() - 18, 68, 18); + viewButton.setBounds(button); +} + +void PannerComponent::pannerChanged(float x, float y) { + auto& processorParams = processorRef.getValueTreeState(); + + auto paramX = processorParams.getParameter(PluginParameters::X_ID.getParamID()); + auto paramY = processorParams.getParameter(PluginParameters::Y_ID.getParamID()); + + paramX->setValueNotifyingHost(paramX->convertTo0to1(x)); + paramY->setValueNotifyingHost(paramY->convertTo0to1(y)); +} + +void PannerComponent::timerCallback() { + float normalizedX = processorRef.getValueTreeState().getParameter("param_x")->getValue(); + float normalizedY = processorRef.getValueTreeState().getParameter("param_y")->getValue(); + float normalizedZ = processorRef.getValueTreeState().getParameter("param_z")->getValue(); + + float x = PluginParameters::xRange.convertFrom0to1(normalizedX); + float y = PluginParameters::yRange.convertFrom0to1(normalizedY); + float z = PluginParameters::zRange.convertFrom0to1(normalizedZ); + + if (show2D) { + pannerVisualisation.setVisualPosition(x, y, z); + } else { + openGLVisualisation.setVisualPosition(x, y, z); + } +} diff --git a/source/ui/PannerComponent.h b/source/ui/PannerComponent.h index 6abd3f3..9ba3053 100644 --- a/source/ui/PannerComponent.h +++ b/source/ui/PannerComponent.h @@ -6,74 +6,32 @@ #define BINAURALPANNER_PANNERCOMPONENT_H #include -#include "ui/PannerVisualisation.h" +#include "PannerVisualisation.h" +#include "opengl/Panner3dOpenGL.h" +#include "../PluginProcessor.h" class PannerComponent : public juce::Component, juce::Timer, PannerVisualisation::Listener { public: - explicit PannerComponent(AudioPluginAudioProcessor& processor) : processorRef(processor), viewButton("viewButton", juce::DrawableButton::ButtonStyle::ImageFitted) { - addAndMakeVisible(pannerVisualisation); - pannerVisualisation.addListener(this); - startTimerHz(30); - - viewButton.setClickingTogglesState(true); - viewButton.setImages(button2d.get(), - button2d.get(), - button3d.get(), - button2d.get(), - button3d.get(), - button3d.get(), - button3d.get(), - button3d.get()); - addAndMakeVisible(viewButton); - } - - ~PannerComponent() override { - pannerVisualisation.removeListener(this); - stopTimer(); - } + explicit PannerComponent(AudioPluginAudioProcessor& processor); + ~PannerComponent() override; private: + void paint (juce::Graphics& g) override; + void resized() override; - void paint (juce::Graphics& g) override { - g.setColour(juce::Colour {0xff151517}); - g.fillRoundedRectangle(getLocalBounds().toFloat(), 10.f); - } - - void resized() override { - pannerVisualisation.setBounds(getLocalBounds().reduced(10)); - juce::Rectangle button (getWidth() - 68, getHeight() - 18, 68, 18); - viewButton.setBounds(button); - } - - void pannerChanged(float x, float y) override { - auto& processorParams = processorRef.getValueTreeState(); - - auto paramX = processorParams.getParameter(PluginParameters::X_ID.getParamID()); - auto paramY = processorParams.getParameter(PluginParameters::Y_ID.getParamID()); - - paramX->setValueNotifyingHost(paramX->convertTo0to1(x)); - paramY->setValueNotifyingHost(paramY->convertTo0to1(y)); - } - - void timerCallback() override { - float normalizedX = processorRef.getValueTreeState().getParameter("param_x")->getValue(); - float normalizedY = processorRef.getValueTreeState().getParameter("param_y")->getValue(); - float normalizedZ = processorRef.getValueTreeState().getParameter("param_z")->getValue(); - - float x = PluginParameters::xRange.convertFrom0to1(normalizedX); - float y = PluginParameters::yRange.convertFrom0to1(normalizedY); - float z = PluginParameters::zRange.convertFrom0to1(normalizedZ); - - pannerVisualisation.setVisualPosition(x, y, z); - } + void pannerChanged(float x, float y) override; + void timerCallback() override; private: AudioPluginAudioProcessor& processorRef; PannerVisualisation pannerVisualisation; + MainContentComponent openGLVisualisation; std::unique_ptr button2d = juce::Drawable::createFromImageData(BinaryData::button_2d_svg, BinaryData::button_2d_svgSize); std::unique_ptr button3d = juce::Drawable::createFromImageData(BinaryData::button_3d_svg, BinaryData::button_3d_svgSize); juce::DrawableButton viewButton; + + bool show2D = true; }; #endif //BINAURALPANNER_PANNERCOMPONENT_H diff --git a/source/ui/ParameterComponent.cpp b/source/ui/ParameterComponent.cpp new file mode 100644 index 0000000..b811266 --- /dev/null +++ b/source/ui/ParameterComponent.cpp @@ -0,0 +1,30 @@ +// +// Created by Valentin Ackva on 17/06/2024. +// + +#include "ParameterComponent.h" + +ParameterComponent::ParameterComponent(AudioPluginAudioProcessor &processor) : processorRef(processor) { + genericParameter = std::make_unique(processorRef); + addAndMakeVisible(*genericParameter); +} + +void ParameterComponent::paint(juce::Graphics &g) { + g.setColour(juce::Colours::transparentBlack); + g.fillAll(); + g.setColour(juce::Colour{0xff151517}); + g.fillRoundedRectangle(getLocalBounds().toFloat(), 10.f); + + auto bounds = getLocalBounds(); + auto headerBounds = bounds.removeFromTop(static_cast((float)getHeight() * 0.2f)); + orbeLogo->drawWithin(g, headerBounds.toFloat(), juce::RectanglePlacement::doNotResize, 1.f); +} + +void ParameterComponent::resized() { + auto bounds = getLocalBounds(); + auto headerBounds = bounds.removeFromTop(static_cast((float)getHeight() * 0.2f)); + bounds.removeFromBottom(10); + auto paramBounds = bounds; + + genericParameter->setBounds(paramBounds); +} diff --git a/source/ui/ParameterComponent.h b/source/ui/ParameterComponent.h index 89c2a3d..f95dce6 100644 --- a/source/ui/ParameterComponent.h +++ b/source/ui/ParameterComponent.h @@ -12,44 +12,21 @@ class CustomGenericAudioProcessorEditor : public juce::GenericAudioProcessorEdit public: CustomGenericAudioProcessorEditor(juce::AudioProcessor& processor) : juce::GenericAudioProcessorEditor(processor) { - // Set the size if needed setSize(400, 300); - } void paint(juce::Graphics& g) override { - // Set the background color g.setColour(juce::Colour{0xff151517}); g.fillAll(); } }; + class ParameterComponent : public juce::Component { public: - ParameterComponent(AudioPluginAudioProcessor& processor) : processorRef(processor) { - // Use the custom generic editor - genericParameter = std::make_unique(processorRef); - addAndMakeVisible(*genericParameter); - } + explicit ParameterComponent(AudioPluginAudioProcessor& processor); - void paint(juce::Graphics& g) override { - g.setColour(juce::Colours::transparentBlack); - g.fillAll(); - g.setColour(juce::Colour{0xff151517}); - g.fillRoundedRectangle(getLocalBounds().toFloat(), 10.f); - - auto bounds = getLocalBounds(); - auto headerBounds = bounds.removeFromTop(static_cast((float)getHeight() * 0.2f)); - orbeLogo->drawWithin(g, headerBounds.toFloat(), juce::RectanglePlacement::doNotResize, 1.f); - } - - void resized() override { - auto bounds = getLocalBounds(); - auto headerBounds = bounds.removeFromTop(static_cast((float)getHeight() * 0.2f)); - bounds.removeFromBottom(10); - auto paramBounds = bounds; - - genericParameter->setBounds(paramBounds); - } + void paint(juce::Graphics& g) override; + void resized() override; private: AudioPluginAudioProcessor& processorRef; diff --git a/source/ui/Test.h b/source/ui/Test.h deleted file mode 100644 index 9b92f1f..0000000 --- a/source/ui/Test.h +++ /dev/null @@ -1,73 +0,0 @@ -// -// Created by Valentin Ackva on 06/05/2024. -// - -#ifndef BINAURALPANNER_TEST_H -#define BINAURALPANNER_TEST_H - -#include - -#include - -class OrbitalComponent : public juce::Component, - private juce::Timer -{ -public: - OrbitalComponent() - { - startTimerHz(60); // Setting the timer for 60 frames per second - setSize(500, 500); // Set the size of the component - } - - void paint(juce::Graphics& g) override - { - g.fillAll(juce::Colours::black); // Background color - auto bounds = getLocalBounds().toFloat(); - auto center = bounds.getCentre(); - - // Draw the planet at the center - g.setColour(juce::Colours::grey); - g.fillEllipse(center.x - 20, center.y - 20, 40, 40); // Static size for the planet - - // Draw multiple orbits with varying inclinations and tracks - drawOrbit(g, center, 100, 0.0f, 0.0f); // Horizontal orbit - drawOrbit(g, center, 120, 45.0f, 0.15f); // Tilted orbit at 45 degrees - drawOrbit(g, center, 140, 90.0f, 0.3f); // Vertical orbit - } - - void timerCallback() override - { - orbitProgress += 0.005f; // Increment the orbit progress - if (orbitProgress >= 1.0f) - orbitProgress = 0.0f; - repaint(); // Redraw the component - } - - void drawOrbit(juce::Graphics& g, juce::Point center, float radius, float tiltDegrees, float progressOffset) - { - float orbitProgressLocal = fmod(orbitProgress + progressOffset, 1.0f); - float angle = juce::MathConstants::twoPi * orbitProgressLocal; - float tiltRadians = juce::degreesToRadians(tiltDegrees); - - // Calculate the ellipse representing the orbit - float majorAxis = radius * cos(tiltRadians); // Adjust width based on tilt - float minorAxis = radius * sin(tiltRadians); // Adjust height based on tilt - - // Draw the orbit path - g.setColour(juce::Colours::grey.withAlpha(0.5f)); - g.drawEllipse(center.x - majorAxis, center.y - minorAxis, 2 * majorAxis, 2 * minorAxis, 1.0f); - - // Position of the moving circle on this orbit - juce::Point orbitPoint(center.x + majorAxis * std::cos(angle), - center.y + minorAxis * std::sin(angle)); - - // Draw the moving circle - g.setColour(juce::Colours::white); - g.fillEllipse(orbitPoint.x - 5, orbitPoint.y - 5, 10, 10); // Smaller circle size - } - -private: - float orbitProgress = 0.0f; // Normalized progress of each orbit -}; - -#endif //BINAURALPANNER_TEST_H diff --git a/source/ui/opengl/Panner3dOpenGL.h b/source/ui/opengl/Panner3dOpenGL.h new file mode 100644 index 0000000..6b739d2 --- /dev/null +++ b/source/ui/opengl/Panner3dOpenGL.h @@ -0,0 +1,395 @@ +#include +using namespace ::juce::gl; + +//============================================================================== +class MainContentComponent : public juce::OpenGLAppComponent, + public juce::Slider::Listener +{ +public: + MainContentComponent() + { + addAndMakeVisible(rotationXSlider); + rotationXSlider.setRange(-10.0, 10.0, 0.01); + rotationXSlider.addListener(this); + rotationXSlider.setValue(0.0); + + addAndMakeVisible(rotationYSlider); + rotationYSlider.setRange(-10.0, 10.0, 0.01); + rotationYSlider.addListener(this); + rotationYSlider.setValue(0.0); + + addAndMakeVisible(rotationZSlider); + rotationZSlider.setRange(-10.0, 10.0, 0.01); + rotationZSlider.addListener(this); + rotationZSlider.setValue(0.0); + + addAndMakeVisible(cameraXSlider); + cameraXSlider.setRange(-50.0, 50.0, 0.01); + cameraXSlider.addListener(this); + cameraXSlider.setValue(0.0); + + addAndMakeVisible(cameraYSlider); + cameraYSlider.setRange(-50.0, 50.0, 0.01); + cameraYSlider.addListener(this); + cameraYSlider.setValue(3.0); + + addAndMakeVisible(cameraZSlider); + cameraZSlider.setRange(-50.0, 50.0, 0.01); + cameraZSlider.addListener(this); + cameraZSlider.setValue(-20.0); + + if (true) { + rotationXSlider.setVisible(false); + rotationYSlider.setVisible(false); + rotationZSlider.setVisible(false); + cameraXSlider.setVisible(false); + cameraYSlider.setVisible(false); + cameraZSlider.setVisible(false); + } + + setSize(800, 600); + } + + ~MainContentComponent() override + { + shutdownOpenGL(); + } + + void initialise() override + { + createShaders(); + openGLContext.extensions.glGenBuffers(1, &vertexBuffer); + openGLContext.extensions.glBindBuffer(GL_ARRAY_BUFFER, vertexBuffer); + + float margin = 0.f; + float vertices[] = { + -1.0f + margin, 1.0f - margin, + -1.0f + margin, -1.0f + margin, + 1.0f - margin, 1.0f - margin, + 1.0f - margin, -1.0f + margin, + }; + + openGLContext.extensions.glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW); + openGLContext.extensions.glBindBuffer(GL_ARRAY_BUFFER, 0); + + // Enable blending + glEnable(GL_BLEND); + glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); + } + + void shutdown() override + { + shader.reset(); + uniforms.reset(); + openGLContext.extensions.glDeleteBuffers(1, &vertexBuffer); + } + + void render() override + { + using namespace ::juce::gl; + + jassert(juce::OpenGLHelpers::isContextActive()); + + auto desktopScale = (float)openGLContext.getRenderingScale(); + juce::OpenGLHelpers::clear(juce::Colour(0xff151517)); + + glViewport(0, 0, juce::roundToInt(desktopScale * (float)getWidth()), juce::roundToInt(desktopScale * (float)getHeight())); + + shader->use(); + + if (uniforms->iResolution.get() != nullptr) + uniforms->iResolution->set((GLfloat)getWidth(), (GLfloat)getHeight(), desktopScale); + + if (uniforms->iTime.get() != nullptr) + uniforms->iTime->set((GLfloat)juce::Time::getMillisecondCounterHiRes() * 0.001f); + + if (uniforms->iPosition.get() != nullptr) + uniforms->iPosition->set((GLfloat)rotationXSlider.getValue(), (GLfloat)rotationYSlider.getValue(), (GLfloat)rotationZSlider.getValue()); + + if (uniforms->iCameraPosition.get() != nullptr) + uniforms->iCameraPosition->set((GLfloat)cameraXSlider.getValue(), (GLfloat)cameraYSlider.getValue(), (GLfloat)cameraZSlider.getValue()); + + openGLContext.extensions.glBindBuffer(GL_ARRAY_BUFFER, vertexBuffer); + openGLContext.extensions.glEnableVertexAttribArray(positionAttribute); + openGLContext.extensions.glVertexAttribPointer(positionAttribute, 2, GL_FLOAT, GL_FALSE, 0, 0); + + glDrawArrays(GL_TRIANGLE_STRIP, 0, 4); + + openGLContext.extensions.glDisableVertexAttribArray(positionAttribute); + openGLContext.extensions.glBindBuffer(GL_ARRAY_BUFFER, 0); + } + + void setVisualPosition(float x, float y, float z) { + rotationXSlider.setValue(x, juce::sendNotification); + rotationYSlider.setValue(y, juce::sendNotification); + rotationZSlider.setValue(z, juce::sendNotification); + } + + void paint(juce::Graphics& g) override + { + // No painting necessary as OpenGL handles rendering + } + + void resized() override + { + auto area = getLocalBounds(); + rotationXSlider.setBounds(area.removeFromTop(20)); + rotationYSlider.setBounds(area.removeFromTop(20)); + rotationZSlider.setBounds(area.removeFromTop(20)); + cameraXSlider.setBounds(area.removeFromTop(20)); + cameraYSlider.setBounds(area.removeFromTop(20)); + cameraZSlider.setBounds(area.removeFromTop(20)); + } + + void sliderValueChanged(juce::Slider* slider) override + { + if (slider == &rotationXSlider || slider == &rotationYSlider || slider == &rotationZSlider || + slider == &cameraXSlider || slider == &cameraYSlider || slider == &cameraZSlider) + { + repaint(); + } + } + + void createShaders() + { + vertexShader = R"( + attribute vec2 position; + varying vec2 fragCoord; + void main() + { + fragCoord = position * 0.5 + 0.5; // Transform to [0, 1] range + gl_Position = vec4(position, 0.0, 1.0); + } + )"; + + fragmentShader = R"( + const float pi = 3.14159265359; + const float tau = 2. * pi; + + uniform vec3 iResolution; + uniform float iTime; + uniform vec3 iPosition; + uniform vec3 iCameraPosition; + varying vec2 fragCoord; + + float noise(vec3 p) { + return fract(sin(dot(p, vec3(12.9898, 78.233, 45.164))) * 43758.5453); + } + + float noise(vec3 p, float time) { + return noise(p + vec3(time)); + } + + vec3 trig_palette(vec3 a, vec3 b, vec3 c, vec3 d, float t) { + return a + b * cos((tau * c * t + d)); + } + + float sphere_distance(vec3 p) { + return length(p); + } + + vec3 sphere_0_position() { + return vec3(0.); + } + + float sphere_0_radius() { + return 1.; + } + + vec3 sphere_0_color(vec3 p) { + vec2 xy = p.xy - sphere_0_position().xy; + float ang = atan(xy.y, xy.x); + vec3 pal = trig_palette(vec3(.8, .2, .2), vec3(.2, .2, .2), vec3(1., 1., 1.), vec3(.0, .33, .67), ang / tau); + return mix(vec3(1., 0., 0.), pal * 0.5, min(length(xy), 1.)); // Make the sphere color less bright + } + + float sphere_0_density(vec3 p) { + vec3 d = sphere_0_position() - p; + float l = length(d); + vec3 q = sphere_0_radius() * normalize(d); + return abs(noise(q * 2., iTime / 5. - l)); + } + + float sphere_0_distance(vec3 p) { + return sphere_distance(p - sphere_0_position()) - 1.; + } + + vec3 sphere_1_position() { + return iPosition; + } + + float sphere_1_radius() { + return 1.0; + } + + vec3 sphere_1_color(vec3 p) { + vec2 xy = p.xy - sphere_1_position().xy; + float ang = atan(xy.y, xy.x); + vec3 pal = trig_palette(vec3(.2, .6, .3), vec3(.2, .2, .3), vec3(1., 1., 1.), vec3(.0, .33, .67), ang / tau); + return mix(vec3(0., 1., 0.), pal, min(length(xy), 1.)); + } + + float sphere_1_density(vec3 p) { + vec3 d = sphere_1_position() - p; + float l = length(d); + vec3 q = sphere_1_radius() * normalize(d); + return abs(noise(q * 1.5, iTime / 5. - l)); + } + + float sphere_1_distance(vec3 p) { + return sphere_distance(p - sphere_1_position()) - 0.5f; + } + + float scene_distance(vec3 p) { + float s0 = sphere_0_distance(p); + float s1 = sphere_1_distance(p); + return min(s0, s1); + } + + vec4 scene_color(vec3 p) { + float s0 = sphere_0_distance(p); + float s1 = sphere_1_distance(p); + if (s0 < s1) { + return vec4(sphere_0_color(p), 0.1); // 0.1 alpha for more transparency + } else { + return vec4(sphere_1_color(p), 1.0); // Opaque + } + } + + float scene_density(vec3 p) { + float s0 = sphere_0_distance(p); + float s1 = sphere_1_distance(p); + if (s0 < s1) { + return sphere_0_density(p); + } else { + return sphere_1_density(p); + } + } + + vec4 scene_halo(vec3 p, vec3 d) { + float sd0 = 2. * sphere_0_distance(p) / 1.8; + vec3 v0 = p - sphere_0_position(); + float a0 = 2. / (1. + sd0 * sd0 * sd0); + float sd1 = 2. * sphere_1_distance(p) / 1.8; + vec3 v1 = p - sphere_1_position(); + float a1 = 2. / (1. + sd1 * sd1 * sd1); + float sum_a = a0 + a1; + vec3 rgb = (a0 * sphere_0_color(p) + a1 * sphere_1_color(p)) / sum_a; + float a = 1. - (1. - a0) * (1. - a1); + float density = .2 + .8 * scene_density(p); + return vec4(rgb, density * a); + } + + void mainImage(out vec4 rgba, in vec2 coords) { + coords = (2. * coords.xy - iResolution.xy) / iResolution.x; + vec3 ro = iCameraPosition; // Use camera position + vec3 rd = normalize(vec3(coords.xy, 1.)); + + // Initialize total distance and maximum steps + float totalDistance = 0.0; + int maxSteps = 150; + float maxDistance = 100.0; + vec4 halo = vec4(0.); + + for (int i = 0; i < maxSteps; ++i) { + float sd = min(scene_distance(ro), 0.25); + if (totalDistance > maxDistance || sd < 0.01) { + break; + } + ro += sd * rd; + totalDistance += sd; + vec4 halo_i = scene_halo(ro, rd); + halo += (1. - halo.a) * sd * vec4(halo_i.rgb * halo_i.a, halo_i.a); + } + if (totalDistance > maxDistance) { + discard; + } + // Remove the star field + vec3 rgb = vec3(0.0); + + rgba = vec4(halo.rgb + (1. - halo.a) * rgb, 1.); + } + + void main() + { + mainImage(gl_FragColor, fragCoord.xy * iResolution.xy); + } + )"; + + std::unique_ptr newShader(new juce::OpenGLShaderProgram(openGLContext)); + juce::String statusText; + + if (newShader->addVertexShader(juce::OpenGLHelpers::translateVertexShaderToV3(vertexShader)) && + newShader->addFragmentShader(juce::OpenGLHelpers::translateFragmentShaderToV3(fragmentShader)) && + newShader->link()) + { + shader.reset(newShader.release()); + shader->use(); + + uniforms.reset(new Uniforms(*shader)); + + positionAttribute = glGetAttribLocation(shader->getProgramID(), "position"); + + statusText = "GLSL: v" + juce::String(juce::OpenGLShaderProgram::getLanguageVersion(), 2); + } + else + { + auto vertexError = newShader->getLastError(); + DBG("Vertex Shader Error: " + vertexError); // Output the error to the console + + auto fragmentError = newShader->getLastError(); + DBG("Fragment Shader Error: " + fragmentError); // Output the error to the console + + statusText = newShader->getLastError(); + DBG("Linking Error: " + statusText); // Output the error to the console + + jassertfalse; // Break in debugger to inspect the issue + } + } + +private: + GLuint vertexBuffer; + GLint positionAttribute; + juce::String vertexShader; + juce::String fragmentShader; + + std::unique_ptr shader; + + struct Uniforms + { + explicit Uniforms(juce::OpenGLShaderProgram& shaderProgram) + { + iResolution.reset(createUniform(shaderProgram, "iResolution")); + iTime.reset(createUniform(shaderProgram, "iTime")); + iPosition.reset(createUniform(shaderProgram, "iPosition")); + iCameraPosition.reset(createUniform(shaderProgram, "iCameraPosition")); + } + + std::unique_ptr iResolution; + std::unique_ptr iTime; + std::unique_ptr iPosition; + std::unique_ptr iCameraPosition; + + private: + static juce::OpenGLShaderProgram::Uniform* createUniform(juce::OpenGLShaderProgram& shaderProgram, const juce::String& uniformName) + { + using namespace ::juce::gl; + auto location = glGetUniformLocation(shaderProgram.getProgramID(), uniformName.toRawUTF8()); + if (location < 0) + return nullptr; + + return new juce::OpenGLShaderProgram::Uniform(shaderProgram, uniformName.toRawUTF8()); + } + }; + + std::unique_ptr uniforms; + + juce::Slider rotationXSlider; + juce::Slider rotationYSlider; + juce::Slider rotationZSlider; + juce::Slider cameraXSlider; + juce::Slider cameraYSlider; + juce::Slider cameraZSlider; + + JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(MainContentComponent) +}; \ No newline at end of file