6502.org Forum  Projects  Code  Documents  Tools  Forum
It is currently Sun Nov 10, 2024 8:42 pm

All times are UTC




Post new topic Reply to topic  [ 7 posts ] 
Author Message
 Post subject: Bubble Universe ...
PostPosted: Wed May 29, 2024 2:18 pm 
Offline
User avatar

Joined: Wed Feb 14, 2018 2:33 pm
Posts: 1485
Location: Scotland
Bubble Universe is a graphical demo/thing that's a few lines of Basic. It's been about for a while and runs on various 6502 systems like the Commander-X16 and BBC Micro. I first saw it via a Facebook post by Paul Dunn who is the creator of "SpecBas" - a modern re-implementation of Spectrum Basic - Anyone know of any others?

Here is the Basic code:

Code:
// bubble-universe
//    Adapted from Facebook posting by Paul Dunn

rad

n   = 200
tau = PI * 2
r   = tau / 235

x = 0
v = 0
t = 0
sz = 90
sw = gWidth  / 2
sh = gHeight / 2

origin (sw, sh)

//cycle
  cls2
  for i = 0 to n cycle
    for j = 0 to n cycle
      u = sin (i + v) + sin (r * i + x)
      v = cos (i + v) + cos (r * i + x)
      x = u + t
      rgbColour (i,j,99)
      plot (u * sz, v * sz)
    repeat
  repeat
  t = t + 1/n
  update
//repeat

end


the Basic is my own variant of Basic (called RTB Basic), but should be adaptable to others.

The first frame looks like:

Attachment:
Screenshot_2024-05-29_14-59-42.png
Screenshot_2024-05-29_14-59-42.png [ 64.81 KiB | Viewed 1265 times ]


The program would normally run to generate an animation - if your underlying system was fast enough - the 6502 isn't, not even at 16Mhz, neither is the '816, however for a bit of fun, I decided to implement it in my BCPL system on my Ruby '816 board (16Mhz).

Here is the BCPL code:

Code:
GET "libhdr.h"
GET "sys.h"
GET "math.h"
GET "vdu.h"

MANIFEST
{
  n   = 200
  tau = M_PI #* 2.0
  r   = tau #/ 235.0
  sz  = 90.0 //100.0
  sw  = 640 / 2    // Width
  sh  = 360 / 2    // Height
}


AND start () = VALOF
{
  LET x, u, v = ?,?,? 
  LET t = 0.0

  LET oldV = vduSetup (1)

  vduCls ()
  gOrigin (sw, sh)

  u := 0.0
  v := 0.0
  x := 0.0

//  FOR z = 1 TO 1 DO
//  {
    vduCls ()
    FOR i = 0 TO n DO
    {
      FOR j = 0 TO n DO
      {
        LET fi  = FLOAT i
        LET rix = r #* fi #+ x
        LET iv  =      fi #+ v

        u := sys (Sys_flt, fl_sin, iv) #+ sys (Sys_flt, fl_sin, rix)
        v := sys (Sys_flt, fl_cos, iv) #+ sys (Sys_flt, fl_cos, rix)
        x := u #+ t
        vduGColRGB (i,j,99)

        vduPoint (FIX (u #* sz), FIX (v #* sz))
      }
    }
    t := t #+ 0.025
    vduUpdate ()
 
//  } //REPEAT

  gOrigin (0,0)

  RESULTIS 0
}


timing? Well the RTB Basic version on my Linux (i7) desktop can achieve some 60 frames per second... The BCPL version on my '816 board can render one frame in about 140 seconds ... So it's not going to win any awards there...

One slow-down of the BCPL version is the graphics - in my Ruby board, there is no directly connected graphics - it's all done over 115200 baud serial line and to plot a single point you need to send 6 bytes in addition to changing the colour which takes 5 bytes...

If I run it without the output then it's down to 60 seconds...

some other links to it:

https://retrocomputingforum.com/t/the-m ... ion/3651/9

https://stardot.org.uk/forums/viewtopic.php?t=27721

https://cx16forum.com/forum/viewtopic.php?t=6437

Anyone up to doing a version on their 6502/65816 system? Let's see it in action!

-Gordon

_________________
--
Gordon Henderson.
See my Ruby 6502 and 65816 SBC projects here: https://projects.drogon.net/ruby/


Top
 Profile  
Reply with quote  
 Post subject: Re: Bubble Universe ...
PostPosted: Wed May 29, 2024 4:18 pm 
Offline
User avatar

Joined: Mon Aug 30, 2021 11:52 am
Posts: 287
Location: South Africa
I amazed I've never seen or heard of the Bubble Universe before. I think I'll take a break from languag'ing (which is necessary but not a lot of fun) and have a play with this; targeting assembly on a 12MHz '816 system in 320x200x8bpp. It's also gonna force me to use more of Garth's lookup tables because so far I've only used multiplication.

I'll post back here if I achieve something.


Top
 Profile  
Reply with quote  
 Post subject: Re: Bubble Universe ...
PostPosted: Wed May 29, 2024 6:25 pm 
Offline
User avatar

Joined: Wed Feb 14, 2018 2:33 pm
Posts: 1485
Location: Scotland
AndrewP wrote:
I amazed I've never seen or heard of the Bubble Universe before. I think I'll take a break from languag'ing (which is necessary but not a lot of fun) and have a play with this; targeting assembly on a 12MHz '816 system in 320x200x8bpp. It's also gonna force me to use more of Garth's lookup tables because so far I've only used multiplication.

I'll post back here if I achieve something.


Hope you get something. Obviously while lookup tables and fixed-point arithmetic should be faster, calculating sine (& cosine) is relatively easy using the Taylor series with nothing more than addition and multiplication. My BCPL system uses 32-bit IEEE 754 floats.

I built my own 8x8 table based multiplier for my '816 system, but reverted to other means which was almost as fast but didn't soak up 128KB of RAM (it has 512KB) using the 8x8 to create a 32x32 was relatively easy.

Cheers,

-Gordon

_________________
--
Gordon Henderson.
See my Ruby 6502 and 65816 SBC projects here: https://projects.drogon.net/ruby/


Top
 Profile  
Reply with quote  
 Post subject: Re: Bubble Universe ...
PostPosted: Wed May 29, 2024 8:37 pm 
Offline

Joined: Tue Sep 03, 2002 12:58 pm
Posts: 336
I've been doing a little experimenting to see where simplifications can be made. The good news is that no general multiplications are required: all of the multiplications are by constants (and some of the constants can be made powers of two with no bad effect).

The bad news is that it is very sensitive to the precision of sin and cos. Using 256K 16-bit entries is sufficient. The width can be reduced a little (12 bits are OK, 8 bits are not), but the 256K entries are required. Anything less causes noticeable glitches. I'm using double precision floats for everything else: reducing precision elsewhere might also have an effect.

I'm going to have to implement a version for my little extended-6502 computer. It's a very pretty effect.


Top
 Profile  
Reply with quote  
 Post subject: Re: Bubble Universe ...
PostPosted: Thu May 30, 2024 5:35 am 
Offline
User avatar

Joined: Tue Feb 28, 2023 11:39 pm
Posts: 256
Location: Texas
Here's a completely unoptimized version I made in C++ for Windows:

Code:
// BubbleUni.cpp : Defines the entry point for the application.
//

#define WIN32_LEAN_AND_MEAN             // Exclude rarely-used stuff from Windows headers
// Windows Header Files
#include <windows.h>
// C RunTime Header Files
#include <stdlib.h>
#include <malloc.h>
#include <memory.h>
#include <tchar.h>

#include "BubbleUni.h"

#define _USE_MATH_DEFINES

#include "math.h"

void DrawDIB();

char *g_dib = nullptr;
size_t g_dibSize = 0;

// Resolution of our DIB
const int WIDTH = 1280; // 640 * 2
const int HEIGHT = 960; // 480 * 2

// State for actual bubble universe program
const int N = 200;
const int SCALE = 90;

const double ASPECT = (double)WIDTH / HEIGHT;

const double TAU = M_PI / 2;
const double r = TAU / 256;

double t = 0;

void DrawFrame()
{
    if (!g_dib)
        return; // Don't compute if we don't have a DIB to draw to!

    double x = 0;
    double u = 0;
    double v = 0;
   
    int hw = WIDTH / 2;
    int hh = HEIGHT / 2;

    // Fill with black
    memset(g_dib, 0, g_dibSize);

    for (int i = 0; i <= N; ++i)
    {
        for (int j = 0; j <= N; ++j)
        {
            u = sin(i + v) + sin(r * i + x);
            v = cos(i + v) + cos(r * i + x);
            x = u + t;

            int dx = (int)(u * SCALE) + hw;
            int dy = (int)(v * SCALE * ASPECT) + hh;

            int off = (dx + dy * WIDTH) * 3;
            g_dib[off + 0] = i;
            g_dib[off + 1] = j;
            g_dib[off + 2] = 99;
        }
    }

    t += 1.0 / N;

    DrawDIB();
}

/*
 * Below is the bulk of the Win32 startup/shutdown code.
 */

// Global Variables:
HINSTANCE g_instance; // current instance

// Save the drawing context globally.
HDC g_dc;
BITMAPINFO bmap;
int g_width;
int g_height;

#define MAIN_CLS _T("BUBBLE_UNI_MAIN_WIN__")
#define TITLE _T("Bubble Universe Test")

// Forward declarations of functions included in this code module:
ATOM MyRegisterClass();
BOOL InitInstance(int);
LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM);

int APIENTRY wWinMain(_In_ HINSTANCE hInstance,
    _In_opt_ HINSTANCE hPrevInstance,
    _In_ LPWSTR    lpCmdLine,
    _In_ int       nCmdShow)
{
    (void)(hPrevInstance);
    (void)(lpCmdLine);

    // TODO: Place code here.

    // Initialize global strings
    g_instance = hInstance; // Store instance handle in our global variable

    // Perform application initialization:
    if (!InitInstance(nCmdShow))
        return FALSE;

    MSG msg;

    // Main message loop:
    for (;;)
    {
        if (PeekMessage(&msg, nullptr, 0, 0, PM_REMOVE))
        {
            if (msg.message == WM_QUIT)
                break;

            TranslateMessage(&msg);
            DispatchMessage(&msg);
        }

        DrawFrame();
    }

    delete[] g_dib;

    return (int)msg.wParam;
}

//
//  FUNCTION: MyRegisterClass()
//
//  PURPOSE: Registers the window class.
//
ATOM MyRegisterClass()
{
    WNDCLASSEXW wcex;

    wcex.cbSize = sizeof(WNDCLASSEX);

    wcex.style = CS_HREDRAW | CS_VREDRAW;
    wcex.lpfnWndProc = WndProc;
    wcex.cbClsExtra = 0;
    wcex.cbWndExtra = 0;
    wcex.hInstance = g_instance;
    wcex.hIcon = LoadIcon(nullptr, IDI_APPLICATION);
    wcex.hCursor = LoadCursor(nullptr, IDC_ARROW);
    wcex.hbrBackground = nullptr; // We'll take care of painting everything
    wcex.lpszMenuName = nullptr; // No menu
    wcex.lpszClassName = MAIN_CLS;
    wcex.hIconSm = LoadIcon(nullptr, IDI_APPLICATION);

    return RegisterClassExW(&wcex);
}

//
//   FUNCTION: InitInstance(HINSTANCE, int)
//
//   PURPOSE: Saves instance handle and creates main window
//
//   COMMENTS:
//
//        In this function, we save the instance handle in a global variable and
//        create and display the main program window.
//
BOOL InitInstance(int nCmdShow)
{
    MyRegisterClass();

    HWND hWnd = CreateWindowW(
        MAIN_CLS,
        TITLE,
        WS_OVERLAPPEDWINDOW,
        CW_USEDEFAULT, 0,
        CW_USEDEFAULT, 0,
        nullptr,
        nullptr,
        g_instance,
        nullptr);

    if (!hWnd)
        return FALSE;

    ShowWindow(hWnd, nCmdShow);
    UpdateWindow(hWnd);

    return TRUE;
}

void RebuildDIB()
{
    delete[] g_dib;

    g_dibSize = WIDTH * HEIGHT * 3; // 24-bpp

    g_dib = new char[g_dibSize];

    memset(&bmap, 0, sizeof(bmap));

    bmap.bmiHeader.biSize = sizeof(bmap.bmiHeader);
    bmap.bmiHeader.biWidth = WIDTH;
    bmap.bmiHeader.biHeight = HEIGHT;
    bmap.bmiHeader.biPlanes = 1;
    bmap.bmiHeader.biBitCount = 24;
    bmap.bmiHeader.biCompression = BI_RGB;
    bmap.bmiHeader.biSizeImage = 0;
    bmap.bmiHeader.biXPelsPerMeter = 0;
    bmap.bmiHeader.biYPelsPerMeter = 0;
    bmap.bmiHeader.biClrUsed = 0;
    bmap.bmiHeader.biClrImportant = 0;
}

void DrawDIB()
{
    StretchDIBits(g_dc, 0, 0, g_width, g_height, 0, 0, WIDTH, HEIGHT, g_dib, &bmap, 0, SRCCOPY);
}

//
//  FUNCTION: WndProc(HWND, UINT, WPARAM, LPARAM)
//
//  PURPOSE: Processes messages for the main window.
//
//  WM_COMMAND  - process the application menu
//  WM_PAINT    - Paint the main window
//  WM_DESTROY  - post a quit message and return
//
//
LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
    LRESULT rval = 0;

    switch (message)
    {
    case WM_CREATE:
        g_dc = GetDC(hWnd);
        break;

    case WM_SIZE:
        if (wParam == SIZE_MINIMIZED)
            break; // Don't adjust if window is minimized.

        g_width = (int)LOWORD(lParam);
        g_height = (int)HIWORD(lParam);

        RebuildDIB();

        break;

    case WM_DESTROY:
        ReleaseDC(hWnd, g_dc);
        PostQuitMessage(0);
        break;

    default:
        rval = DefWindowProc(hWnd, message, wParam, lParam);
        break;
    }

    return rval;
}


You may need to tweak for your specific system. I have a 4090 that I run at 3840x2160

The bitmap it draws to is a 1280x960 and then stretched to whatever size the Window is via the GDI. So this is pretty painfully slow really.

It might be possible to write this as GLSL shader and run almost completely on a video card these days. ^^;


Enjoy!


Edit:
Screen shot:
Attachment:
Screenshot 2024-05-30 003743.png
Screenshot 2024-05-30 003743.png [ 101.85 KiB | Viewed 1184 times ]


Top
 Profile  
Reply with quote  
 Post subject: Re: Bubble Universe ...
PostPosted: Thu May 30, 2024 6:22 am 
Offline
User avatar

Joined: Wed Feb 14, 2018 2:33 pm
Posts: 1485
Location: Scotland
Yuri wrote:
Here's a completely unoptimized version I made in C++ for Windows:


Nice... But I'm glad I don't do pixel poking in the MS Windows world. (But with SDL its not much different in other worlds)

And it might look better if you define TAU to be 2Pi rather than Pi/2, but that's OK I made that mistake when I first did my Basic version...

Some "light" reading.. https://tauday.com/

-Gordon

_________________
--
Gordon Henderson.
See my Ruby 6502 and 65816 SBC projects here: https://projects.drogon.net/ruby/


Last edited by drogon on Mon Jun 03, 2024 7:18 am, edited 1 time in total.

Top
 Profile  
Reply with quote  
 Post subject: Re: Bubble Universe ...
PostPosted: Mon Jun 03, 2024 1:53 am 
Offline
User avatar

Joined: Tue Feb 28, 2023 11:39 pm
Posts: 256
Location: Texas
drogon wrote:
Yuri wrote:
Here's a completely unoptimized version I made in C++ for Windows:

Nice... But I'm glad I don't do pixel poking in the MS Windows world. (But with SDL its not much different in other worlds)



To be fair, most of the code is just creating a window and responding to window messages. The drawing bit is just writing to flat 24-bit bitmap and then asking the GDI to blit that to the screen.

Which you could do through SDL, or OpenGL, or Vulkan. I was just lazy and used GDI because it's pretty much always there and ready for Win32 apps. Back in the day, the GDI wouldn't have been nearly quick enough to run this as smoothly.

Quote:
And it might look better if you define TAU to be 2Pi rather than Pi/2, but that's OK I made that mistake when I first did my Basic version...


D'OH! >_<

Oh well, easy fix. :)


Top
 Profile  
Reply with quote  
Display posts from previous:  Sort by  
Post new topic Reply to topic  [ 7 posts ] 

All times are UTC


Who is online

Users browsing this forum: No registered users and 20 guests


You cannot post new topics in this forum
You cannot reply to topics in this forum
You cannot edit your posts in this forum
You cannot delete your posts in this forum
You cannot post attachments in this forum

Search for:
Jump to: