/* ============================================================================== This file is part of the JUCE examples. Copyright (c) 2020 - Raw Material Software Limited The code included in this file is provided under the terms of the ISC license http://www.isc.org/downloads/software-support-policy/isc-license. Permission To use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted provided that the above copyright notice and this permission notice appear in all copies. THE SOFTWARE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE DISCLAIMED. ============================================================================== */ /******************************************************************************* The block below describes the properties of this PIP. A PIP is a short snippet of code that can be read by the Projucer and used to generate a JUCE project. BEGIN_JUCE_PIP_METADATA name: OpenGLDemo version: 1.0.0 vendor: JUCE website: http://juce.com description: Simple 3D OpenGL application. dependencies: juce_core, juce_data_structures, juce_events, juce_graphics, juce_gui_basics, juce_gui_extra, juce_opengl exporters: xcode_mac, vs2019, linux_make, androidstudio, xcode_iphone moduleFlags: JUCE_STRICT_REFCOUNTEDPOINTER=1 type: Component mainClass: OpenGLDemo useLocalCopy: 1 END_JUCE_PIP_METADATA *******************************************************************************/ #pragma once #include "../Assets/DemoUtilities.h" #include "../Assets/WavefrontObjParser.h" struct OpenGLUtils { //============================================================================== /** Vertex data to be passed to the shaders. For the purposes of this demo, each vertex will have a 3D position, a colour and a 2D texture co-ordinate. Of course you can ignore these or manipulate them in the shader programs but are some useful defaults to work from. */ struct Vertex { float position[3]; float normal[3]; float colour[4]; float texCoord[2]; }; //============================================================================== // This class just manages the attributes that the demo shaders use. struct Attributes { explicit Attributes (OpenGLShaderProgram& shader) { position .reset (createAttribute (shader, "position")); normal .reset (createAttribute (shader, "normal")); sourceColour .reset (createAttribute (shader, "sourceColour")); textureCoordIn.reset (createAttribute (shader, "textureCoordIn")); } void enable() { using namespace ::juce::gl; if (position.get() != nullptr) { glVertexAttribPointer (position->attributeID, 3, GL_FLOAT, GL_FALSE, sizeof (Vertex), nullptr); glEnableVertexAttribArray (position->attributeID); } if (normal.get() != nullptr) { glVertexAttribPointer (normal->attributeID, 3, GL_FLOAT, GL_FALSE, sizeof (Vertex), (GLvoid*) (sizeof (float) * 3)); glEnableVertexAttribArray (normal->attributeID); } if (sourceColour.get() != nullptr) { glVertexAttribPointer (sourceColour->attributeID, 4, GL_FLOAT, GL_FALSE, sizeof (Vertex), (GLvoid*) (sizeof (float) * 6)); glEnableVertexAttribArray (sourceColour->attributeID); } if (textureCoordIn.get() != nullptr) { glVertexAttribPointer (textureCoordIn->attributeID, 2, GL_FLOAT, GL_FALSE, sizeof (Vertex), (GLvoid*) (sizeof (float) * 10)); glEnableVertexAttribArray (textureCoordIn->attributeID); } } void disable() { using namespace ::juce::gl; if (position.get() != nullptr) glDisableVertexAttribArray (position->attributeID); if (normal.get() != nullptr) glDisableVertexAttribArray (normal->attributeID); if (sourceColour.get() != nullptr) glDisableVertexAttribArray (sourceColour->attributeID); if (textureCoordIn.get() != nullptr) glDisableVertexAttribArray (textureCoordIn->attributeID); } std::unique_ptr position, normal, sourceColour, textureCoordIn; private: static OpenGLShaderProgram::Attribute* createAttribute (OpenGLShaderProgram& shader, const char* attributeName) { using namespace ::juce::gl; if (glGetAttribLocation (shader.getProgramID(), attributeName) < 0) return nullptr; return new OpenGLShaderProgram::Attribute (shader, attributeName); } }; //============================================================================== // This class just manages the uniform values that the demo shaders use. struct Uniforms { explicit Uniforms (OpenGLShaderProgram& shader) { projectionMatrix.reset (createUniform (shader, "projectionMatrix")); viewMatrix .reset (createUniform (shader, "viewMatrix")); texture .reset (createUniform (shader, "demoTexture")); lightPosition .reset (createUniform (shader, "lightPosition")); bouncingNumber .reset (createUniform (shader, "bouncingNumber")); } std::unique_ptr projectionMatrix, viewMatrix, texture, lightPosition, bouncingNumber; private: static OpenGLShaderProgram::Uniform* createUniform (OpenGLShaderProgram& shader, const char* uniformName) { using namespace ::juce::gl; if (glGetUniformLocation (shader.getProgramID(), uniformName) < 0) return nullptr; return new OpenGLShaderProgram::Uniform (shader, uniformName); } }; //============================================================================== /** This loads a 3D model from an OBJ file and converts it into some vertex buffers that we can draw. */ struct Shape { Shape() { if (shapeFile.load (loadEntireAssetIntoString ("teapot.obj")).wasOk()) for (auto* s : shapeFile.shapes) vertexBuffers.add (new VertexBuffer (*s)); } void draw (Attributes& attributes) { using namespace ::juce::gl; for (auto* vertexBuffer : vertexBuffers) { vertexBuffer->bind(); attributes.enable(); glDrawElements (GL_TRIANGLES, vertexBuffer->numIndices, GL_UNSIGNED_INT, nullptr); attributes.disable(); } } private: struct VertexBuffer { explicit VertexBuffer (WavefrontObjFile::Shape& shape) { using namespace ::juce::gl; numIndices = shape.mesh.indices.size(); glGenBuffers (1, &vertexBuffer); glBindBuffer (GL_ARRAY_BUFFER, vertexBuffer); Array vertices; createVertexListFromMesh (shape.mesh, vertices, Colours::green); glBufferData (GL_ARRAY_BUFFER, vertices.size() * (int) sizeof (Vertex), vertices.getRawDataPointer(), GL_STATIC_DRAW); glGenBuffers (1, &indexBuffer); glBindBuffer (GL_ELEMENT_ARRAY_BUFFER, indexBuffer); glBufferData (GL_ELEMENT_ARRAY_BUFFER, numIndices * (int) sizeof (juce::uint32), shape.mesh.indices.getRawDataPointer(), GL_STATIC_DRAW); } ~VertexBuffer() { using namespace ::juce::gl; glDeleteBuffers (1, &vertexBuffer); glDeleteBuffers (1, &indexBuffer); } void bind() { using namespace ::juce::gl; glBindBuffer (GL_ARRAY_BUFFER, vertexBuffer); glBindBuffer (GL_ELEMENT_ARRAY_BUFFER, indexBuffer); } GLuint vertexBuffer, indexBuffer; int numIndices; JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (VertexBuffer) }; WavefrontObjFile shapeFile; OwnedArray vertexBuffers; static void createVertexListFromMesh (const WavefrontObjFile::Mesh& mesh, Array& list, Colour colour) { auto scale = 0.2f; WavefrontObjFile::TextureCoord defaultTexCoord = { 0.5f, 0.5f }; WavefrontObjFile::Vertex defaultNormal = { 0.5f, 0.5f, 0.5f }; for (int i = 0; i < mesh.vertices.size(); ++i) { auto& v = mesh.vertices.getReference (i); auto& n = (i < mesh.normals.size() ? mesh.normals.getReference (i) : defaultNormal); auto& tc = (i < mesh.textureCoords.size() ? mesh.textureCoords.getReference (i) : defaultTexCoord); list.add ({ { scale * v.x, scale * v.y, scale * v.z, }, { scale * n.x, scale * n.y, scale * n.z, }, { colour.getFloatRed(), colour.getFloatGreen(), colour.getFloatBlue(), colour.getFloatAlpha() }, { tc.x, tc.y } }); } } }; //============================================================================== struct ShaderPreset { const char* name; const char* vertexShader; const char* fragmentShader; }; static Array getPresets() { #define SHADER_DEMO_HEADER \ "/* This is a live OpenGL Shader demo.\n" \ " Edit the shader program below and it will be \n" \ " compiled and applied to the model above!\n" \ "*/\n\n" ShaderPreset presets[] = { { "Texture + Lighting", SHADER_DEMO_HEADER "attribute vec4 position;\n" "attribute vec4 normal;\n" "attribute vec4 sourceColour;\n" "attribute vec2 textureCoordIn;\n" "\n" "uniform mat4 projectionMatrix;\n" "uniform mat4 viewMatrix;\n" "uniform vec4 lightPosition;\n" "\n" "varying vec4 destinationColour;\n" "varying vec2 textureCoordOut;\n" "varying float lightIntensity;\n" "\n" "void main()\n" "{\n" " destinationColour = sourceColour;\n" " textureCoordOut = textureCoordIn;\n" "\n" " vec4 light = viewMatrix * lightPosition;\n" " lightIntensity = dot (light, normal);\n" "\n" " gl_Position = projectionMatrix * viewMatrix * position;\n" "}\n", SHADER_DEMO_HEADER #if JUCE_OPENGL_ES "varying lowp vec4 destinationColour;\n" "varying lowp vec2 textureCoordOut;\n" "varying highp float lightIntensity;\n" #else "varying vec4 destinationColour;\n" "varying vec2 textureCoordOut;\n" "varying float lightIntensity;\n" #endif "\n" "uniform sampler2D demoTexture;\n" "\n" "void main()\n" "{\n" #if JUCE_OPENGL_ES " highp float l = max (0.3, lightIntensity * 0.3);\n" " highp vec4 colour = vec4 (l, l, l, 1.0);\n" #else " float l = max (0.3, lightIntensity * 0.3);\n" " vec4 colour = vec4 (l, l, l, 1.0);\n" #endif " gl_FragColor = colour * texture2D (demoTexture, textureCoordOut);\n" "}\n" }, { "Textured", SHADER_DEMO_HEADER "attribute vec4 position;\n" "attribute vec4 sourceColour;\n" "attribute vec2 textureCoordIn;\n" "\n" "uniform mat4 projectionMatrix;\n" "uniform mat4 viewMatrix;\n" "\n" "varying vec4 destinationColour;\n" "varying vec2 textureCoordOut;\n" "\n" "void main()\n" "{\n" " destinationColour = sourceColour;\n" " textureCoordOut = textureCoordIn;\n" " gl_Position = projectionMatrix * viewMatrix * position;\n" "}\n", SHADER_DEMO_HEADER #if JUCE_OPENGL_ES "varying lowp vec4 destinationColour;\n" "varying lowp vec2 textureCoordOut;\n" #else "varying vec4 destinationColour;\n" "varying vec2 textureCoordOut;\n" #endif "\n" "uniform sampler2D demoTexture;\n" "\n" "void main()\n" "{\n" " gl_FragColor = texture2D (demoTexture, textureCoordOut);\n" "}\n" }, { "Flat Colour", SHADER_DEMO_HEADER "attribute vec4 position;\n" "attribute vec4 sourceColour;\n" "attribute vec2 textureCoordIn;\n" "\n" "uniform mat4 projectionMatrix;\n" "uniform mat4 viewMatrix;\n" "\n" "varying vec4 destinationColour;\n" "varying vec2 textureCoordOut;\n" "\n" "void main()\n" "{\n" " destinationColour = sourceColour;\n" " textureCoordOut = textureCoordIn;\n" " gl_Position = projectionMatrix * viewMatrix * position;\n" "}\n", SHADER_DEMO_HEADER #if JUCE_OPENGL_ES "varying lowp vec4 destinationColour;\n" "varying lowp vec2 textureCoordOut;\n" #else "varying vec4 destinationColour;\n" "varying vec2 textureCoordOut;\n" #endif "\n" "void main()\n" "{\n" " gl_FragColor = destinationColour;\n" "}\n" }, { "Rainbow", SHADER_DEMO_HEADER "attribute vec4 position;\n" "attribute vec4 sourceColour;\n" "attribute vec2 textureCoordIn;\n" "\n" "uniform mat4 projectionMatrix;\n" "uniform mat4 viewMatrix;\n" "\n" "varying vec4 destinationColour;\n" "varying vec2 textureCoordOut;\n" "\n" "varying float xPos;\n" "varying float yPos;\n" "varying float zPos;\n" "\n" "void main()\n" "{\n" " vec4 v = vec4 (position);\n" " xPos = clamp (v.x, 0.0, 1.0);\n" " yPos = clamp (v.y, 0.0, 1.0);\n" " zPos = clamp (v.z, 0.0, 1.0);\n" " gl_Position = projectionMatrix * viewMatrix * position;\n" "}", SHADER_DEMO_HEADER #if JUCE_OPENGL_ES "varying lowp vec4 destinationColour;\n" "varying lowp vec2 textureCoordOut;\n" "varying lowp float xPos;\n" "varying lowp float yPos;\n" "varying lowp float zPos;\n" #else "varying vec4 destinationColour;\n" "varying vec2 textureCoordOut;\n" "varying float xPos;\n" "varying float yPos;\n" "varying float zPos;\n" #endif "\n" "void main()\n" "{\n" " gl_FragColor = vec4 (xPos, yPos, zPos, 1.0);\n" "}" }, { "Changing Colour", SHADER_DEMO_HEADER "attribute vec4 position;\n" "attribute vec2 textureCoordIn;\n" "\n" "uniform mat4 projectionMatrix;\n" "uniform mat4 viewMatrix;\n" "\n" "varying vec2 textureCoordOut;\n" "\n" "void main()\n" "{\n" " textureCoordOut = textureCoordIn;\n" " gl_Position = projectionMatrix * viewMatrix * position;\n" "}\n", SHADER_DEMO_HEADER "#define PI 3.1415926535897932384626433832795\n" "\n" #if JUCE_OPENGL_ES "precision mediump float;\n" "varying lowp vec2 textureCoordOut;\n" #else "varying vec2 textureCoordOut;\n" #endif "uniform float bouncingNumber;\n" "\n" "void main()\n" "{\n" " float b = bouncingNumber;\n" " float n = b * PI * 2.0;\n" " float sn = (sin (n * textureCoordOut.x) * 0.5) + 0.5;\n" " float cn = (sin (n * textureCoordOut.y) * 0.5) + 0.5;\n" "\n" " vec4 col = vec4 (b, sn, cn, 1.0);\n" " gl_FragColor = col;\n" "}\n" }, { "Simple Light", SHADER_DEMO_HEADER "attribute vec4 position;\n" "attribute vec4 normal;\n" "\n" "uniform mat4 projectionMatrix;\n" "uniform mat4 viewMatrix;\n" "uniform vec4 lightPosition;\n" "\n" "varying float lightIntensity;\n" "\n" "void main()\n" "{\n" " vec4 light = viewMatrix * lightPosition;\n" " lightIntensity = dot (light, normal);\n" "\n" " gl_Position = projectionMatrix * viewMatrix * position;\n" "}\n", SHADER_DEMO_HEADER #if JUCE_OPENGL_ES "varying highp float lightIntensity;\n" #else "varying float lightIntensity;\n" #endif "\n" "void main()\n" "{\n" #if JUCE_OPENGL_ES " highp float l = lightIntensity * 0.25;\n" " highp vec4 colour = vec4 (l, l, l, 1.0);\n" #else " float l = lightIntensity * 0.25;\n" " vec4 colour = vec4 (l, l, l, 1.0);\n" #endif "\n" " gl_FragColor = colour;\n" "}\n" }, { "Flattened", SHADER_DEMO_HEADER "attribute vec4 position;\n" "attribute vec4 normal;\n" "\n" "uniform mat4 projectionMatrix;\n" "uniform mat4 viewMatrix;\n" "uniform vec4 lightPosition;\n" "\n" "varying float lightIntensity;\n" "\n" "void main()\n" "{\n" " vec4 light = viewMatrix * lightPosition;\n" " lightIntensity = dot (light, normal);\n" "\n" " vec4 v = vec4 (position);\n" " v.z = v.z * 0.1;\n" "\n" " gl_Position = projectionMatrix * viewMatrix * v;\n" "}\n", SHADER_DEMO_HEADER #if JUCE_OPENGL_ES "varying highp float lightIntensity;\n" #else "varying float lightIntensity;\n" #endif "\n" "void main()\n" "{\n" #if JUCE_OPENGL_ES " highp float l = lightIntensity * 0.25;\n" " highp vec4 colour = vec4 (l, l, l, 1.0);\n" #else " float l = lightIntensity * 0.25;\n" " vec4 colour = vec4 (l, l, l, 1.0);\n" #endif "\n" " gl_FragColor = colour;\n" "}\n" }, { "Toon Shader", SHADER_DEMO_HEADER "attribute vec4 position;\n" "attribute vec4 normal;\n" "\n" "uniform mat4 projectionMatrix;\n" "uniform mat4 viewMatrix;\n" "uniform vec4 lightPosition;\n" "\n" "varying float lightIntensity;\n" "\n" "void main()\n" "{\n" " vec4 light = viewMatrix * lightPosition;\n" " lightIntensity = dot (light, normal);\n" "\n" " gl_Position = projectionMatrix * viewMatrix * position;\n" "}\n", SHADER_DEMO_HEADER #if JUCE_OPENGL_ES "varying highp float lightIntensity;\n" #else "varying float lightIntensity;\n" #endif "\n" "void main()\n" "{\n" #if JUCE_OPENGL_ES " highp float intensity = lightIntensity * 0.5;\n" " highp vec4 colour;\n" #else " float intensity = lightIntensity * 0.5;\n" " vec4 colour;\n" #endif "\n" " if (intensity > 0.95)\n" " colour = vec4 (1.0, 0.5, 0.5, 1.0);\n" " else if (intensity > 0.5)\n" " colour = vec4 (0.6, 0.3, 0.3, 1.0);\n" " else if (intensity > 0.25)\n" " colour = vec4 (0.4, 0.2, 0.2, 1.0);\n" " else\n" " colour = vec4 (0.2, 0.1, 0.1, 1.0);\n" "\n" " gl_FragColor = colour;\n" "}\n" } }; return Array (presets, numElementsInArray (presets)); } //============================================================================== // These classes are used to load textures from the various sources that the demo uses.. struct DemoTexture { virtual ~DemoTexture() {} virtual bool applyTo (OpenGLTexture&) = 0; String name; }; struct DynamicTexture : public DemoTexture { DynamicTexture() { name = "Dynamically-generated texture"; } Image image; BouncingNumber x, y; bool applyTo (OpenGLTexture& texture) override { int size = 128; if (! image.isValid()) image = Image (Image::ARGB, size, size, true); { Graphics g (image); g.fillAll (Colours::lightcyan); g.setColour (Colours::darkred); g.drawRect (0, 0, size, size, 2); g.setColour (Colours::green); g.fillEllipse (x.getValue() * (float) size * 0.9f, y.getValue() * (float) size * 0.9f, (float) size * 0.1f, (float) size * 0.1f); g.setColour (Colours::black); g.setFont (40); g.drawFittedText (String (Time::getCurrentTime().getMilliseconds()), image.getBounds(), Justification::centred, 1); } texture.loadImage (image); return true; } }; static Image resizeImageToPowerOfTwo (Image image) { if (! (isPowerOfTwo (image.getWidth()) && isPowerOfTwo (image.getHeight()))) return image.rescaled (jmin (1024, nextPowerOfTwo (image.getWidth())), jmin (1024, nextPowerOfTwo (image.getHeight()))); return image; } struct BuiltInTexture : public DemoTexture { BuiltInTexture (const char* nm, const void* imageData, size_t imageSize) : image (resizeImageToPowerOfTwo (ImageFileFormat::loadFrom (imageData, imageSize))) { name = nm; } Image image; bool applyTo (OpenGLTexture& texture) override { texture.loadImage (image); return false; } }; struct TextureFromFile : public DemoTexture { TextureFromFile (const File& file) { name = file.getFileName(); image = resizeImageToPowerOfTwo (ImageFileFormat::loadFrom (file)); } Image image; bool applyTo (OpenGLTexture& texture) override { texture.loadImage (image); return false; } }; struct TextureFromAsset : public DemoTexture { TextureFromAsset (const char* assetName) { name = assetName; image = resizeImageToPowerOfTwo (getImageFromAssets (assetName)); } Image image; bool applyTo (OpenGLTexture& texture) override { texture.loadImage (image); return false; } }; }; //============================================================================== /** This is the main demo component - the GL context gets attached to it, and it implements the OpenGLRenderer callback so that it can do real GL work. */ class OpenGLDemo : public Component, private OpenGLRenderer, private AsyncUpdater { public: OpenGLDemo() { if (auto* peer = getPeer()) peer->setCurrentRenderingEngine (0); setOpaque (true); controlsOverlay.reset (new DemoControlsOverlay (*this)); addAndMakeVisible (controlsOverlay.get()); openGLContext.setRenderer (this); openGLContext.attachTo (*this); openGLContext.setContinuousRepainting (true); controlsOverlay->initialise(); setSize (500, 500); } ~OpenGLDemo() override { openGLContext.detach(); } void newOpenGLContextCreated() override { // nothing to do in this case - we'll initialise our shaders + textures // on demand, during the render callback. freeAllContextObjects(); if (controlsOverlay.get() != nullptr) controlsOverlay->updateShader(); } void openGLContextClosing() override { // When the context is about to close, you must use this callback to delete // any GPU resources while the context is still current. freeAllContextObjects(); if (lastTexture != nullptr) setTexture (lastTexture); } void freeAllContextObjects() { shape .reset(); shader .reset(); attributes.reset(); uniforms .reset(); texture .release(); } // This is a virtual method in OpenGLRenderer, and is called when it's time // to do your GL rendering. void renderOpenGL() override { using namespace ::juce::gl; const ScopedLock lock (mutex); jassert (OpenGLHelpers::isContextActive()); auto desktopScale = (float) openGLContext.getRenderingScale(); OpenGLHelpers::clear (getUIColourIfAvailable (LookAndFeel_V4::ColourScheme::UIColour::windowBackground, Colours::lightblue)); if (textureToUse != nullptr) if (! textureToUse->applyTo (texture)) textureToUse = nullptr; // First draw our background graphics to demonstrate the OpenGLGraphicsContext class if (doBackgroundDrawing) drawBackground2DStuff (desktopScale); updateShader(); // Check whether we need to compile a new shader if (shader.get() == nullptr) return; // Having used the juce 2D renderer, it will have messed-up a whole load of GL state, so // we need to initialise some important settings before doing our normal GL 3D drawing.. glEnable (GL_DEPTH_TEST); glDepthFunc (GL_LESS); glEnable (GL_BLEND); glBlendFunc (GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); glActiveTexture (GL_TEXTURE0); glEnable (GL_TEXTURE_2D); glViewport (0, 0, roundToInt (desktopScale * (float) bounds.getWidth()), roundToInt (desktopScale * (float) bounds.getHeight())); texture.bind(); glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT); glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT); shader->use(); if (uniforms->projectionMatrix.get() != nullptr) uniforms->projectionMatrix->setMatrix4 (getProjectionMatrix().mat, 1, false); if (uniforms->viewMatrix.get() != nullptr) uniforms->viewMatrix->setMatrix4 (getViewMatrix().mat, 1, false); if (uniforms->texture.get() != nullptr) uniforms->texture->set ((GLint) 0); if (uniforms->lightPosition.get() != nullptr) uniforms->lightPosition->set (-15.0f, 10.0f, 15.0f, 0.0f); if (uniforms->bouncingNumber.get() != nullptr) uniforms->bouncingNumber->set (bouncingNumber.getValue()); shape->draw (*attributes); // Reset the element buffers so child Components draw correctly glBindBuffer (GL_ARRAY_BUFFER, 0); glBindBuffer (GL_ELEMENT_ARRAY_BUFFER, 0); if (! controlsOverlay->isMouseButtonDownThreadsafe()) rotation += (float) rotationSpeed; } Matrix3D getProjectionMatrix() const { const ScopedLock lock (mutex); auto w = 1.0f / (scale + 0.1f); auto h = w * bounds.toFloat().getAspectRatio (false); return Matrix3D::fromFrustum (-w, w, -h, h, 4.0f, 30.0f); } Matrix3D getViewMatrix() const { const ScopedLock lock (mutex); auto viewMatrix = draggableOrientation.getRotationMatrix() * Vector3D (0.0f, 1.0f, -10.0f); auto rotationMatrix = Matrix3D::rotation ({ rotation, rotation, -0.3f }); return rotationMatrix * viewMatrix; } void setTexture (OpenGLUtils::DemoTexture* t) { lastTexture = textureToUse = t; } void setShaderProgram (const String& vertexShader, const String& fragmentShader) { newVertexShader = vertexShader; newFragmentShader = fragmentShader; } void paint (Graphics&) override {} void resized() override { const ScopedLock lock (mutex); bounds = getLocalBounds(); controlsOverlay->setBounds (bounds); draggableOrientation.setViewport (bounds); } Rectangle bounds; Draggable3DOrientation draggableOrientation; bool doBackgroundDrawing = false; float scale = 0.5f, rotationSpeed = 0.0f; BouncingNumber bouncingNumber; CriticalSection mutex; private: void handleAsyncUpdate() override { controlsOverlay->statusLabel.setText (statusText, dontSendNotification); } void drawBackground2DStuff (float desktopScale) { // Create an OpenGLGraphicsContext that will draw into this GL window.. std::unique_ptr glRenderer (createOpenGLGraphicsContext (openGLContext, roundToInt (desktopScale * (float) bounds.getWidth()), roundToInt (desktopScale * (float) bounds.getHeight()))); if (glRenderer.get() != nullptr) { Graphics g (*glRenderer); g.addTransform (AffineTransform::scale (desktopScale)); for (auto s : stars) { auto size = 0.25f; // This stuff just creates a spinning star shape and fills it.. Path p; p.addStar ({ (float) bounds.getWidth() * s.x.getValue(), (float) bounds.getHeight() * s.y.getValue() }, 7, (float) bounds.getHeight() * size * 0.5f, (float) bounds.getHeight() * size, s.angle.getValue()); auto hue = s.hue.getValue(); g.setGradientFill (ColourGradient (Colours::green.withRotatedHue (hue).withAlpha (0.8f), 0, 0, Colours::red.withRotatedHue (hue).withAlpha (0.5f), 0, (float) bounds.getHeight(), false)); g.fillPath (p); } } } OpenGLContext openGLContext; //============================================================================== /** This component sits on top of the main GL demo, and contains all the sliders and widgets that control things. */ class DemoControlsOverlay : public Component, private CodeDocument::Listener, private Slider::Listener, private Timer { public: DemoControlsOverlay (OpenGLDemo& d) : demo (d) { addAndMakeVisible (statusLabel); statusLabel.setJustificationType (Justification::topLeft); statusLabel.setFont (Font (14.0f)); addAndMakeVisible (sizeSlider); sizeSlider.setRange (0.0, 1.0, 0.001); sizeSlider.addListener (this); addAndMakeVisible (zoomLabel); zoomLabel.attachToComponent (&sizeSlider, true); addAndMakeVisible (speedSlider); speedSlider.setRange (0.0, 0.5, 0.001); speedSlider.addListener (this); speedSlider.setSkewFactor (0.5f); addAndMakeVisible (speedLabel); speedLabel.attachToComponent (&speedSlider, true); addAndMakeVisible (showBackgroundToggle); showBackgroundToggle.onClick = [this] { demo.doBackgroundDrawing = showBackgroundToggle.getToggleState(); }; addAndMakeVisible (tabbedComp); tabbedComp.setTabBarDepth (25); tabbedComp.setColour (TabbedButtonBar::tabTextColourId, Colours::grey); tabbedComp.addTab ("Vertex", Colours::transparentBlack, &vertexEditorComp, false); tabbedComp.addTab ("Fragment", Colours::transparentBlack, &fragmentEditorComp, false); vertexDocument.addListener (this); fragmentDocument.addListener (this); textures.add (new OpenGLUtils::TextureFromAsset ("portmeirion.jpg")); textures.add (new OpenGLUtils::TextureFromAsset ("tile_background.png")); textures.add (new OpenGLUtils::TextureFromAsset ("juce_icon.png")); textures.add (new OpenGLUtils::DynamicTexture()); addAndMakeVisible (textureBox); textureBox.onChange = [this] { selectTexture (textureBox.getSelectedId()); }; updateTexturesList(); addAndMakeVisible (presetBox); presetBox.onChange = [this] { selectPreset (presetBox.getSelectedItemIndex()); }; auto presets = OpenGLUtils::getPresets(); for (int i = 0; i < presets.size(); ++i) presetBox.addItem (presets[i].name, i + 1); addAndMakeVisible (presetLabel); presetLabel.attachToComponent (&presetBox, true); addAndMakeVisible (textureLabel); textureLabel.attachToComponent (&textureBox, true); lookAndFeelChanged(); } void initialise() { showBackgroundToggle.setToggleState (false, sendNotification); textureBox.setSelectedItemIndex (0); presetBox .setSelectedItemIndex (0); speedSlider.setValue (0.01); sizeSlider .setValue (0.5); } void resized() override { auto area = getLocalBounds().reduced (4); auto top = area.removeFromTop (75); auto sliders = top.removeFromRight (area.getWidth() / 2); showBackgroundToggle.setBounds (sliders.removeFromBottom (25)); speedSlider .setBounds (sliders.removeFromBottom (25)); sizeSlider .setBounds (sliders.removeFromBottom (25)); top.removeFromRight (70); statusLabel.setBounds (top); auto shaderArea = area.removeFromBottom (area.getHeight() / 2); auto presets = shaderArea.removeFromTop (25); presets.removeFromLeft (100); presetBox.setBounds (presets.removeFromLeft (150)); presets.removeFromLeft (100); textureBox.setBounds (presets); shaderArea.removeFromTop (4); tabbedComp.setBounds (shaderArea); } bool isMouseButtonDownThreadsafe() const { return buttonDown; } void mouseDown (const MouseEvent& e) override { const ScopedLock lock (demo.mutex); demo.draggableOrientation.mouseDown (e.getPosition()); buttonDown = true; } void mouseDrag (const MouseEvent& e) override { const ScopedLock lock (demo.mutex); demo.draggableOrientation.mouseDrag (e.getPosition()); } void mouseUp (const MouseEvent&) override { buttonDown = false; } void mouseWheelMove (const MouseEvent&, const MouseWheelDetails& d) override { sizeSlider.setValue (sizeSlider.getValue() + d.deltaY); } void mouseMagnify (const MouseEvent&, float magnifyAmmount) override { sizeSlider.setValue (sizeSlider.getValue() + magnifyAmmount - 1.0f); } void selectPreset (int preset) { const auto& p = OpenGLUtils::getPresets()[preset]; vertexDocument .replaceAllContent (p.vertexShader); fragmentDocument.replaceAllContent (p.fragmentShader); startTimer (1); } void selectTexture (int itemID) { if (itemID == 1000) { textureFileChooser = std::make_unique ("Choose an image to open...", File::getSpecialLocation (File::userPicturesDirectory), "*.jpg;*.jpeg;*.png;*.gif"); auto chooserFlags = FileBrowserComponent::openMode | FileBrowserComponent::canSelectFiles; textureFileChooser->launchAsync (chooserFlags, [this] (const FileChooser& fc) { if (fc.getResult() == File{}) return; textures.add (new OpenGLUtils::TextureFromFile (fc.getResult())); updateTexturesList(); textureBox.setSelectedId (textures.size()); }); } else { if (auto* t = textures[itemID - 1]) demo.setTexture (t); } } void updateTexturesList() { textureBox.clear(); for (int i = 0; i < textures.size(); ++i) textureBox.addItem (textures.getUnchecked (i)->name, i + 1); textureBox.addSeparator(); textureBox.addItem ("Load from a file...", 1000); } void updateShader() { startTimer (10); } Label statusLabel; private: void sliderValueChanged (Slider*) override { const ScopedLock lock (demo.mutex); demo.scale = (float) sizeSlider .getValue(); demo.rotationSpeed = (float) speedSlider.getValue(); } enum { shaderLinkDelay = 500 }; void codeDocumentTextInserted (const String& /*newText*/, int /*insertIndex*/) override { startTimer (shaderLinkDelay); } void codeDocumentTextDeleted (int /*startIndex*/, int /*endIndex*/) override { startTimer (shaderLinkDelay); } void timerCallback() override { stopTimer(); demo.setShaderProgram (vertexDocument .getAllContent(), fragmentDocument.getAllContent()); } void lookAndFeelChanged() override { auto editorBackground = getUIColourIfAvailable (LookAndFeel_V4::ColourScheme::UIColour::windowBackground, Colours::white); for (int i = tabbedComp.getNumTabs(); i >= 0; --i) tabbedComp.setTabBackgroundColour (i, editorBackground); vertexEditorComp .setColour (CodeEditorComponent::backgroundColourId, editorBackground); fragmentEditorComp.setColour (CodeEditorComponent::backgroundColourId, editorBackground); } OpenGLDemo& demo; Label speedLabel { {}, "Speed:" }, zoomLabel { {}, "Zoom:" }; CodeDocument vertexDocument, fragmentDocument; CodeEditorComponent vertexEditorComp { vertexDocument, nullptr }, fragmentEditorComp { fragmentDocument, nullptr }; TabbedComponent tabbedComp { TabbedButtonBar::TabsAtLeft }; ComboBox presetBox, textureBox; Label presetLabel { {}, "Shader Preset:" }, textureLabel { {}, "Texture:" }; Slider speedSlider, sizeSlider; ToggleButton showBackgroundToggle { "Draw 2D graphics in background" }; OwnedArray textures; std::unique_ptr textureFileChooser; std::atomic buttonDown { false }; JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (DemoControlsOverlay) }; std::unique_ptr controlsOverlay; float rotation = 0.0f; std::unique_ptr shader; std::unique_ptr shape; std::unique_ptr attributes; std::unique_ptr uniforms; OpenGLTexture texture; OpenGLUtils::DemoTexture* textureToUse = nullptr; OpenGLUtils::DemoTexture* lastTexture = nullptr; String newVertexShader, newFragmentShader, statusText; struct BackgroundStar { SlowerBouncingNumber x, y, hue, angle; }; BackgroundStar stars[3]; //============================================================================== void updateShader() { if (newVertexShader.isNotEmpty() || newFragmentShader.isNotEmpty()) { std::unique_ptr newShader (new OpenGLShaderProgram (openGLContext)); if (newShader->addVertexShader (OpenGLHelpers::translateVertexShaderToV3 (newVertexShader)) && newShader->addFragmentShader (OpenGLHelpers::translateFragmentShaderToV3 (newFragmentShader)) && newShader->link()) { shape .reset(); attributes.reset(); uniforms .reset(); shader.reset (newShader.release()); shader->use(); shape .reset (new OpenGLUtils::Shape ()); attributes.reset (new OpenGLUtils::Attributes (*shader)); uniforms .reset (new OpenGLUtils::Uniforms (*shader)); statusText = "GLSL: v" + String (OpenGLShaderProgram::getLanguageVersion(), 2); } else { statusText = newShader->getLastError(); } triggerAsyncUpdate(); newVertexShader = {}; newFragmentShader = {}; } } JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (OpenGLDemo) };