Visual Particle System in an OpenGlWidget

Hello everyone,

I am using VCV Rack in my Laptop Orchestra class, and my final project is to create my own piece and build patches in Rack to perform it. I also recently participated in a hackathon which inspired me to keep writing C++ code! From these two experiences, I came up with a vision to make my own Rack module which would allow the audience to visualize the performance of the piece I create.

I am fairly new to creating Rack plugins/modules, and I know some DSP in C++. What resources are available for me to start developing my own module, besides the tutorial which goes over how to setup the SDK?

I already created OpenGl widget for my module called “Modular Forecast.”

T.V. Image from: File:TV-T&PC.svg - Wikimedia Commons

I most likely won’t use this image in the release because although the website says it’s public domain, I am still skeptical about running into copyright issues. I just am using it as my own placeholder for now to visualize what this module might look like.

My idea for this is to be able to take the audio input of a patch wired to one of the weather labels and generate weather related graphics on the black screen (OpenGlWidget) inside the T.V. For example, whatever patch is plugged into the ‘Snow’ input would generate snowflakes falling on the screen when audio is generated. To make the module more interactive, the density of the snowflake precipitation would be determined by characteristics of the audio going through the ‘Snow’ input, such as amplitude and frequency. I want to challenge myself to use a third-party library to measure these characteristics, and I am familiar with Soundpipe, so I thought it would be a good idea to try using that.

However, I am stuck on the concept of rendering the particle simulation of snow/rain inside the module itself. Currently, the particle simulation algorithm hasn’t been implemented yet, but I’m just trying to render some basic shapes. I have tried looking at source code for other modules, but most of them use nanovg which I am not familiar with (for particle rendering purposes). Would I still be able to use OpenGL inside the OpenGlWidget? If so, what is the command I would use to make changes to the FrameBuffer and draw shapes? I tried calling the drawFramebuffer() function after setting the coordinates and glEnd(), but I couldn’t get a simple triangle to show up. I’m not sure if I am using the API properly.

Please let me know what I should do. Any advice helps!

Thank you,

Evan Murray

2 Likes

Seeing your source code is completely necessary in order to figure out what you have or have not done.

I completely understand. I just created a GitHub repository with everything in it.

struct ModularForecastDisplay : OpenGlWidget {
    ModularForecast *module;
    ModularForecastDisplay(){}
    
    //Draw a custom widget in the module - the weather view
    //args.vg is the NVGContext

    void draw(const DrawArgs &args) override {
        
        //background
        nvgBeginPath(args.vg);
        nvgRect(args.vg, 0, 0, box.size.x, box.size.y);
        nvgFillColor(args.vg, nvgRGB(20, 30, 33));
        nvgFill(args.vg);
        
        //basic shape
        nvgBeginPath(args.vg);    //begin / close previous path

        nvgMoveTo(args.vg, box.size.x / 2, box.size.y / 2);    //center box
        nvgLineTo(args.vg, box.size.x / 2 - 40, box.size.y / 2 - 40);
        nvgLineTo(args.vg, box.size.x / 2 + 80, box.size.y / 2 - 80);
        nvgLineTo(args.vg, box.size.x / 2, box.size.y / 2);

        nvgFillColor(args.vg, nvgRGB(20, 155, 2));
        nvgFill(args.vg);

        glEnable(GL_DEPTH_TEST);
        
        //Testing OpenGL
        
        //Clear information from last draw
        glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
        
        glMatrixMode(GL_MODELVIEW);
        glLoadIdentity();
        
        glBegin(GL_QUADS); //Begin quads
        
        //Trapezoid (f forces the compiler to treat these nums as floats)
        glVertex3f(-0.7f, -1.5f, -5.0f);
        glVertex3f(0.7f, -1.5f, -5.0f);
        glVertex3f(0.4f, -0.5f, -5.0f);
        glVertex3f(-0.4f, -0.5f, -5.0f);
        
        glEnd(); //Done!
        
        drawFramebuffer();
        
        
        if(module == NULL) return;
    }
};

The nonovg header file is well commented

1 Like

You can’t put OpenGL calls in draw(). To see why, look at this simplified version of the draw routine in Window::run().

nvgBeginFrame(vg, fbWidth, fbHeight, pixelRatio);

APP->scene->draw();

glViewport(0, 0, fbWidth, fbHeight);
glClearColor(0.0, 0.0, 0.0, 1.0);
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT);
nvgEndFrame(vg);

NanoVG functions don’t do any drawing. They add commands to a command buffer which is converted into OpenGL calls when nvgEndFrame() is called. If you draw anything directly in draw(), it will be cleared with glClear(), and then your NanoVG stuff will be drawn over the cleared screen in nvgEndFrame().

I suggest looking at the documentation of OpenGlWidget. The only example of OpenGlWidget I’m aware of is OpenGlWidget itself, which draws a colorful triangle in its drawFramebuffer(). You should not attempt to call NanoVG functions in this method unless you understand exactly how NanoVG is implemented.

4 Likes

Thank you so much for the advice and quick replies! @Vortico @Coirt

I looked at the NANOVG_H and it was filled with information and documentation. However, I decided I am going to stay away from using NanoVG function calls since I do not understand it.

I knew there was source code for Rack, but I did not realize the API source code was in there as well. I took a look at the OpenGlWidget implementation here, and it was very helpful! Now I understand the difference between draw() and drawFramebuffer() more clearly. I even tried overriding drawFramebuffer() myself to draw a rectangle, and it worked.

For anyone else who is curious on how to do this, I am actually working on developing a VCV Rack module which uses OpenGL. You can check out the source code here: https://github.com/emurray2/auraaudio-vcv-rack

It may not be the best way to do things, but I am still learning. Once again, I appreciate the help, and my question is now closed.

5 Likes

This simple test widget with nanovg works on my linux:

#include "plugin.hpp"

struct ColoredGlass : Module {
	enum ParamIds {
		NUM_PARAMS
	};

	enum InputIds {
		ENUMS(CH_INPUTS, 1),
		NUM_INPUTS
	};
	enum OutputIds {
		MIX_OUTPUT,
		NUM_OUTPUTS
	};

	enum LightIds {
		NUM_LIGHTS
	};

	ColoredGlass() {
		config(NUM_PARAMS, NUM_INPUTS, NUM_OUTPUTS, NUM_LIGHTS);
	}

	void process(const ProcessArgs& args) override {
	}
};

struct ColoredGlassGlWidget : OpenGlWidget {
	ColoredGlass *module = new ColoredGlass();

	void frame(void) {
		glClear(GL_COLOR_BUFFER_BIT);
		glClearColor(1.0f, 0.15f, 0.3f, 1.0f);
	}
	
	int i = 0;
	void draw(const DrawArgs &args) override {
		nvgBeginPath(args.vg);
		nvgRect(args.vg, 0 - i++, 0, 10, 10);
		nvgFillColor(args.vg, nvgRGB(120, 30, 33));
		nvgCircle(args.vg, 100, 100, 50);
		nvgFill(args.vg);
	}
};

struct ColoredGlassWidget : ModuleWidget {
	ColoredGlassWidget(ColoredGlass* module) {
		setModule(module);
		setPanel(APP->window->loadSvg(asset::plugin(pluginInstance, "res/ColoredGlass.svg")));

		{
			ColoredGlassGlWidget *display = new ColoredGlassGlWidget();
			display->module = module;
			display->box.pos = Vec(150, (RACK_GRID_HEIGHT / 8) - 10);
			display->box.size = Vec(box.size.x - 190, RACK_GRID_HEIGHT - 80);
			display->setSize(Vec(395, 305));
			display->setPosition(Vec(150,35));

			addChild(display);
		}
	}
};

Model* modelColoredGlass = createModel<ColoredGlass,ColoredGlassWidget>("ColoredGlass");

Not sure why you have opened this thread again after 2 years. Also I think this code is a bad example. The OpenGL code you have added will never be executed, and the rest is just nanovg, you don’t need an OpenGLWidget for it.