1 /*
  2  * Copyright (c) 2024, Oracle and/or its affiliates. All rights reserved.
  3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
  4  *
  5  * This code is free software; you can redistribute it and/or modify it
  6  * under the terms of the GNU General Public License version 2 only, as
  7  * published by the Free Software Foundation.  Oracle designates this
  8  * particular file as subject to the "Classpath" exception as provided
  9  * by Oracle in the LICENSE file that accompanied this code.
 10  *
 11  * This code is distributed in the hope that it will be useful, but WITHOUT
 12  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 13  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
 14  * version 2 for more details (a copy is included in the LICENSE file that
 15  * accompanied this code).
 16  *
 17  * You should have received a copy of the GNU General Public License version
 18  * 2 along with this work; if not, write to the Free Software Foundation,
 19  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
 20  *
 21  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
 22  * or visit www.oracle.com if you need additional information or have any
 23  * questions.
 24  */
 25 package nbody;
 26 
 27 import wrap.Wrap;
 28 import wrap.glwrap.GLTexture;
 29 import wrap.glwrap.GLWindow;
 30 
 31 import java.io.IOException;
 32 import java.lang.foreign.Arena;
 33 import java.util.stream.IntStream;
 34 
 35 import static opengl.opengl_h.GLUT_DEPTH;
 36 import static opengl.opengl_h.GLUT_DOUBLE;
 37 import static opengl.opengl_h.GLUT_RGB;
 38 import static opengl.opengl_h.GL_COLOR_BUFFER_BIT;
 39 import static opengl.opengl_h.GL_DEPTH_BUFFER_BIT;
 40 import static opengl.opengl_h.GL_MODELVIEW;
 41 import static opengl.opengl_h.GL_TEXTURE_2D;
 42 import static opengl.opengl_h.glBindTexture;
 43 import static opengl.opengl_h.glClear;
 44 import static opengl.opengl_h.glClearColor;
 45 import static opengl.opengl_h.glColor3f;
 46 import static opengl.opengl_h.glDisable;
 47 import static opengl.opengl_h.glEnable;
 48 import static opengl.opengl_h.glMatrixMode;
 49 import static opengl.opengl_h.glRasterPos2f;
 50 import static opengl.opengl_h.glScalef;
 51 import static opengl.opengl_h.glTexCoord2f;
 52 import static opengl.opengl_h.glVertex3f;
 53 import static opengl.opengl_h.glutBitmapCharacter;
 54 import static opengl.opengl_h.glutBitmapTimesRoman24$segment;
 55 import static opengl.opengl_h.glutSwapBuffers;
 56 
 57 
 58 public abstract class NBodyGLWindow extends GLWindow {
 59     protected final float delT = .1f;
 60 
 61     protected final float espSqr = 0.1f;
 62 
 63     protected final float mass = .5f;
 64 
 65     protected final GLTexture particle;
 66     protected final Wrap.Float4Arr xyzPosFloatArr;
 67     protected final Wrap.Float4Arr xyzVelFloatArr;
 68 
 69     protected int bodyCount;
 70     protected int frameCount = 0;
 71     protected final long startTime = System.currentTimeMillis();
 72 
 73     protected final Mode mode;
 74 
 75     public NBodyGLWindow(Arena arena, int width, int height, GLTexture particle, int bodyCount, Mode mode) {
 76         super(arena, width, height, "nbody", GLUT_DOUBLE() | GLUT_RGB() | GLUT_DEPTH(), particle);
 77         this.particle = particle;
 78         this.bodyCount = bodyCount;
 79         this.xyzPosFloatArr = Wrap.Float4Arr.of(arena, bodyCount);
 80         this.xyzVelFloatArr = Wrap.Float4Arr.of(arena, bodyCount);
 81 
 82         this.mode = mode;
 83         final float maxDist = 80f;
 84 
 85         System.out.println(bodyCount + " particles");
 86 
 87         for (int body = 0; body < bodyCount; body++) {
 88             final float theta = (float) (Math.random() * Math.PI * 2);
 89             final float phi = (float) (Math.random() * Math.PI * 2);
 90             final float radius = (float) (Math.random() * maxDist);
 91 
 92             var radial = Wrap.Float4Arr.float4.of(
 93                     (float) (radius * Math.cos(theta) * Math.sin(phi)),
 94                     (float) (radius * Math.sin(theta) * Math.sin(phi)),
 95                     (float) (radius * Math.cos(phi)),
 96                     0f);
 97 
 98             xyzPosFloatArr.set(body, radial);
 99         }
100     }
101 
102 
103     float rot = 0f;
104 
105     public static void run(int body, int size, Wrap.Float4Arr xyzPos, Wrap.Float4Arr xyzVel, float mass, float delT, float espSqr) {
106         float accx = 0.f;
107         float accy = 0.f;
108         float accz = 0.f;
109 
110         final float myPosx = xyzPos.getx(body);
111         final float myPosy = xyzPos.gety(body);
112         final float myPosz = xyzPos.getz(body);
113         final float myVelx = xyzVel.getx(body);
114         final float myVely = xyzVel.gety(body);
115         final float myVelz = xyzVel.getz(body);
116 
117         for (int i = 0; i < size; i++) {
118             final float dx = xyzPos.get(i).x() - myPosx;
119             final float dy = xyzPos.get(i).y() - myPosy;
120             final float dz = xyzPos.get(i).z() - myPosz;
121             final float invDist = 1 / (float) Math.sqrt((dx * dx) + (dy * dy) + (dz * dz) + espSqr);
122             final float s = mass * invDist * invDist * invDist;
123             accx = accx + (s * dx);
124             accy = accy + (s * dy);
125             accz = accz + (s * dz);
126         }
127         accx = accx * delT;
128         accy = accy * delT;
129         accz = accz * delT;
130 
131         xyzPos.setx(body, myPosx + (myVelx + accx * .5f) * delT);
132         xyzPos.sety(body, myPosy + (myVely + accy * .5f) * delT);
133         xyzPos.setz(body, myPosz + (myVelz + accz * .5f) * delT);
134 
135         xyzVel.setx(body, myVelx + accx);
136         xyzVel.sety(body, myVely + accy);
137         xyzVel.setz(body, myVelz + accz);
138     }
139 
140     public static void runf4(int body, int size, Wrap.Float4Arr xyzPos, Wrap.Float4Arr xyzVel, float mass, float delT, float espSqr) {
141         var accf4 = Wrap.Float4Arr.float4.zero;
142         var myPosf4 = xyzPos.get(body);
143         var myVelf4 = xyzVel.get(body);
144         for (int i = 0; i < size; i++) {
145             var delta = xyzPos.get(i).sub(myPosf4); // xyz[i]-myPos
146             var dSqrd = delta.mul(delta);           // delta^2
147             var invDist = 1f / (float) Math.sqrt(dSqrd.x() + dSqrd.y() + dSqrd.z() + espSqr);
148             accf4 = accf4.add(delta.mul(mass * invDist * invDist * invDist)); // accf4 += delta*(invDist^3*mass)
149         }
150         accf4 = accf4.mul(delT);
151         xyzPos.set(body, myPosf4.add(myVelf4.mul(delT)).add(accf4.mul(.5f * delT)));
152         xyzVel.set(body, myVelf4.add(accf4));
153     }
154 
155     protected void moveBodies() {
156         switch (mode) {
157             case JavaMT4 -> IntStream.range(0, bodyCount).parallel().forEach(
158                     i -> runf4(i, bodyCount, xyzPosFloatArr, xyzVelFloatArr, mass, delT, espSqr)
159             );
160             case JavaMT -> IntStream.range(0, bodyCount).parallel().forEach(
161                     i -> run(i, bodyCount, xyzPosFloatArr, xyzVelFloatArr, mass, delT, espSqr)
162             );
163             case JavaSeq4 -> IntStream.range(0, bodyCount).forEach(
164                     i -> runf4(i, bodyCount, xyzPosFloatArr, xyzVelFloatArr, mass, delT, espSqr)
165             );
166             case JavaSeq -> IntStream.range(0, bodyCount).forEach(
167                     i -> run(i, bodyCount, xyzPosFloatArr, xyzVelFloatArr, mass, delT, espSqr)
168             );
169             default -> throw new RuntimeException("Should never get here");
170         }
171     }
172 
173     protected static final float WEST = 0;
174     protected static final float EAST = 1;
175     protected static final float NORTH = 0;
176     protected static final float SOUTH = 1;
177     protected static float dx = -.5f;
178     protected static float dy = -.5f;
179     protected static float dz = -.5f;
180 
181 
182     @Override
183     public void display() {
184         moveBodies();
185         glClearColor(0f, 0f, 0f, 0f);
186         glClear(GL_COLOR_BUFFER_BIT() | GL_DEPTH_BUFFER_BIT());
187         glEnable(GL_TEXTURE_2D()); // Annoyingly important,
188         glBindTexture(GL_TEXTURE_2D(), textureBuf.get(particle.idx));
189         glPushMatrix1(() -> {
190             glScalef(.01f, .01f, .01f);
191             glColor3f(1f, 1f, 1f);
192             glQuads(() -> {
193                 for (int bodyIdx = 0; bodyIdx < bodyCount; bodyIdx++) {
194                     var bodyf4 = xyzPosFloatArr.get(bodyIdx);
195 
196                             /*
197                              * Textures are mapped to a quad by defining the vertices in
198                              * the order SW,NW,NE,SE
199                              &
200                              *   2--->3
201                              *   ^    |
202                              *   |    v
203                              *   1    4
204                              *
205                              * Here we are describing the 'texture plane' for the body.
206                              * Ideally we need to rotate this to point to the camera (see billboarding)
207                              */
208 
209                     glTexCoord2f(WEST, SOUTH);
210                     glVertex3f(bodyf4.x() + WEST + dx, bodyf4.y() + SOUTH + dy, bodyf4.z() + dz);
211                     glTexCoord2f(WEST, NORTH);
212                     glVertex3f(bodyf4.x() + WEST + dx, bodyf4.y() + NORTH + dy, bodyf4.z() + dz);
213                     glTexCoord2f(EAST, NORTH);
214                     glVertex3f(bodyf4.x() + EAST + dx, bodyf4.y() + NORTH + dy, bodyf4.z() + dz);
215                     glTexCoord2f(EAST, SOUTH);
216                     glVertex3f(bodyf4.x() + EAST + dx, bodyf4.y() + SOUTH + dy, bodyf4.z() + dz);
217 
218                 }
219             });
220         });
221         glDisable(GL_TEXTURE_2D()); // Annoyingly important .. took two days to work that out
222         glMatrixMode(GL_MODELVIEW());
223         glPushMatrix1(() -> {
224             glColor3f(0.0f, 1.0f, 0.0f);
225             var font = glutBitmapTimesRoman24$segment();
226             long elapsed = System.currentTimeMillis() - startTime;
227             float secs = elapsed / 1000f;
228             var FPS = "Mode: " + mode.toString() + " Bodies " + bodyCount + " FPS: " + ((frameCount / secs));
229             glRasterPos2f(-.8f, .7f);
230             for (int c : FPS.getBytes()) {
231                 glutBitmapCharacter(font, c);
232             }
233         });
234         glutSwapBuffers();
235         frameCount++;
236     }
237 
238     @Override
239     public void onIdle() {
240         // rot += 1f;
241         super.onIdle();
242     }
243 }
244