swift sphere combine star data
Asked Answered
A

1

1

i want to build a stargazing app. And now i already built a sphere and cover it with a star map (based on celestial coordinates). ( https://svs.gsfc.nasa.gov/cgi-bin/details.cgi?aid=3895 )

now i have a json file which has the star catalog from YBS. (also based on celestial coordinates). ( http://tdc-www.harvard.edu/catalogs/bsc5.html )

i want to combine the data with star map, wishing the map to show the name of Constellation whenever my camera node turn to that place. but i don't know how to combine data and sphere. since the sphere will rotate due to user's latitude and time. The star data's coordinates also have to change.

Does anyone has suggestions?

Archiearchiepiscopacy answered 21/10, 2016 at 7:15 Comment(0)
W
5

Not sure in your environment but in your case I would:

  1. render textured sphere (with the deep map)

    The sphere must be centered in your camera position and have big radius covering whole view area. To avoid seems in polar regions you can use this:

  2. then render the BSC

    Start with dots (Points). How ever if you want to have (un)zoom and or better visualize the magnitude of stars then you need Blending capabilities and render the stars as semi-transparent disc facing camera (billboards) with radius and intensity dependent on zoom and magnitude.

    I usually use this texture for local star (D=1/3 width, rest is corona):

    local star

    And this for the BSC stars (D = almost 100% width):

    distant star

    The alpha is computed as color intensity alpha=r+g+b/3.

    This way visual and physical binaries will blend together adding their visual magnitude as in reality. This will also avoid the flickering during any view change due to aliasing between very close stars.

    Here GIF animation of zoom (colors are dithered hence the greenish noise) so you got feeling how it looks like:

    distant star zoom

[Edit1] simple full VCL C++ OpenGL example

I use the deep maps from your link. They are rendered with spherical distortion so Sphere triangulation has no point (will not improve anything as the source data is already wrong). That implies the use of standard spherical mesh with singularities on poles. The JPG files are unusable due to lossy compression artifacts messing everything up (especially near poles). I use the TIF and rescale all textures to 4096x2048 resolution. Lower resolution does not feel right to me.

After this is is just a matter of blending the sphere skybox with each texture together. The result is like this:

overview

Which shows North pole area so you can see the distortions are not that bad (unless you zoom of coarse).

After this you can add the stars that are not present in the deep map. But as the deep map already has the BSC included I see no point of adding it again (unless you want to calibrate your renderer to be the same as the deep map was created with).

As requested here Complete example in C++/GL It was written in BDS2006 so it is based on VCL Form application with single 20ms Timer on it. You can ignore all the VCL stuff (the only thing that is used form it is bitmap loader and I am confident you got yours already) and use only event code you need.

//---------------------------------------------------------------------------
#include <vcl.h>
#include <Math.h>
#include <gl/gl.h>
#include <gl/glu.h>
#pragma hdrstop
#include "Unit1.h"
//---------------------------------------------------------------------------
#pragma package(smart_init)
#pragma resource "*.dfm"
TForm1 *Form1;
//---------------------------------------------------------------------------
// key codes (Arrows + Space), pressed state
WORD key_left =37; bool _left =false;
WORD key_right=39; bool _right=false;
WORD key_up   =38; bool _up   =false;
WORD key_down =40; bool _down =false;
WORD key_reset=32; bool _reset=false;
//---------------------------------------------------------------------------
GLfloat rep[16],inv[16]; // camera matrix and its pseudo inverse
void pseudo_inverse(GLfloat *a,GLfloat *b)  // a = inverse(b)
    {
    // this works only for orthonormal matrices with origin (0,0,0) and no projections
    a[ 0]=b[ 0]; a[ 4]=b[ 1]; a[ 8]=b[ 2]; a[12]=b[ 3];
    a[ 1]=b[ 4]; a[ 5]=b[ 5]; a[ 9]=b[ 6]; a[13]=b[ 7];
    a[ 2]=b[ 8]; a[ 6]=b[ 9]; a[10]=b[10]; a[14]=b[11];
    a[ 3]=b[12]; a[ 7]=b[13]; a[11]=b[14]; a[15]=b[15];
    }
//---------------------------------------------------------------------------
const int nb=64;        // slices
const int na=nb<<1;     // points per equator
const int _skybox_textures=4;
class skybox
    {
public:
    bool _init;             // has been initiated ?
    GLfloat pos[na][nb][3]; // vertex
    GLfloat txr[na][nb][2]; // texcoord
    GLuint  txrid[_skybox_textures]; // texture ids
    skybox() { _init=false; }
    ~skybox() { if (_init) glDeleteTextures(_skybox_textures,txrid); }
    void init();        // call after OpenGL is already working !!!
    void draw();
    };
void skybox::init()
    {
    if (!_init) { _init=true; glGenTextures(_skybox_textures,txrid); }
    GLfloat x,y,z,a,b,da,db,r=99.9;
    GLfloat tx0,tdx,ty0,tdy;// just correction if CLAMP_TO_EDGE is not available
    int ia,ib;

    // a,b to texture coordinate system
    tx0=0.0;
    ty0=0.5;
    tdx=0.5/M_PI;
    tdy=1.0/M_PI;

    // load textures to GPU memory
    Graphics::TBitmap *bmp=new Graphics::TBitmap;   // new bmp
    #ifndef GL_CLAMP_TO_EDGE
    #define GL_CLAMP_TO_EDGE 0x812F
    #endif
    for (int i=0;i<_skybox_textures;i++)
        {
        Byte q;
        unsigned int *pp;
        int xs,ys,x,y,adr,*txr;
        union { unsigned int c32; Byte db[4]; } c;
        // load bmp from file
             if (i==0) bmp->LoadFromFile("skybox_grid.bmp");
        else if (i==1) bmp->LoadFromFile("skybox_sectors.bmp");
        else if (i==2) bmp->LoadFromFile("skybox_figures.bmp");
        else if (i==3) bmp->LoadFromFile("skybox_stars.bmp");
        else break;
        bmp->HandleType=bmDIB;      // allow direct access to pixels
        bmp->PixelFormat=pf32bit;   // set pixel to 32bit so int is the same size as pixel
        xs=bmp->Width;              // resolution should be power of 2
        ys=bmp->Height;
        txr=new int[xs*ys];         // create 1D txr[] array and store texture in it in GL manner
        for(adr=0,y=0;y<ys;y++)
            {
            pp=(unsigned int*)bmp->ScanLine[y];
            for(x=0;x<xs;x++,adr++)
                {
                // rgb2bgr and copy bmp -> txr[]
                c.c32=pp[x];
                q      =c.db[2];
                c.db[2]=c.db[0];
                c.db[0]=q;
                txr[adr]=c.c32;
                }
            }
        glEnable(GL_TEXTURE_2D);    // copy txr[] to GL
        glBindTexture(GL_TEXTURE_2D,txrid[i]);
        glPixelStorei(GL_UNPACK_ALIGNMENT, 4);
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S,GL_CLAMP_TO_EDGE);
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T,GL_CLAMP_TO_EDGE);
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER,GL_LINEAR);
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER,GL_LINEAR);
        glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE,GL_MODULATE);
        glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, xs, ys, 0, GL_RGBA, GL_UNSIGNED_BYTE, txr);
        glDisable(GL_TEXTURE_2D);
        delete[] txr;               // release memory
        }
    delete bmp;
    // generate sphere mesh
    da=(2.0*M_PI)/GLfloat(na-1);
    db=     M_PI /GLfloat(nb-1);
    for (ib=0,b=-0.5*M_PI;ib<nb;ib++,b+=db)
    for (ia=0,a= 0.0     ;ia<na;ia++,a+=da)
        {
        x=cos(b)*cos(a);
        y=cos(b)*sin(a);
        z=sin(b);
        pos[ia][ib][0]=r*x;
        pos[ia][ib][1]=r*y;
        pos[ia][ib][2]=r*z;
        txr[ia][ib][0]=tx0+(a*tdx);
        txr[ia][ib][1]=ty0+(b*tdy);
        }
    }
void skybox::draw()
    {
    if (!_init) return;
    int i,ia,ib0,ib1;
    // color table
    GLfloat col[_skybox_textures][3]=
        {
        // R   G   B
        { 0.3,0.2,0.4 },    // Ra,Dec grid
        { 0.0,0.2,0.3 },    // sectors
        { 0.0,0.3,0.4 },    // figures
        { 1.0,1.0,1.0 },    // stars
        };
    // modlevie = inverse of camera matrix to allow local coordinate system rotations
    glMatrixMode(GL_MODELVIEW);
    glPushMatrix();
    glLoadMatrixf(inv);
    // set rendering pipeline
    glDisable(GL_DEPTH_TEST);
    glEnable(GL_TEXTURE_2D);
    glEnable(GL_BLEND);
    glBlendFunc(GL_SRC_COLOR, GL_ONE_MINUS_SRC_COLOR);
    // render mesh once per each texture layer (stars are last)
    for (i=0;i<_skybox_textures;i++)
        {
        glBindTexture(GL_TEXTURE_2D,txrid[i]);
        glColor3fv(col[i]);
        for (ib0=0,ib1=1;ib1<nb;ib0=ib1,ib1++)
            {
            glBegin(GL_QUAD_STRIP);
            for (ia=0;ia<na;ia++)
                {
                glTexCoord2fv(txr[ia][ib0]);
                glVertex3fv  (pos[ia][ib0]);
                glTexCoord2fv(txr[ia][ib1]);
                glVertex3fv  (pos[ia][ib1]);
                }
            glEnd();
            }
        }
    // restore states ...
    glEnable(GL_DEPTH_TEST);
    glDisable(GL_TEXTURE_2D);
    glDisable(GL_BLEND);
    glMatrixMode(GL_MODELVIEW);
    glPopMatrix();
    }
//---------------------------------------------------------------------------
skybox sky;
//---------------------------------------------------------------------------
int TForm1::ogl_init()
    {
    if (ogl_inicialized) return 1;
    hdc = GetDC(Form1->Handle);             // get device context
    PIXELFORMATDESCRIPTOR pfd;
    ZeroMemory( &pfd, sizeof( pfd ) );      // set the pixel format for the DC
    pfd.nSize = sizeof( pfd );
    pfd.nVersion = 1;
    pfd.dwFlags = PFD_DRAW_TO_WINDOW | PFD_SUPPORT_OPENGL | PFD_DOUBLEBUFFER;
    pfd.iPixelType = PFD_TYPE_RGBA;
    pfd.cColorBits = 24;
    pfd.cDepthBits = 24;
    pfd.iLayerType = PFD_MAIN_PLANE;
    SetPixelFormat(hdc,ChoosePixelFormat(hdc, &pfd),&pfd);
    hrc = wglCreateContext(hdc);            // create current rendering context
    if(hrc == NULL)
            {
            ShowMessage("Could not initialize OpenGL Rendering context !!!");
            ogl_inicialized=0;
            return 0;
            }
    if(wglMakeCurrent(hdc, hrc) == false)
            {
            ShowMessage("Could not make current OpenGL Rendering context !!!");
            wglDeleteContext(hrc);          // destroy rendering context
            ogl_inicialized=0;
            return 0;
            }
    ogl_resize();
    glEnable(GL_DEPTH_TEST);                // Zbuf
    glDisable(GL_CULL_FACE);                // vynechavaj odvratene steny
    glDisable(GL_TEXTURE_2D);               // pouzivaj textury, farbu pouzivaj z textury
    glDisable(GL_BLEND);                    // priehladnost
    glShadeModel(GL_SMOOTH);                // gourard shading
    glClearColor(0.0f, 0.0f, 0.0f, 1.0f);   // background color
    ogl_inicialized=1;

    return 1;
    }
//---------------------------------------------------------------------------
void TForm1::ogl_exit()
    {
    if (!ogl_inicialized) return;
    wglMakeCurrent(NULL, NULL);     // release current rendering context
    wglDeleteContext(hrc);          // destroy rendering context
    ogl_inicialized=0;
    }
//---------------------------------------------------------------------------
void TForm1::ogl_draw()
    {
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

    sky.draw();

    glFlush();
    SwapBuffers(hdc);
    }
//---------------------------------------------------------------------------
void TForm1::ogl_resize()
    {
    xs=ClientWidth;
    ys=ClientHeight;
    if (xs<=0) xs = 1;                  // Prevent a divide by zero
    if (ys<=0) ys = 1;
    if (!ogl_inicialized) return;
    glViewport(0,0,xs,ys);              // Set Viewport to window dimensions
    glMatrixMode(GL_PROJECTION);        // operacie s projekcnou maticou
    glLoadIdentity();                   // jednotkova matica projekcie
    gluPerspective(60,float(xs)/float(ys),0.1,100.0); // matica=perspektiva,120 stupnov premieta z viewsize do 0.1
    glMatrixMode(GL_TEXTURE);           // operacie s texturovou maticou
    glLoadIdentity();                   // jednotkova matica textury
    glMatrixMode(GL_MODELVIEW);         // operacie s modelovou maticou
    glLoadIdentity();                   // jednotkova matica modelu (objektu)
    ogl_draw();
    }
//---------------------------------------------------------------------------
__fastcall TForm1::TForm1(TComponent* Owner):TForm(Owner)
    {
    ogl_inicialized=0;
    hdc=NULL;
    hrc=NULL;
    ogl_init();
    sky.init();
    int i;  // unit matrices at start
    for (i=0;i<16;i++) rep[i]=0.0;
    for (i=0;i<16;i+=5) rep[i]=1.0;
    for (i=0;i<16;i++) inv[i]=rep[i];
    }
//---------------------------------------------------------------------------
void __fastcall TForm1::FormDestroy(TObject *Sender)   { ogl_exit(); }
void __fastcall TForm1::FormResize(TObject *Sender)    { ogl_resize(); }
void __fastcall TForm1::Splitter1Moved(TObject *Sender){ ogl_resize(); }
void __fastcall TForm1::FormPaint(TObject *Sender)     { ogl_draw(); }
//---------------------------------------------------------------------------
void __fastcall TForm1::Timer1Timer(TObject *Sender)
    {
    GLfloat da=5.0; // angular turn speed in [deg/timer_iteration]
    pseudo_inverse(inv,rep);
    glMatrixMode(GL_MODELVIEW);
    glPushMatrix();
    glLoadMatrixf(rep);
    bool _redraw=false;
    if (_left ) { _redraw=true; glRotatef(+da,0.0,1.0,0.0); }
    if (_right) { _redraw=true; glRotatef(-da,0.0,1.0,0.0); }
    if (_up   ) { _redraw=true; glRotatef(+da,1.0,0.0,0.0); }
    if (_down ) { _redraw=true; glRotatef(-da,1.0,0.0,0.0); }
    if (_reset) { _redraw=true; glLoadIdentity(); }
    if (_redraw)
        {
        glGetFloatv(GL_MODELVIEW_MATRIX,rep);
        pseudo_inverse(inv,rep);
        }
    glPopMatrix();
    if (_redraw) ogl_draw();
    }
//---------------------------------------------------------------------------
void __fastcall TForm1::FormKeyUp(TObject *Sender, WORD &Key, TShiftState Shift)
    {
    if (Key==key_left ) _left =false;
    if (Key==key_right) _right=false;
    if (Key==key_up   ) _up   =false;
    if (Key==key_down ) _down =false;
    if (Key==key_reset) _reset=false;
    Key=0;  // key is handled
    }
//---------------------------------------------------------------------------
void __fastcall TForm1::FormKeyDown(TObject *Sender, WORD &Key, TShiftState Shift)
    {
    // on key down event
    if (Key==key_left ) _left =true;
    if (Key==key_right) _right=true;
    if (Key==key_up   ) _up   =true;
    if (Key==key_down ) _down =true;
    if (Key==key_reset) _reset=true;
    Key=0;  // key is handled
    }
//---------------------------------------------------------------------------
void __fastcall TForm1::FormActivate(TObject *Sender)
    {
    _left =false; // clear key flags after focus change
    _right=false; // just to avoid constantly "pressed" keys
    _up   =false; // after window focus swaping during key press
    _down =false; // many games are ignoring this and you need to
    _reset=false; // press&release the stuck key again to stop movement ...
    }
//---------------------------------------------------------------------------

Here compiled Demo and full source with the textures included

Control is via keyboard arrows and space. Now it is just a matter of playing with colors,blend functions etc. Example does use only OpenGL 1.0 without extension (except the CLAMP_TO_EDGE).

You can exchange the multiple times rendering to single pass with MultiTexturing with the proper combination functions but I do not use that feature for a long time (as I switch to GLSL instead) so I am not confident to add a code for that.

Have fun.

Wavy answered 21/10, 2016 at 8:31 Comment(8)
@Unheilig btw if youre interested take a look at the last 3 links in here Is it possible to make realistic n-body solar system simulation in matter of size and mass? especialy the The Colors of the StarsWavy
So, here I am with my questions: 1: why did you use GL_CLAMP_TO_EDGE 0x812F? 2: for the part that generates the mesh, can you elaborate more on ... = r*x; .. = r*y; r*z; .. = tx0+(a*tdx); .. =(b*tdy); mathematically, and lastly the following part where you scan in the bmp: c.c32=pp[x]; q = c.db[2]; c.db[2]=c.db[0]; c.db[0]=q; txr[adr]=c.c32;? In addition, where are your shaders? Thanks.Coprophilous
@Coprophilous #1 To keep this simple and not needing any additional stuff I used only OpenGL 1.0 so no shaders no extensions. The only extension used is GL_CLAMP_TO_EDGE which maps texture coordinates to range <0,1> instead of (0,1). As I did not use any extension loader like GLEW and as This extension is present on all GL capable HW I needed to define the value ... I could also use const GLuint innstead if you got extension loader then you do not need to define it at all. #3 for the same reasons I did not use any shaders instead fixed pipeline is used.Wavy
@Coprophilous #2 the (x,y,z) holds the actual cartesian coordinate computed from actual spherical coordinates (a,b). the x,y,z is normal vector (leftover from chunk of code I did this from) if you multiply it by r you will get the surface coordinate hence r*x,r*y,r*zWavy
@Coprophilous The bmp stuff is simply accessing pixels of bitmap. The pp points to memory where scanline[y] is stored as DWORD[] and color just allows direct access to each pixel as array of 4 bytes (where the r,g,b,a) is stored. What I am doing is reversing RGBA to BGRA because bmp pixelformat has reverse order then my OpenGL texture and copying all scanlines to single 1D array to load it to OpenGL.Wavy
@Coprophilous and lastly the tx,ty stuff just maps from spherical a,b angles to <0,1> texture coordinates range (simple linear interpolation)Wavy
thanks a lot for the detail information! Now i only know how to use swift so i will spend more time in the future to learn how to use openGL :)Archiearchiepiscopacy
@Archiearchiepiscopacy OpenGL is cross platform so the source will be the same. The only thing that will be different is OS/GL interaction so functions responsible for creation of GL context and swap of buffers will be different. I am on windows so functions I am writing about are with prefix wgl you will have to change them with their counterpart for OS you are using.Wavy

© 2022 - 2024 — McMap. All rights reserved.