Skip to content

zachliu/ccbp_advanced

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

3 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

3D Magnetic Field Visualization — Linux Port

A real-time 3D visualization of the magnetic field distribution around a rectangular current-carrying loop, computed using the Biot-Savart law.

Original: Ph3DSys by Mars, September--October 2003, built with Borland C++ Builder 6.0, OpenGL, and the VCL framework on Windows.

Port: SDL2 + legacy OpenGL + Dear ImGui on Linux, April 2026.

Screenshot of the Linux port showing the rectangular grid visualization mode with the ImGui control panel


Table of Contents

  1. Background — CCBP
  2. Building and Running
  3. Dependencies
  4. Controls
  5. Project Structure
  6. How the Port Was Done
  7. Physics
  8. Architecture Comparison
  9. Original Project Files
  10. Known Differences from the Original
  11. Performance Notes

Background — CCBP

This project was originally developed as part of the CCBP (A Course in Calculus-Computer-Based Physics) teaching system at Huazhong University of Science and Technology (HUST).

CCBP was a physics education reform framework created by Professor Li Yuanjie (李元杰) of HUST, proposed in 1997 and published as a two-volume textbook (大学物理CCBP教程, Hubei Science and Technology Press, July 2000). The system integrated digital computation, three-dimensional visualization and simulation, and traditional calculus-based physics instruction — using computer programs to make abstract classical and quantum phenomena visible and intuitive for science and engineering students.

The CCBP system was adopted at over 40 universities across China. Professor Li was invited to present it at institutions including Tsinghua University and Peking University, and received the National First Hundred Outstanding Teaching Masters Award for this work. The original simulations were written in C/C++ for DOS/Windows using Borland's BGI graphics and Turbo C 2.0; the CCBP/ directory in this project contains the TOpenGLAPPanel component — a custom VCL/OpenGL panel from the AP.Soft library that was distributed as part of the CCBP toolkit.

After Professor Li left HUST in 2004, the CCBP program gradually faded as its underlying technology became obsolete. The textbooks are now out of print.

References:


Building and Running

# Install dependencies (Ubuntu/Debian)
sudo apt install libsdl2-dev libglu1-mesa-dev cmake g++

# Clone Dear ImGui into the project root (if not already present)
git clone --depth 1 https://github.com/ocornut/imgui.git imgui

# Build
mkdir -p build && cd build
cmake ..
make -j$(nproc)

# Run
./ph3dsys

The executable is built in the build/ directory. No installation step is needed; just run it from there.


Dependencies

Library Purpose Install (apt)
SDL2 Windowing, input, OpenGL context libsdl2-dev
OpenGL 3D rendering (legacy fixed-function pipeline) Comes with GPU drivers
GLU Utility functions: perspective projection, quadric objects libglu1-mesa-dev
Dear ImGui Immediate-mode GUI (sliders, buttons, menus) Vendored in imgui/
CMake >= 3.10 Build system cmake
C++11 compiler g++ or clang++ g++

Controls

Mouse

Action Effect
Left-drag on the 3D view Rotate the camera
Scroll wheel Zoom in / out

Keyboard

Key Effect
1 Switch to Rectangular Grid mode
2 Switch to Circular Grid mode
Space Toggle auto-rotation on/off
R Reset camera to default position and angle
Esc Quit

GUI Panel (top-left)

  • Visualization Mode — Radio buttons to choose between the rectangular (Cartesian) grid and the circular (polar) grid.
  • Animation — Static or auto-rotate.
  • R (radius) — Slider controlling the current loop radius (1--50).
  • A (scale) — Slider controlling the spatial scaling factor (1--30).
  • I (current) — Slider controlling the current magnitude (10,000--500,000). The "Effective I" readout below it shows the derived value matching the original's display formula.
  • X, Y, Z — Drag-integers to shift the mesh position in 3D space.
  • Distance — Slider for camera distance (zoom).
  • Reset Position — Button to reset all view and position parameters to defaults.

Menu Bar

  • Help > About — Shows project origin and description.
  • Help > Quit — Exits the application.

Project Structure

Directories

Directory Origin Description
src/ Port Linux port source code (main.cpp, ph3dfunc.cpp/h)
build/ Port CMake build output; contains the compiled ph3dsys executable
imgui/ Port Dear ImGui library (vendored, git-cloned from GitHub)
CCBP/ Original CCBP (Comprehensive Computer-Based Physics) component library — contains TOpenGLAPPanel source, the custom VCL/OpenGL panel component from AP.Soft
OpenGL/ Original Bundled OpenGL headers and Windows DLLs/LIBs (gl.h, glu.h, glut.h, opengl32.dll, etc.)
doc/ Original Chinese development manual (开发手册.doc, Word 5.1 format, code page 936)
res/ Original 15 JPEG screenshot/diagram images from the original documentation
windows_build/ Original Pre-built Windows executable (Ph3DSys.exe) with runtime dependencies (DLLs, BMP buttons)

Files in the project root

File(s) Origin Description
CMakeLists.txt Port CMake build configuration for the Linux port
README.md Port This file
MainFunc.cpp Original Core simulation logic: physics (fz()), mesh generation, VCL UI event handlers (872 lines)
MainFunc.h Original TForm1 class definition, POINT3F and RGBcolor class definitions (173 lines)
MainFunc.dfm Original VCL form designer layout (binary text format defining the UI)
Ph3DSys.cpp Original WinMain entry point (33 lines)
Ph3DSys.bpr Original Borland C++ Builder 6 project file (XML)
Ph3DSys.dsk Original Borland IDE desktop/session state
Ph3DFunc.h Original Header for the graphics DLL — the only documentation of its API (58 lines)
Ph3DFunc.dll Original Closed-source Windows DLL providing 3D drawing functions
Ph3DFunc.lib Original Import library for the DLL
Ph3DSys.exe Original Compiled Windows executable (630 KB)
*.obj Original Borland compiler object files
*.res, *.tds, *.ddp Original Borland resource, debug symbol, and IDE state files
*.BMP Original 10 button icon bitmaps (32x32, 16-color) used by the VCL UI
glut32.dll Original GLUT runtime DLL (Windows)
USE.cpp/h/dfm Original Stub helper form (minimal, unused in the port)
imgui.ini Port Dear ImGui layout state (auto-generated at runtime)

Port source files

src/
|-- main.cpp          573 lines  Main application: SDL2 init, event loop,
|                                 physics (fz + mesh generation), ImGui UI,
|                                 OpenGL rendering, mesh caching
|-- ph3dfunc.cpp       483 lines  Reimplementation of the original Ph3DFunc.dll
|                                 (30+ OpenGL wrapper functions)
|-- ph3dfunc.h          77 lines  Public API matching the original DLL header

How the Port Was Done

The Problem

The original application depended on three non-portable layers:

  1. Borland VCL (Visual Component Library) — The entire UI: the window, panels, scrollbars, buttons, radio buttons, menus, combo boxes, timers, mouse events, and image-based navigation buttons. VCL is a Delphi/C++ Builder-specific framework with no Linux equivalent.

  2. Ph3DFunc.dll — A closed-source Windows DLL providing ~30 high-level 3D drawing functions (phSurface, phSphere, phCone, phCoordinate, phSetLight, etc.). Only the header file (Ph3DFunc.h) and the compiled .dll/.lib were available; no source code.

  3. TOpenGLAPPanel — A custom VCL component (from a third-party package called "AP.Soft") that managed the OpenGL rendering context within a Borland form, including WGL context creation, pixel format setup, double buffering, and font rendering. Source was available in CCBP/ but tightly coupled to VCL.

What Was Portable

The physics and math code — the fz() function implementing the Biot-Savart law and the two mesh-generation loops — are pure C floating-point arithmetic with no platform dependencies. The OpenGL rendering calls in render_scene() (matrix transforms, polygon modes, lighting setup) use standard OpenGL that works identically on Linux.

Replacement Strategy

Original Layer Replacement Rationale
VCL windowing + events SDL2 Cross-platform, lightweight, handles OpenGL context natively
VCL UI widgets Dear ImGui Immediate-mode GUI; trivial SDL2+OpenGL integration; sliders, radio buttons, menus out of the box
TOpenGLAPPanel (WGL context) SDL2 SDL_GL_CreateContext() SDL2 abstracts platform-specific GL context creation
Ph3DFunc.dll (30 functions) Reimplemented in ph3dfunc.cpp Using legacy OpenGL (glBegin/glEnd) and GLU quadrics to match the era
VCL TTimer SDL_GetTicks() + frame delta Standard SDL2 time-based animation
BMP button images ImGui buttons/sliders Modern UI; no bitmap resources needed

Detailed Porting Notes

Physics (fz() and mesh generation)

Ported verbatim from MainFunc.cpp lines 42--431. Variable names, constants, loop bounds, boundary conditions, and the trapezoidal integration are identical. The only changes are:

  • float → explicit cosf/sinf/powf/tanf (C99 float variants instead of implicit double-to-float conversions in old Borland C++)
  • Physics parameters (R, A, I) renamed to R_param, A_param, I_param to avoid conflicts with standard library symbols

Ph3DFunc.dll reimplementation (ph3dfunc.cpp)

The DLL exported ~30 functions. The simulation only uses about 10 of them. All 30 were reimplemented for completeness:

  • phSetLight(number, position, ambient, diffuse) — Wraps glLightfv to configure OpenGL light sources. The ambient/diffuse parameters are scaled from the original's 0--10 range to 0.0--1.0 floats.

  • phSetColor(r, g, b) — Sets both glColor3f and glMaterialfv(GL_AMBIENT_AND_DIFFUSE) so the color works with or without lighting enabled.

  • phSetMaterial(ambient, diffuse) — Maps RGBcolor (0--255) to OpenGL material properties (0.0--1.0).

  • phTranslatef(position) — Thin wrapper around glTranslatef.

  • phRotate(O, P, angle) — Rotates around an arbitrary axis defined by points O and P. Translates to origin, rotates, translates back.

  • phCoordinate(O, X, Y, length) — Draws a colored 3D coordinate system: red X axis, green Y axis, blue Z axis (computed as X cross Y). Temporarily disables lighting for clean line rendering.

  • phSurface(p, maxi, maxj, color) — The critical function. Renders an maxi x maxj grid of POINT3F vertices as GL_QUAD_STRIP rows with per-face computed normals. This is what draws the magnetic field surface.

  • phSphere, phCone, phCylinder, phDisk — Use GLU quadric objects (gluSphere, gluCylinder, gluDisk) with automatic orientation alignment via glRotatef to match the two-point API of the original.

  • phArrow, phSpring, phMissile — Composed from the above primitives.

  • phPoint, phLine, phTriangle, phQuads, phPolygon — Direct glBegin/glEnd wrappers with automatic normal computation.

  • Math helpers (normalize, normcrossprod, calcNormal, ReduceToUnit) — Standard vector math, used internally by the surface and polygon renderers.

OpenGL context and rendering pipeline

The original's OpenGLAPPanelInit and OpenGLAPPanelResize mapped directly:

Original (VCL/WGL)                    Port (SDL2)
─────────────────                     ──────────
TForm1::OpenGLAPPanelInit()      →    gl_init()
  glShadeModel(GL_SMOOTH)              (identical)
  glClearColor(0,0,0,1)                (identical)
  glEnable(GL_DEPTH_TEST)              (identical)
  glEnable(GL_BLEND)                   (identical)

TForm1::OpenGLAPPanelResize()    →    gl_resize(w, h)
  glViewport(...)                      (identical)
  gluPerspective(10, ...)              (identical)

TForm1::OpenGLAPPanelPaint()     →    render_scene()
  Camera setup at z=-800               (identical)
  Rotate -90 around X and Z            (identical)
  Translate by 'trans'                  (identical)
  Light setup via phSetLight            (identical)
  Mesh generation + phSurface           (moved to cached functions)

VCL UI → Dear ImGui

Every VCL widget was replaced with an ImGui equivalent:

Original VCL Widget                   ImGui Replacement
────────────────────                  ─────────────────
TRadioButton (RadioButton1/2)    →    ImGui::RadioButton()
TScrollBar (ScrollBar_R/A/I)     →    ImGui::SliderFloat()
TEdit (Edit_R/A/I, Edit_X/Y/Z)  →    ImGui::Text() / ImGui::DragInt()
TComboBox (ChoiseCartoon)        →    ImGui::RadioButton() (simpler)
TImage buttons (up/down/left/right) → ImGui::DragInt() for X/Y/Z
TImage buttons (turnMAX/turnMIN) →    ImGui::SliderFloat("Distance")
TButton (RePosition)             →    ImGui::Button("Reset Position")
TMainMenu (EXIT/HELP/USE/ABOUT) →    ImGui::BeginMainMenuBar()
TTimer (Timer1, animation)       →    SDL_GetTicks() delta-time loop
Mouse drag rotation              →    SDL_MOUSEMOTION events
Application::MessageBox (About)  →    ImGui::Begin("About") window

Mouse interaction

The original handled mouse drag via OpenGLAPPanelMouseDown/Move/Up events on the VCL panel. The port uses SDL_MOUSEBUTTONDOWN, SDL_MOUSEMOTION, and SDL_MOUSEBUTTONUP events with io.WantCaptureMouse gating so that dragging over the ImGui panel doesn't rotate the scene.

Scroll-wheel zoom was added as an improvement over the original's button-based zoom (turnMAX/turnMIN image buttons).


Physics

The simulation computes the magnetic field strength at each point on a 2D grid that is then displayed as a 3D surface where height (Z) represents field intensity.

The Biot-Savart Law

The magnetic field B at a point due to a current-carrying wire element is:

dB = (mu_0 / 4*pi) * (I * dl x r_hat) / r^2

where mu_0 is the permeability of free space (1.26e-6 H/m), I is the current, dl is the wire element vector, and r is the distance from the wire element to the field point.

Function fz(x, y) — Rectangular loop integration

Used by visualization mode 1 (Rectangular Grid). Integrates the Biot-Savart contribution from all four sides of a unit square current loop:

  • Side 1 (angles -45 to +45): Bottom edge, parameterized by angle
  • Side 2 (angles +45 to +135): Right edge
  • Side 3 (angles +135 to +225): Top edge
  • Side 4 (angles +225 to +315): Left edge

Each side uses trapezoidal-rule integration with 10-degree steps (15-degree for side 4). The endpoints are weighted by 1/2 (standard trapezoidal rule). The result is scaled by mu_0 * 10 * I / (40 * pi).

Visualization Mode 0 — Circular/polar grid

Uses a 43x43 polar coordinate mesh. For each grid point, iterates around a circular current loop in 12-degree steps (30 iterations), computing the Biot-Savart contribution at each step using a direct discrete summation (trapezoidal rule with Simpson-like weighting). Field values are clamped: +65 inside the loop, -55 outside.

Visualization Mode 1 — Rectangular/Cartesian grid

Uses a 69x69 Cartesian mesh spanning [-34, +34] units. Coordinates are normalized by the scaling factor A. The fz() function is called for each point, with special boundary handling near the wire location (0.85 < |x| < 1.2 or 0.85 < |y| < 1.22) where the field would diverge, clamping to fz(0.8, _) to avoid singularities.

Physical Constants

Constant Symbol Value Description
Permeability of free space mu_0 1.26e-6 H/m Magnetic constant
Current I 250,000 A (default) Adjustable via slider
Loop radius R 17 units (default) Adjustable via slider
Scale factor A 10 units (default) Adjustable via slider
Angular divisions de 27 Fixed; used in polar mode

Architecture Comparison

Original (2003)

WinMain (Ph3DSys.cpp)
  |
  v
TForm1 (VCL) ─── MainFunc.cpp
  |                  |
  |-- TOpenGLAPPanel (WGL context, CCBP/)
  |-- TTimer ─────── Timer1Timer() → Refresh + animation
  |-- TScrollBar ─── ScrollBar_R/A/IChange()
  |-- TRadioButton ─ RadioButton1/2Click()
  |-- TImage ─────── up/down/left/rightMouseDown/Up()
  |-- TComboBox ──── ComboBox_ChoiseCartoonChange()
  |-- TMainMenu ──── EXIT/USE/ABOUTClick()
  |
  v
OpenGLAPPanelPaint()
  |-- fz() ──────── Biot-Savart calculation (every frame!)
  |-- phSurface() ─ Ph3DFunc.dll (closed-source)
  |-- phSetLight()
  |-- phCoordinate()

Port (2026)

main() ─── src/main.cpp
  |
  |-- SDL2 window + OpenGL 2.1 context
  |-- Dear ImGui (SDL2 + OpenGL2 backends)
  |
  v
Main loop:
  |-- SDL_PollEvent() ─── mouse drag, scroll, keyboard
  |-- ImGui frame ─────── control panel, menu bar
  |-- check_mesh_dirty()
  |     |-- compute_rect_mesh() ── fz() called only when params change
  |     |-- compute_polar_mesh()
  |-- render_scene() ──── OpenGL draw from cached mesh
  |     |-- phSurface() ─ src/ph3dfunc.cpp (open-source reimplementation)
  |     |-- phSetLight()
  |     |-- phCoordinate()
  |-- SDL_GL_SwapWindow()

Original Project Files

The original Windows source files are preserved unmodified in the project root. They are not used by the build but kept for reference.

File Size Description
MainFunc.cpp 872 lines Core simulation logic, UI event handlers, physics
MainFunc.h 173 lines TForm1 class definition, POINT3F and RGBcolor classes
MainFunc.dfm ~200 lines VCL form designer layout (binary text format)
Ph3DSys.cpp 33 lines WinMain entry point
Ph3DSys.bpr ~300 lines Borland C++ Builder 6 project file (XML)
Ph3DFunc.h 58 lines DLL header — the only documentation of the graphics API
Ph3DFunc.dll 175 KB Closed-source graphics library (Windows PE)
Ph3DFunc.lib 4 KB Import library for the DLL
CCBP/ ~55 KB TOpenGLAPPanel source (VCL component, AP.Soft)
OpenGL/ ~130 KB Bundled OpenGL headers and Windows DLLs/LIBs
doc/ 52 KB Development manual in Chinese (Word 5.1, code page 936)
res/ ~50 KB 15 JPEG screenshot/diagram images
*.BMP 630 bytes each 10 button icon bitmaps (32x32, 16-color)
windows_build/ ~810 KB Original build output (Ph3DSys.exe + runtime DLLs + BMP buttons)

Chinese text

The original UI labels, menu items, and About/Help message boxes were in Chinese (Simplified, GB2312/code page 936). The source files contain Chinese characters in comments that display as mojibake on Linux terminals due to encoding mismatch (the files are raw GB2312 bytes, not UTF-8). The DFM form file stores Chinese text as Unicode escape sequences (#19977#32500...).

The port uses English for all UI text. The original Chinese strings can be found in MainFunc.cpp lines 863 and 869 (ABOUTClick and USEClick).


Known Differences from the Original

Aspect Original Port
Platform Windows (9x/XP) Linux
UI framework Borland VCL (native Windows widgets) Dear ImGui (rendered overlay)
Window size 1025x735 fixed 1280x800 resizable
Zoom control Two image buttons (magnify/shrink) Scroll wheel + slider
Navigation Image buttons for up/down/left/right ImGui DragInt widgets for X/Y/Z
Physics recomputation Every frame (~60 FPS) Only when R, A, I, or mode changes
OpenGL context WGL via TOpenGLAPPanel SDL2 SDL_GL_CreateContext()
Graphics library Ph3DFunc.dll (closed-source) ph3dfunc.cpp (open-source reimplementation)
Language Chinese UI labels English UI labels
BMP button images 10 bitmap files loaded at runtime Not used (ImGui widgets instead)
Menu bar VCL TMainMenu (native Windows) ImGui main menu bar
About dialog Application->MessageBox() (modal) ImGui window (non-modal)
Auto-rotate speed Timer-driven (~fixed interval) Delta-time-driven (frame-rate independent)

OpenGL Compatibility

This port uses OpenGL 2.1 with the Compatibility Profile — sometimes called "legacy" or "fixed-function" OpenGL. This means calls like glBegin/glEnd, glVertex3f, glRotatef, and glLightfv rather than the modern shader-based pipeline (OpenGL 3.2+ Core Profile).

These functions were deprecated in the OpenGL 3.0 spec (2008) and removed from the Core Profile in 3.2, but they remain fully supported through the Compatibility Profile, which every major Linux GPU driver ships:

  • NVIDIA proprietary: Full compatibility profile support (OpenGL 4.6 Compat)
  • AMD (Mesa RADV/radeonsi): Full compatibility profile support
  • Intel (Mesa iris/i965): Full compatibility profile support

No GPU vendor has shipped a Linux driver with only Core Profile support in the 17 years since deprecation. Mesa (the open-source driver stack) explicitly maintains compatibility profile as a project goal.

SDL2 requests a Compatibility Profile context by default, which is what this project uses. The relevant lines in main.cpp:

SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 2);
SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 1);

If modernization were ever needed, the change would be localized to ph3dfunc.cpp: replace glBegin/glEnd calls with vertex buffer objects (VBOs) and write a simple vertex + fragment shader pair (~50 lines of GLSL). The physics code and application structure would not change.


Performance Notes

The most expensive operation is the physics mesh computation:

  • Rectangular grid: 69x69 = 4,761 calls to fz(), each running 4 integration loops with ~10 iterations each. Roughly 190,000 trig function calls.
  • Circular grid: 43x43 = 1,849 grid points, each with up to 30 inner iterations of 2 Biot-Savart evaluations. Roughly 220,000 trig function calls.

The original recomputed this every frame, causing sustained high CPU load (85--95 C on a modern system running at 60 FPS).

The port caches the computed mesh and only recomputes when a physics parameter (R, A, I) changes or the visualization mode is switched. This drops idle CPU temperature to ~55 C and only spikes briefly to ~65 C during parameter adjustment.

View-only operations (rotation, zoom, position shift) just re-render the cached mesh vertices through the OpenGL pipeline, which is essentially free.

About

No description, website, or topics provided.

Resources

License

Stars

0 stars

Watchers

0 watching

Forks

Releases

No releases published

Packages

 
 
 

Contributors