### Author Topic: 3D Bouncing Balls  (Read 168 times)

#### Richard Notley ##### 3D Bouncing Balls
« on: April 21, 2018, 05:38:58 am »
This program simulates coloured balls moving and colliding.  It is a derivative of my previous program.

http://www.qb64.net/forum/index.php?topic=13321.0

In the previous version, the perspective mathematics was calculated within the program from my own equations.  In this version, the perspective is handled by the QB64 function _MAPTRIANGLE (3D).  When I coded the original program, I was not aware of the QB64's in-built function, which amazingly handles the 3-dimensional x_, y- and Z- co-ordinates, and presents the image within the 3D space.

I have further added to the 3D full perspective visualisation by making the actual balls 3D shapes.  The 2D ball image is mapped via _MAPTRIANGLE to an actual spherical shape.  You will notice that this method has the effect of distorting the ball shape, especially at distances from the centre of the screen.   This arises because of the vanishing-point perspective used in _MAPTRIANGLE(3D).

I have created this program as a half-way house towards a stereoscopic 3D program I wish to create: in that program, the balls will be seen to move in and out of the screen.

Esc to quit the program.

Code: [Select]
`' 3D Bouncing Balls Using _MAPTRIANGLE by QWERKEY 21/04/18' Cyperium method for sphere imageCONST True = -1`, False = 0`_TITLE "3D Bouncing Balls"RANDOMIZE (TIMER)CONST NoBalls%% = 10, Uscreen% = 1000, Vscreen% = 800, NoPhis%% = 16, NoAlphas%% = 6, XBright! = 0.5CONST Pi! = 4 * ATN(1), Rad1%% = 25, PerspDist% = 550, DeltaSepn! = 0.25 'small offset to stop balls stickingCONST Alpha! = Pi! / (2 * NoAlphas%%), Phi! = 2 * Pi! / NoPhis%%DIM BallStats!(NoBalls%% - 1, 9), ViewOrder%%(NoBalls%% - 1), Cyperium&(NoBalls%% - 1)DIM MapConv%(NoPhis%%, NoAlphas%%, 1, 2)'Conversion from 2DFOR N%% = 0 TO NoPhis%%    FOR M%% = 0 TO NoAlphas%%        MapConv%(N%%, M%%, 0, 0) = 128 + CINT(127 * SIN((Pi! / 2) - M%% * Alpha!) * COS(N%% * Phi!))        MapConv%(N%%, M%%, 0, 1) = 128 + CINT(127 * SIN((Pi! / 2) - M%% * Alpha!) * SIN(N%% * Phi!))        MapConv%(N%%, M%%, 1, 0) = CINT(Rad1%% * SIN((Pi! / 2) - M%% * Alpha!) * COS(N%% * Phi!))        MapConv%(N%%, M%%, 1, 1) = CINT(Rad1%% * SIN((Pi! / 2) - M%% * Alpha!) * SIN(N%% * Phi!))        MapConv%(N%%, M%%, 1, 2) = CINT(Rad1%% * COS((Pi! / 2) - M%% * Alpha!))    NEXT M%%NEXT N%%'Screen BackgroundTempImage& = _NEWIMAGE(Uscreen%, Vscreen%, 32)_DEST TempImage&COLOR _RGB(255, 255, 255), _RGB(10, 60, 60)CLSFOR UCol% = 0 TO Uscreen% - 1    FOR VCol% = 0 TO Vscreen% - 1        PSET (UCol%, VCol%), _RGB(100, INT(180 * UCol% / Uscreen%), INT(180 * VCol% / Vscreen%))    NEXT VCol%NEXT UCol%Background& = MakeHardware&(TempImage&)'Set array for depth orderJumpStart%% = 1WHILE JumpStart%% <= NoBalls%%: JumpStart%% = JumpStart%% * 2: WENDFOR N%% = 0 TO NoBalls%% - 1    ViewOrder%%(N%%) = N%%NEXT N%%' Define characteristics of ballsDATA 255,0,0DATA 219,80,0DATA 150,100,0DATA 138,117,0DATA 80,155,20DATA 0,255,0DATA 20,135,100DATA 0,105,150DATA 0,60,150DATA 0,0,255FOR N%% = 0 TO NoBalls%% - 1    'Ball mass    BallStats!(N%%, 0) = 0.1 + (0.8 * N%% / (NoBalls%% - 1))    IF BallStats!(N%%, 0) = 0 THEN BallStats!(N%%, 0) = 0.000001    'Ball colours (temporarily use (N%%,1,2,3)    FOR P%% = 1 TO 3        READ BallStats!(N%%, P%%)    NEXT P%%    TempImage& = _NEWIMAGE(256, 256, 32)    _DEST TempImage&    COLOR _RGB(BallStats!(N%%, 1), BallStats!(N%%, 2), BallStats!(N%%, 3)), _RGBA(0, 0, 0, 0)    'Image data goes from 1 to 255 (not 0 to 255)    FOR Z% = 128 TO 255        FOR X% = 1 TO 255            FOR Y% = 1 TO 255                DeltaX% = X% - 127                DeltaY% = Y% - 127                DeltaZ% = Z% - 127                Dist! = SQR((DeltaX% * DeltaX%) + (DeltaY% * DeltaY%) + (DeltaZ% * DeltaZ%))                IF Dist! > 125 AND Dist! < 127 THEN PSET (X%, Y%), _RGB(CINT(Z% * BallStats!(N%%, 1) * (1 - (XBright! * X% / 255)) / 255), CINT(Z% * BallStats!(N%%, 2) * (1 - (XBright! * X% / 255)) / 255), CINT(Z% * BallStats!(N%%, 3) * (1 - (XBright! * X% / 255)) / 255))            NEXT        NEXT    NEXT    Cyperium&(N%%) = MakeHardware&(TempImage&)    'Ball positions/velocities    FOR P%% = 4 TO 6        BallStats!(N%%, P%%) = (0.5 - RND) * 1000    NEXT P%%    FOR P%% = 7 TO 9        Velocity! = 0.5 - RND        IF Velocity! < 0 THEN            Velocity! = Velocity! - 0.5        ELSE            Velocity! = Velocity! + 0.5        END IF        BallStats!(N%%, P%%) = Velocity! * 3    NEXT P%%NEXT N%%'Create screenSCREEN _NEWIMAGE(Uscreen%, Vscreen%, 32)_SCREENMOVE 50, 13_DEST 0_DISPLAYORDER _HARDWARE 'do not even render the software layer, just the hardware one.'Initialise axes and rotationsZRot! = (RND - 0.5) * 0.0011: YRot! = (RND - 0.5) * 0.0011: XRot! = (RND - 0.5) * 0.0011ZTime% = 15 + INT(47 * RND): YTime%% = 15 + INT(47 * RND): XTime% = 15 + INT(47 * RND)ZStart! = TIMER: YStart! = TIMER: XStart! = TIMERZaxis! = 0: Yaxis! = 0: Xaxis! = 0Stereo` = TrueWHILE Stereo`    _LIMIT 120 'Game Frames/Second Rate    _PUTIMAGE (0, 0), Background&    'Calculate moves    FOR N%% = 0 TO NoBalls%% - 1        FOR P%% = 1 TO 3            BallStats!(N%%, 3 + P%%) = BallStats!(N%%, 3 + P%%) + BallStats!(N%%, 6 + P%%)        NEXT P%%    NEXT N%%    'Check for collsions    FOR N%% = 0 TO NoBalls%% - 2        FOR M%% = N%% + 1 TO NoBalls%% - 1            SepnSqd! = (BallStats!(N%%, 4) - BallStats!(M%%, 4)) * (BallStats!(N%%, 4) - BallStats!(M%%, 4)) + (BallStats!(N%%, 5) - BallStats!(M%%, 5)) * (BallStats!(N%%, 5) - BallStats!(M%%, 5)) + (BallStats!(N%%, 6) - BallStats!(M%%, 6)) * (BallStats!(N%%, 6) - BallStats!(M%%, 6))            IF SepnSqd! <= 4 * Rad1%% * Rad1%% THEN                ' Set new velocities                Multiplier! = 0: NDist! = 0: MDist! = 0                FOR P%% = 1 TO 3                    Multiplier! = Multiplier! + (BallStats!(N%%, 6 + P%%) - BallStats!(M%%, 6 + P%%)) * (BallStats!(N%%, 3 + P%%) - BallStats!(M%%, 3 + P%%))                    NDist! = NDist! + BallStats!(N%%, 3 + P%%) * BallStats!(N%%, 3 + P%%)                    MDist! = MDist! + BallStats!(M%%, 3 + P%%) * BallStats!(M%%, 3 + P%%)                NEXT P%%                NDist! = SQR(NDist!)                MDist! = SQR(MDist!)                Multiplier! = 2 * Multiplier! / (SepnSqd! * (BallStats!(N%%, 0) + BallStats!(M%%, 0)))                FOR P%% = 1 TO 3                    SepnVect! = BallStats!(N%%, 3 + P%%) - BallStats!(M%%, 3 + P%%)                    BallStats!(N%%, 6 + P%%) = BallStats!(N%%, 6 + P%%) - (Multiplier! * BallStats!(M%%, 0) * SepnVect!)                    BallStats!(M%%, 6 + P%%) = BallStats!(M%%, 6 + P%%) + (Multiplier! * BallStats!(N%%, 0) * SepnVect!)                    IF NDist! > MDist! THEN                        BallStats!(M%%, 3 + P%%) = BallStats!(M%%, 3 + P%%) - (DeltaSepn! * (BallStats!(N%%, 3 + P%%) - BallStats!(M%%, 3 + P%%)) / SQR(SepnSqd!))                    ELSE                        BallStats!(N%%, 3 + P%%) = BallStats!(N%%, 3 + P%%) + (DeltaSepn! * (BallStats!(N%%, 3 + P%%) - BallStats!(M%%, 3 + P%%)) / SQR(SepnSqd!))                    END IF                NEXT P%%            END IF        NEXT M%%    NEXT N%%    'Look for reflection off sides    FOR N%% = 0 TO NoBalls%% - 1        FOR P%% = 1 TO 3            IF BallStats!(N%%, 3 + P%%) > 500 - Rad1%% THEN                BallStats!(N%%, 6 + P%%) = -BallStats!(N%%, 6 + P%%)                IF BallStats!(N%%, 3 + P%%) + BallStats!(N%%, 6 + P%%) > 500 - Rad1%% THEN                    BallStats!(N%%, 3 + P%%) = BallStats!(N%%, 3 + P%%) - DeltaSepn!                END IF            ELSEIF BallStats!(N%%, 3 + P%%) < -500 + Rad1%% THEN                BallStats!(N%%, 6 + P%%) = -BallStats!(N%%, 6 + P%%)                IF BallStats!(N%%, 3 + P%%) + BallStats!(N%%, 6 + P%%) < -500 + Rad1%% THEN                    BallStats!(N%%, 3 + P%%) = BallStats!(N%%, 3 + P%%) + DeltaSepn!                END IF            END IF        NEXT P%%    NEXT N%%    'Adjust for angle of rotation    FOR N%% = 0 TO NoBalls%% - 1        R2! = SQR(BallStats!(N%%, 4) * BallStats!(N%%, 4) + BallStats!(N%%, 5) * BallStats!(N%%, 5) + BallStats!(N%%, 6) * BallStats!(N%%, 6))        Theta2! = _ACOS(BallStats!(N%%, 6) / R2!)        Phi2! = _ATAN2(BallStats!(N%%, 5), BallStats!(N%%, 4)) + Zaxis!        BallStats!(N%%, 1) = R2! * SIN(Theta2!) * COS(Phi2!)        BallStats!(N%%, 2) = R2! * SIN(Theta2!) * SIN(Phi2!)        BallStats!(N%%, 3) = R2! * COS(Theta2!)        R2! = SQR(BallStats!(N%%, 1) * BallStats!(N%%, 1) + BallStats!(N%%, 2) * BallStats!(N%%, 2) + BallStats!(N%%, 3) * BallStats!(N%%, 3))        Theta2! = _ACOS(BallStats!(N%%, 1) / R2!)        Phi2! = _ATAN2(BallStats!(N%%, 3), BallStats!(N%%, 2)) + Xaxis!        BallStats!(N%%, 2) = R2! * SIN(Theta2!) * COS(Phi2!)        BallStats!(N%%, 3) = R2! * SIN(Theta2!) * SIN(Phi2!)        BallStats!(N%%, 1) = R2! * COS(Theta2!)        R2! = SQR(BallStats!(N%%, 1) * BallStats!(N%%, 1) + BallStats!(N%%, 2) * BallStats!(N%%, 2) + BallStats!(N%%, 3) * BallStats!(N%%, 3))        Theta2! = _ACOS(BallStats!(N%%, 2) / R2!)        Phi2! = _ATAN2(BallStats!(N%%, 1), BallStats!(N%%, 3)) + Yaxis!        BallStats!(N%%, 3) = CINT(R2! * SIN(Theta2!) * COS(Phi2!))        BallStats!(N%%, 1) = CINT(R2! * SIN(Theta2!) * SIN(Phi2!))        BallStats!(N%%, 2) = CINT(R2! * COS(Theta2!))    NEXT N%%    'Change display order: furthest first    Jump%% = JumpStart%%    WHILE Jump%% > 1        Jump%% = (Jump%% - 1) \ 2        DoneHere` = False        WHILE NOT DoneHere`            DoneHere` = True            FOR Upper%% = 1 TO NoBalls%% - Jump%%                Lower%% = Upper%% + Jump%%                '!!! different BallStats! z-dim                IF BallStats!(ViewOrder%%(Upper%% - 1), 3) > BallStats!(ViewOrder%%(Lower%% - 1), 3) THEN                    SWAP ViewOrder%%(Upper%% - 1), ViewOrder%%(Lower%% - 1)                    DoneHere` = False                END IF            NEXT Upper%%        WEND    WEND    'Display balls    FOR N1%% = NoBalls%% - 1 TO 0 STEP -1        P%% = ViewOrder%%(N1%%)        '_PUTIMAGE (BallStats!(P%%, 1) + 500, BallStats!(P%%, 2) + 400), Cyperium&(P%%)        'Ball views done with _MAPTRIANGLE 3D        FOR N%% = 0 TO NoPhis%% - 1            FOR M%% = 0 TO NoAlphas%% - 2                Ax% = MapConv%(N%%, M%%, 1, 0) + BallStats!(P%%, 1)                Ay% = MapConv%(N%%, M%%, 1, 1) + BallStats!(P%%, 2)                Az% = MapConv%(N%%, M%%, 1, 2) + BallStats!(P%%, 3) - PerspDist%                Bx% = MapConv%(N%%, M%% + 1, 1, 0) + BallStats!(P%%, 1)                By% = MapConv%(N%%, M%% + 1, 1, 1) + BallStats!(P%%, 2)                Bz% = MapConv%(N%%, M%% + 1, 1, 2) + BallStats!(P%%, 3) - PerspDist%                Cx% = MapConv%(N%% + 1, M%% + 1, 1, 0) + BallStats!(P%%, 1)                Cy% = MapConv%(N%% + 1, M%% + 1, 1, 1) + BallStats!(P%%, 2)                Cz% = MapConv%(N%% + 1, M%% + 1, 1, 2) + BallStats!(P%%, 3) - PerspDist%                _MAPTRIANGLE (MapConv%(N%%, M%%, 0, 0), MapConv%(N%%, M%%, 0, 1))-(MapConv%(N%%, M%% + 1, 0, 0), MapConv%(N%%, M%% + 1, 0, 1))-(MapConv%(N%% + 1, M%% + 1, 0, 0), MapConv%(N%% + 1, M%% + 1, 0, 1)), Cyperium&(P%%) TO(Ax%, Ay%, Az%)-(Bx%, By%, Bz%)-(Cx%, Cy%, Cz%)                _MAPTRIANGLE (MapConv%(N%% + 1, M%% + 1, 0, 0), MapConv%(N%% + 1, M%% + 1, 0, 1))-(MapConv%(N%% + 1, M%%, 0, 0), MapConv%(N%% + 1, M%%, 0, 1))-(MapConv%(N%%, M%%, 0, 0), MapConv%(N%%, M%%, 0, 1)), Cyperium&(P%%) TO(Cx%, Cy%, Cz%)-(MapConv%(N%% + 1, M%%, 1, 0) + BallStats!(P%%, 1), MapConv%(N%% + 1, M%%, 1, 1) + BallStats!(P%%, 2), MapConv%(N%% + 1, M%%, 1, 2) + BallStats!(P%%, 3) - PerspDist%)-(Ax%, Ay%, Az%)                '_MAPTRIANGLE (MapConv%(N%%, M%%, 0, 0), MapConv%(N%%, M%%, 0, 1))-(MapConv%(N%%, M%% + 1, 0, 0), MapConv%(N%%, M%% + 1, 0, 1))-(MapConv%(N%% + 1, M%% + 1, 0, 0), MapConv%(N%% + 1, M%% + 1, 0, 1)), Cyperium&(P%%) TO(MapConv%(N%%, M%%, 1, 0) + BallStats!(P%%, 1), MapConv%(N%%, M%%, 1, 1) + BallStats!(P%%, 2), MapConv%(N%%, M%%, 1, 2) + BallStats!(P%%, 3) - PerspDist%)-(MapConv%(N%%, M%% + 1, 1, 0) + BallStats!(P%%, 1), MapConv%(N%%, M%% + 1, 1, 1) + BallStats!(P%%, 2), MapConv%(N%%, M%% + 1, 1, 2) + BallStats!(P%%, 3) - PerspDist%)-(MapConv%(N%% + 1, M%% + 1, 1, 0) + BallStats!(P%%, 1), MapConv%(N%% + 1, M%% + 1, 1, 1) + BallStats!(P%%, 2), MapConv%(N%% + 1, M%% + 1, 1, 2) + BallStats!(P%%, 3) - PerspDist%)                '_MAPTRIANGLE (MapConv%(N%% + 1, M%% + 1, 0, 0), MapConv%(N%% + 1, M%% + 1, 0, 1))-(MapConv%(N%% + 1, M%%, 0, 0), MapConv%(N%% + 1, M%%, 0, 1))-(MapConv%(N%%, M%%, 0, 0), MapConv%(N%%, M%%, 0, 1)), Cyperium&(P%%) TO(MapConv%(N%% + 1, M%% + 1, 1, 0) + BallStats!(P%%, 1), MapConv%(N%% + 1, M%% + 1, 1, 1) + BallStats!(P%%, 2), MapConv%(N%% + 1, M%% + 1, 1, 2) + BallStats!(P%%, 3) - PerspDist%)-(MapConv%(N%% + 1, M%%, 1, 0) + BallStats!(P%%, 1), MapConv%(N%% + 1, M%%, 1, 1) + BallStats!(P%%, 2), MapConv%(N%% + 1, M%%, 1, 2) + BallStats!(P%%, 3) - PerspDist%)-(MapConv%(N%%, M%%, 1, 0) + BallStats!(P%%, 1), MapConv%(N%%, M%%, 1, 1) + BallStats!(P%%, 2), MapConv%(N%%, M%%, 1, 2) + BallStats!(P%%, 3) - PerspDist%)            NEXT M%%            _MAPTRIANGLE (MapConv%(N%%, NoAlphas%% - 1, 0, 0), MapConv%(N%%, NoAlphas%% - 1, 0, 1))-(128, 128)-(MapConv%(N%% + 1, NoAlphas%% - 1, 0, 0), MapConv%(N%% + 1, NoAlphas%% - 1, 0, 1)), Cyperium&(P%%) TO(Bx%, By%, Bz%)-(BallStats!(P%%, 1), BallStats!(P%%, 2), BallStats!(P%%, 3) + Rad1%% - PerspDist%)-(Cx%, Cy%, Cz%)            '_MAPTRIANGLE (MapConv%(N%%, NoAlphas%% - 1, 0, 0), MapConv%(N%%, NoAlphas%% - 1, 0, 1))-(128, 128)-(MapConv%(N%% + 1, NoAlphas%% - 1, 0, 0), MapConv%(N%% + 1, NoAlphas%% - 1, 0, 1)), Cyperium&(P%%) TO(MapConv%(N%%, NoAlphas%% - 1, 1, 0) + BallStats!(P%%, 1), MapConv%(N%%, NoAlphas%% - 1, 1, 1) + BallStats!(P%%, 2), MapConv%(N%%, NoAlphas%% - 1, 1, 2) + BallStats!(P%%, 3) - PerspDist%)-(BallStats!(P%%, 1), BallStats!(P%%, 2), BallStats!(P%%, 3) + Rad1%% - PerspDist%)-(MapConv%(N%% + 1, NoAlphas%% - 1, 1, 0) + BallStats!(P%%, 1), MapConv%(N%% + 1, NoAlphas%% - 1, 1, 1) + BallStats!(P%%, 2), MapConv%(N%% + 1, NoAlphas%% - 1, 1, 2) + BallStats!(P%%, 3) - PerspDist%)        NEXT N%%    NEXT N1%%    'rotate around z- axis    Zaxis! = Zaxis! + ZRot!    IF Zaxis! > Pi! THEN        Zaxis! = Zaxis! - 2 * Pi!    ELSEIF Zaxis! < -Pi! THEN        Zaxis! = Zaxis! + 2 * Pi!    END IF    'rotate around y- axis    Yaxis! = Yaxis! + YRot!    IF Yaxis! > Pi! THEN        Yaxis! = Yaxis! - 2 * Pi!    ELSEIF Yaxis! < -Pi! THEN        Yaxis! = Yaxis! + 2 * Pi!    END IF    'rotate around x- axis    Xaxis! = Xaxis! + XRot!    IF Xaxis! > Pi! THEN        Xaxis! = Xaxis! - 2 * Pi!    ELSEIF Xaxis! < -Pi! THEN        Xaxis! = Xaxis! + 2 * Pi!    END IF    IF TIMER > ZStart! + ZTime% THEN        ZRot! = (RND - 0.5) * 0.0011        ZTime% = 15 + INT(47 * RND)        ZStart! = TIMER        RANDOMIZE (ZStart!)    END IF    IF TIMER > YStart! + YTime%% THEN        YRot! = (RND - 0.5) * 0.0011        YTime%% = 15 + INT(47 * RND)        YStart! = TIMER    END IF    IF TIMER > XStart! + XTime% THEN        XRot! = (RND - 0.5) * 0.0011        XTime% = 15 + INT(47 * RND)        XStart! = TIMER    END IF    IF _KEYHIT = 27 THEN Stereo` = False 'Esc  to quit    _DISPLAYWENDSYSTEMFUNCTION MakeHardware& (Imagename&)    MakeHardware& = _COPYIMAGE(Imagename&, 33)    _FREEIMAGE Imagename&END FUNCTION`
Richard
« Last Edit: April 22, 2018, 09:48:15 am by Richard Notley »
QB64 Projects: https://www.dropbox.com/s/yzn5uf4dzztao2f/QB64%20Projects.pdf?dl=0

#### bplus

• Hero Member
•     • • Posts: 756
• B = B + _ ##### Re: 3D Bouncing Balls
« Reply #1 on: April 21, 2018, 07:49:28 am »
Hi Richard,

I like seeing how balls are growing as come closer and shrinking as the go away from observer but the balls are taking weird bounces, is that rotating cube (from the other thread) still acting on the balls? If so, how can balls escape out to edge of screen when they grow big?
« Last Edit: April 21, 2018, 07:56:35 am by bplus »
Will you still love me, will you still need me, when I'm (QB) 64?

#### Richard Notley ##### Re: 3D Bouncing Balls
« Reply #2 on: April 21, 2018, 08:52:18 am »
bplus,  yes you are correct about the bounces.  The balls are moving inside an invisible cube and bouncing off the sides (the cube is rotating randomly about the three axes).  In this version, I have not displayed the cube.  The balls will not get displayed if the z- dimension (the direction into/out of the screen) is > -1 :- in _MAPTRIANGLE(3D) the allowed values of z- are -1 to -10000.  This version is about demonstrating the mapping of the surface of a solid 3D object (ball).  In fact the program only maps the front hemisphere of each ball.  I have still to work out whether the distortions of the spheres are just an artefact of the _MAPTRIANGLE perspective maths, or whether I've done something stupid.

Richard
QB64 Projects: https://www.dropbox.com/s/yzn5uf4dzztao2f/QB64%20Projects.pdf?dl=0

#### FellippeHeitor ##### Re: 3D Bouncing Balls
« Reply #3 on: April 21, 2018, 02:41:28 pm »
Nice one, Richard. I do see the sphere distortion at the screen edges, hope you sort it out and let us know soon.
- InForm for QB64 http://qb64.org/inform

- vWATCH64 (debugger for QB64) http://bit.ly/vWATCH64v1-103

- Games: http://bit.ly/2048_qb64 * http://bit.ly/ClonedShades_qb64source * http://bit.ly/2aqK866 * http://bit.ly/SpaceshipQB64 * http://bit.ly/2rD1pPP

#### Petr

• Hero Member
•     • • Posts: 656 ##### Re: 3D Bouncing Balls
« Reply #4 on: April 22, 2018, 10:16:29 am »
This is a beautiful code. Again I can something to learn. Coding is relax.