How to draw proper logic gate curves (OR/NOR shapes) using quadratic Bezier curves in wxWidgets?

2 hours ago 1
ARTICLE AD BOX

I'm developing a digital circuit design and simulation tool similar to Logisim, and I'm struggling immensely to render the distinctive curved shape of logic gates like OR/NOR gates. Despite numerous attempts with Bezier curve parameters, I cannot achieve the classic "pointed-oval" shape seen in standard schematics.

Important context: The Bezier parameters in my JSON files are the result of painstaking manual tuning over many hours. I haven't found any tools for visually debugging these parameters; it's purely trial-and-error, adjusting bit by bit based on whether the drawn shape looks more "standard." Each adjustment requires recompiling and running the program to see the effect, which is extremely inefficient.

Current Technical Approach (Detailed Implementation)

My architecture consists of two main components that work together to render logic gates from JSON definitions:

1. CanvasModel: JSON Parsing and Element Factory (CanvasModel.h) The CanvasModel.h header defines a loader function that reads my canvas_elements.json file and constructs CanvasElement objects:

 // CanvasModel.h - JSON deserialization entry point  #pragma once  #include <wx/wx.h>  #include <vector>  #include <json/json.h>    ​  class CanvasElement;  ​  // Global function: JSON -> CanvasElement list  std::vector<CanvasElement> LoadCanvasElements(const wxString& jsonPath);

This function parses the JSON array, creates a CanvasElement instance for each gate definition, and calls the element's AddShape() method to populate its internal shape storage.

2. CanvasElement: Shape Storage and Bezier Rendering Engine (CanvasElement.h) The CanvasElement.h header defines the complete data structures and rendering pipeline:

 // CanvasElement.h - Core shape storage and Bezier math  #pragma once  #include <wx/wx.h>  #include <wx/dcgraph.h>  #include <wx/graphics.h>  #include <vector>  #include <variant>  ​  struct Point { int x, y; };  ​  // Quadratic Bezier curve definition (loaded directly from JSON)  struct BezierShape {      Point p0, p1, p2;      wxColour color;      BezierShape(Point p0 = Point(), Point p1 = Point(), Point p2 = Point(),                  wxColour c = wxColour(0, 0, 0))         : p0(p0), p1(p1), p2(p2), color(c) {}  };  ​  // Shape variant that stores all drawable primitives  using Shape = std::variant<Line, PolyShape, Circle, Text, Path, ArcShape, BezierShape>;  ​  class CanvasElement {  private:      std::vector<Shape> m_shapes;  // All shapes for this element, including Bezier curves            // Core Bezier math: samples quadratic curve into polyline      std::vector<wxPoint> CalculateBezier(const Point& p0, const Point& p1,                                           const Point& p2, int segments = 16) const {          std::vector<wxPoint> points;          for (int i = 0; i <= segments; ++i) {              double t = static_cast<double>(i) / segments;              // Quadratic Bezier formula: P(t) = (1-t)²·P₀ + 2(1-t)t·P₁ + t²·P₂              double x = (1-t)*(1-t)*p0.x + 2*(1-t)*t*p1.x + t*t*p2.x;              double y = (1-t)*(1-t)*p0.y + 2*(1-t)*t*p1.y + t*t*p2.y;              points.push_back(wxPoint(static_cast<int>(x), static_cast<int>(y)));         }          return points;     }  ​      void DrawVector(wxGCDC& gcdc) const;      void DrawFallback(wxDC& dc) const;  ​  public:      // Called by LoadCanvasElements() for each shape in JSON      void AddShape(const Shape& shape) { m_shapes.push_back(shape); }            // Main draw entry point      void Draw(wxDC& dc) const;            // Retrieves stored shapes (used by Draw() method)      const std::vector<Shape>& GetShapes() const { return m_shapes; }  };

The Complete Rendering Pipeline:

LoadCanvasElements() reads JSON and creates CanvasElement instances

For each Bezier definition in JSON, it constructs a BezierShape and calls AddShape() to push it into m_shapes

During rendering, CanvasElement::Draw() iterates through m_shapes variant

When a BezierShape is encountered, CalculateBezier() converts the mathematical curve into a polyline of 16 segments

The resulting wxPoint array is drawn using wxGCDC::DrawLines() or equivalent

3. Current Output The result after this pipeline processes my manually-tuned parameters:

my current OR gate

My current OR gate rendering: After countless manual parameter adjustments, the three Bezier curves combine into a fat pear shape. The back is too rounded, there are visible creases where curves meet, and the overall shape is far from the standard OR gate.

This is what a standard OR gate should look like (screenshot from open-source software logisim-evolution):

standard OR gate

Standard OR gate shape: Relatively flat back, smooth and natural curves, distinct "pointed" output side, and well-proportioned overall geometry.

The Problem

The three Bezier curves don't create a seamless, mathematically precise OR gate shape. The issues are:

Discontinuity: Curves meet with visible kinks instead of smooth tangents

Wrong geometry: The back curve is too semicircular; real OR gates have a flatter back with specific curvature

No standard: I can't find canonical Bezier control points for logic gates anywhere

Questions

Is there a standard set of quadratic Bezier parameters that produces the correct OR/NOR gate shape used in IEEE/ANSI symbols?

Should I abandon Bezier curves and switch to:

SVG path definitions loaded from files?

Custom C++ drawing functions with hardcoded geometry?

Arcs + lines instead of pure Bezier curves?

How do professional EDA tools like Logisim, KiCad, or digital logic simulators typically render these shapes? Do they use vector primitives or bitmap icons?

If sticking with my current Bezier approach, what's the mathematical relationship between the control points to ensure:

Smooth continuity (G1 continuity) where curves meet

Proper aspect ratio (height vs width)

The characteristic "pointed" output side?

Any guidance or examples from someone who has implemented logic gate rendering would be greatly appreciated. I'd prefer to keep my JSON-driven vector system if possible, but I'm open to architectural changes if that's the wrong approach entirely.

Read Entire Article