Thread: Update screenie with billboards

  1. #31
    Crazy Fool Perspective's Avatar
    Join Date
    Jan 2003
    Location
    Canada
    Posts
    2,640
    it looks a little odd having stars between you (the camera) and the planet. Stars are huge and planets are small.... the planet looks close yet the stars in front of it look far away.

    I think you should use the distance from the camera to the farthest visible planet as an inner limit in your star field generation.

  2. #32
    S Sang-drax's Avatar
    Join Date
    May 2002
    Location
    Göteborg, Sweden
    Posts
    2,072
    Quote Originally Posted by Perspective
    it looks a little odd having stars between you (the camera) and the planet. Stars are huge and planets are small.... .
    Wow... didn't even notice that. It looks... odd.
    I like the planet though.
    Last edited by Sang-drax : Tomorrow at 02:21 AM. Reason: Time travelling

  3. #33
    Registered User
    Join Date
    Mar 2003
    Posts
    580
    When rendering the stars, I would suggest clamping the depth values between 1 and 1, such that nothing gets drawn on top of them. It doesn't make sense for stars to be between you and the planet, because a star would typically be many times more massive than any planet.

    At least that is how I did things when rendering my own starfield type stuff.
    See you in 13

  4. #34
    Registered User VirtualAce's Avatar
    Join Date
    Aug 2001
    Posts
    9,607
    The stars have to be in front of the planet. These stars are not the backdrop for space. They are just the crap that flies around you when you accelerate. So in reality they wouldn't be there because we would hope there wasn't that much stuff in space to fly around ya, but it gives the illusion of motion.

    Many space genre games use this technique. Star Wars Tie Fighter used 2D bitmaps of debris that flew by you extremely fast. Independence War used actual pixels like I am, and a host of other games use similar methods.

    When you are not moving the stars are not even rendered. They are only there to give the illusion of motion through space. Without them you really don't feel like you are moving at all while flying towards planets, starbases, etc.
    I purposely turned Z buffering off for the stars.

    Also, would it be difficult to decrease the size of the atmosphere. I would like to see what it looks like with the atmosphere slightly smaller.
    Yes there is a huge problem with the atmosphere's. Those of you who know about Z buffering and how it works will understand this. The problem that occurs when I make the atmosphere smaller is that at great distances from the planet, D3D has a hard time rendering the alpha blended sphere over the textured sphere correctly. Since the z values are so small what happens is that at some point the Z distance is so small between atmosphere and planet that they Z fight each other. This causes flickering triangles of non-alpha blended planet texture to appear. This is because at that point the atmosphere and the planet model are Z fighting each other. There are several workarounds but none that I like thus far. Here are some ideas:

    1. Render the atmosphere only at very close range. Problem: Would cause a very noticeable 'popping' effect as the atmosphere would suddenly be rendered as you approached the planet.

    2. Make the atmosphere about 5 units larger than the planet - that way the Z's never ever fight each other. Problem: Atmosphere is too big in radius compared to the planet at close range (what you see in the screenshots).

    3. Program a pixel shader that will blend per pixel instead of interpolated vertex to vertex. This would minimize the artifacts a lot.

    4. Create a dynamic sized atmosphere that would adjust as the camera/player approached the planet. Problem: Dynamic buffers are extremely expensive and re-computing the sphere geometry for a smaller radius per frame would be a major bottleneck.


    I'm open to any ideas that you may have on how to overcome this problem.
    Last edited by VirtualAce; 10-13-2004 at 03:48 PM.

  5. #35
    Registered User VirtualAce's Avatar
    Join Date
    Aug 2001
    Posts
    9,607
    Here is the starfield class. I'm having major problems with certain things. Perhaps some of you could assist me.

    CStarField.h
    Code:
    #ifndef CSTARFIELD
    #define CSTARFIELD
     
    #include "d3dx9.h"
    #include "CSphere.h"
    #include "CFileLog.h"
    #include "d:\vc6gamelibs\3dlibs\engine\d3dUtility.h"
     
    struct StarVertex
    {
      D3DXVECTOR3 Pos;
      D3DCOLOR Alpha;
      D3DCOLOR Color;
     
      StarVertex(D3DXVECTOR3 _pos,D3DCOLOR _color):Pos(_pos),Color(_color) {}
      StarVertex(void) {Pos=D3DXVECTOR3(0.0f,0.0f,0.0f);Color=0;};
     
      static const DWORD FVF;
    };
     
    class CStarField
    {
      CFileLog StarLog;
     
      IDirect3DDevice9 *Device;
      ID3DXLine *LineInterface;
      
      //Vertices - six per line - 3 for startpoint, 3 for endpoint - later for lines
      StarVertex *Vertices;
     
      //Positions for all stars
      //Each 6 vertices will be transformed by this
      D3DXVECTOR3 Pos;
      D3DMATERIAL9 Material;
     
      //For transformation - not needed at this point
      D3DXMATRIX LRotation;
      D3DXMATRIX WRotation;
      D3DXMATRIX World;
      unsigned int NumStars;
      float Radius;
     
      public:
      CStarField(void) 
      {
    	LineInterface=NULL;
    	Vertices=NULL;
    	D3DXMatrixIdentity(&LRotation);
    	D3DXMatrixIdentity(&WRotation);
    	D3DXMatrixIdentity(&World);
    	NumStars=0;
      }
      ~CStarField(void)
      {
    	if (LineInterface) LineInterface->Release();
    	if (Vertices) delete [] Vertices;
      }
      void Create(IDirect3DDevice9 *_device,unsigned int _numstars,int _radius);
      void Render(D3DXMATRIX view,D3DXVECTOR3 cameraPos,float timeDelta);
      void Update(D3DXVECTOR3 _lookVector,float _speed)
      {
    	for (int i=0;i<NumStars;i++)
    	{
    	  Vertices[i].Pos-=_lookVector*_speed;
     
    	  float x=Vertices[i].Pos.x;
    	  float y=Vertices[i].Pos.y;
    	  float z=Vertices[i].Pos.z;
    	  if (x<=-Radius || y<=-Radius || z<-Radius ||
    		  x>Radius || y>Radius || z>Radius)
    	  {
    		 int r=(int)Radius;
    		 x=(float)(-r + rand() % (r*2));
    		 y=(float)(-r + rand() % (r*2));
    		 z=(float)(-r + rand() % (r*2));
    		 Vertices[i].Pos=D3DXVECTOR3(x,y,z);
    	  }
    	}
      }
    };
    #endif
    CStarField.cpp
    Code:
    #include "CStarField.h"
     
    const DWORD StarVertex::FVF=D3DFVF_XYZ | D3DFVF_SPECULAR;
     
    void CStarField::Create(IDirect3DDevice9 *_device,unsigned int _numstars,int _radius)
    {
      Device=_device;
      Radius=(float)_radius;
     
      float newx=0,newy=0,newz=0;
      float alpha=0.0f,beta=0.0f;
     
      Vertices=new StarVertex[_numstars];
      NumStars=(_numstars);
      for (unsigned int i=0;i<NumStars;i++)
      {
    	int radius2=Radius*2;
    	newx=-Radius+rand()%radius2;
    	newy=-Radius+rand()%radius2;
    	newz=-Radius+rand()%radius2;
     
    	Vertices[i]=StarVertex(D3DXVECTOR3((float)newx,(float)newy,(float)newz),
    						 D3DCOLOR_ARGB(55,255,255,255));
     
     
    	//StarVertex[i+1].Pos=D3DXVECTOR3(newx*4.0f,newy*4.0f,newz*4.0f);
      }
    }
     
    void CStarField::Render(D3DXMATRIX view,D3DXVECTOR3 cameraPos,float timeDelta)
    {
      D3DXMATRIX trans;
      D3DXMatrixTranslation(&trans,cameraPos.x,cameraPos.y,cameraPos.z);
      Device->SetTransform(D3DTS_WORLD,&trans);
     
      Device->SetRenderState(D3DRS_ALPHABLENDENABLE,true);
      Device->SetRenderState(D3DRS_SRCBLEND,D3DBLEND_SRCALPHA);
      Device->SetRenderState(D3DRS_DESTBLEND,D3DBLEND_INVSRCALPHA);
     
     
      Device->SetRenderState(D3DRS_DIFFUSEMATERIALSOURCE,D3DMCS_COLOR1);
     
      Device->SetRenderState(D3DRS_LIGHTING,false);
      Device->SetRenderState(D3DRS_CULLMODE,D3DCULL_NONE);
      //Device->SetRenderState(D3DRS_FILLMODE,D3DFILL_POINT);
     
      Device->SetFVF(StarVertex::FVF);
     
     
      Device->DrawPrimitiveUP(D3DPT_POINTLIST,
    									NumStars,
    								   (StarVertex *)Vertices,
    								   sizeof(StarVertex));
      Device->SetRenderState(D3DRS_LIGHTING,true);
      Device->SetRenderState(D3DRS_ALPHABLENDENABLE,true);
      //Device->SetRenderState(D3DRS_FILLMODE,D3DFILL_SOLID);
      Device->SetRenderState(D3DRS_CULLMODE,D3DCULL_CCW);
    }
    This is the problem. For some reason in my FVF (flexible vertex format) when I use D3DFVF_DIFFUSE the stars are pure black. It does not matter what color I make them, they still show up as black...even with lighting off and alphablending off. I'm not sure why this is. The only way it works is to use D3DFVF_SPECULAR. It doesn't make sense. There does not need to be a material here because I'm not lighting the stars. It would not look right to have the surrounding stars lit by the sun - I don't care if you are a billion miles from the sun, I still want you to see the surrounding starfield.

    So I'm using un-transformed, pre-lit vertices and yet it does not work as expected. Later I want to specify a vertex alpha using:

    Device->SetRenderState(D3DRS_SPECULARMATERIALSOURCE,D3DMC S_COLOR2);

    This will allow me to specify an alpha value in the specular component of my vertex thus allowing me to fade stars out that are far away and fade stars in that are close. I could fake it with color - but I want the stars to disappear. If you are in another system making the stars black does not make them disappear...it makes them black.

    Anyone have any ideas as to why my diffuse color is not working in my call to

    Device->SetFVF(StarVertex::FVF);

    This should set the correct FVF for the DrawPrimitiveUP() call. Sorry for the mess of code, but I'm still experimenting with this class so it's in a very early state here.
    Last edited by VirtualAce; 10-13-2004 at 04:07 PM.

  6. #36
    Registered User VirtualAce's Avatar
    Join Date
    Aug 2001
    Posts
    9,607
    I will also post my planet code to see if you guys can help me fix this Z fighting problem.

    CSphere.h
    Code:
    #ifndef CSPHERE
    #define CSPHERE
     
    #include "d3dx9.h"
    #include "CQuickCom.h"
    #include "CFileLog.h"
    #include "d3dx9mesh.h"
     
     
     
    #define DEGTORAD(x) (x*D3DX_PI/180.0f)
     
    struct CSphereVertex
    {
    D3DXVECTOR3 pos;
    D3DXVECTOR3 normal;
    float u,v;
    static const DWORD FVF; 
     
    CSphereVertex(D3DXVECTOR3 tpos,D3DXVECTOR3 tnormal,float tu,float tv):
    pos(tpos),normal(tnormal),u(tu),v(tv){}
     
    };
     
    class CSphere
    {
     
    protected:
    	CFileLog SphereLog;
     
     
    	IDirect3DDevice9 *Device;
    	IDirect3DVertexBuffer9 *VB;
    	IDirect3DIndexBuffer9 *IB;
     
    	ID3DXMesh *SphereMesh;
     
     
    	unsigned int NumTris;
    	unsigned int NumFaces;
    	unsigned int NumVertices;
    	unsigned int NumVertsPerRow;
    	unsigned int NumVertsPerCol;
     
    float Radius;
     
    public:
    	CSphere(void) {Device=NULL,VB=NULL,IB=NULL,NumTris=0,NumFaces=0,
    	NumVertices=0,NumVertsPerRow=0,NumVertsPerCol=0,Radius=0.0f;};
     
    	~CSphere(void)
    	{
    	 SAFE_RELEASE(VB);
    	 SAFE_RELEASE(IB);
    	 SAFE_RELEASE(SphereMesh);
    }
     
    //Old - not used
    //void CreateSphere(IDirect3DDevice9 *_device,
    // float _radius,
    // unsigned int _faces,
    // float _texmag,
    // D3DXVECTOR3 &pos);
     
    void CreateMeshSphere(IDirect3DDevice9 *_device,
    float _radius,
    unsigned int _faces);
     
    //Old - not used 
    //IDirect3DVertexBuffer9 *GetVB(void) {return VB;};
    //IDirect3DIndexBuffer9 *GetIB(void) {return IB;};
     
    ID3DXMesh *GetMesh(void) {return SphereMesh;};
     
    unsigned int GetNumTris(void) {return NumTris;};
    unsigned int GetNumVertices(void) {return NumVertices;};
    };
     
    #endif
    CSphere.cpp
    Code:
    #include "CSphere.h"
     
     
     
    const DWORD CSphereVertex::FVF=D3DFVF_XYZ | D3DFVF_NORMAL | D3DFVF_TEX1;
     
     
    void CSphere::CreateMeshSphere(IDirect3DDevice9 *_device,float _radius,unsigned int _faces)
    {
    Device=_device;
    Radius=_radius;
    NumFaces=_faces;
     
    LPD3DXMESH temp;
    if (FAILED(D3DXCreateSphere(Device,Radius,NumFaces,NumFaces,&temp,NULL)))
    {
    ::MessageBox(0,"Failed to create sphere",0,0);
    }
     
    if (FAILED(temp->CloneMeshFVF(D3DXMESH_SYSTEMMEM,CSphereVertex::FVF,Device,&SphereMesh)))
    {
    ::MessageBox(0,"Failed to clone mesh",0,0);
    }
     
    CSphereVertex *Vertices;
     
    SphereMesh->LockVertexBuffer(0,(void **)&Vertices);
     
    float angleinc_alpha=360.0f/_faces;
    float angleinc_beta=180.0f/_faces;
     
     
    float alpha=0.0f;
    float beta=-180.0f;
    float u=0.0f,v=0.0f;
     
    unsigned int vertex_count;
    float PI2=D3DX_PI*2.0f;
     
    for (unsigned int i=0;i<SphereMesh->GetNumVertices();i++)
    {
    u=(DEGTORAD(alpha)+D3DX_PI)/(PI2);
    v=DEGTORAD(beta)/D3DX_PI;
     
    Vertices[i].u=u;
    Vertices[i].v=v;
     
    alpha+=angleinc_alpha;
    vertex_count++;
    if (vertex_count>=(_faces))
    {
    alpha=0.0f;
    beta+=angleinc_beta;
    //Vertices[i+1].v=0.0f;
    //Vertices[i+1].u=0.0f;
    vertex_count=0;
    }
    }
    SphereMesh->UnlockVertexBuffer();
     
     
    temp->Release();
     
    }
    CPlanet.h
    Code:
    #ifndef CPLANET
    #define CPLANET
     
    #include "CPrimitives.h"
    #include "d3dx9.h"
    #include <String>
    #include "CQuickCom.h"
    #include "CSphere.h"
     
     
     
    class CPlanet
    {
    IDirect3DDevice9 *Device;
     
    CSphere Planet;
    CSphere Atmosphere;
     
    D3DXVECTOR3 Pos;
    D3DXVECTOR3 Rotation;
    D3DXVECTOR3 RotationVelocity;
     
     
    IDirect3DTexture9 *Texture;
     
     
    D3DMATERIAL9 Material;
    D3DMATERIAL9 AMaterial;
     
     
    float Radius;
    float ARadius;
    int Faces;
    bool AtmosphereFlag;
     
    public:
    CPlanet(void);
    ~CPlanet(void);
     
    void SetTexture(std::string PlanetFile);
     
    void SetPosition(D3DXVECTOR3 *_newpos) {Pos=*_newpos;};
    void GetPosition(D3DXVECTOR3 *_curpos) {*_curpos=Pos;};
     
    void SetRotationParams(D3DXVECTOR3 *_rotation,D3DXVECTOR3 *_rotation_velocity)
    {
    Rotation=*_rotation;
    RotationVelocity=*_rotation_velocity;
    }
     
    void GetRotationParams(D3DXVECTOR3 *_rotation,D3DXVECTOR3 *_rotation_velocity)
    {
    *_rotation=Rotation;
    *_rotation_velocity=RotationVelocity;
    }
     
    void CreateModel(IDirect3DDevice9 *_Device,float _Radius,int _Faces,D3DXVECTOR3 &pos);
     
    void TestCreateModel(IDirect3DDevice9 *_device,float _radius,unsigned int _faces)
    {
    Device=_device;
    Radius=_radius;
    Faces=_faces;
     
     
    Planet.CreateMeshSphere(_device,_radius,_faces);
    D3DXComputeNormals(Planet.GetMesh(),NULL);
     
    }
     
     
     
    void SetMaterial(D3DMATERIAL9 *_material) {Material=*_material;};
    void GetMaterial(D3DMATERIAL9 *_material) {*_material=Material;};
     
     
     
    float GetRadius(void) {return Radius;};
     
    float GetAtmosphereRadius(void) {return ARadius;};
     
    void SetAtmosphere(D3DMATERIAL9 *_material,float _radius);
     
    void Render(float timeDelta);
    void TestRender(float timeDelta);
     
     
    };
     
    #endif
    CPlanet.cpp
    Code:
    #include "CPlanet.h"
     
     
    CPlanet::CPlanet(void)
    {
    Device=NULL;
    Pos=D3DXVECTOR3(0.0f,0.0f,0.0f);
    Rotation=D3DXVECTOR3(0.0f,0.0f,0.0f);
    RotationVelocity=D3DXVECTOR3(0.0f,0.0f,0.0f);
    Texture=NULL;
     
    Radius=0.0f;
    }
     
    CPlanet::~CPlanet(void)
    {
    if (Texture) Texture->Release();
     
    }
     
    void CPlanet::SetTexture(std::string PlanetFile)
    {
    if (FAILED(D3DXCreateTextureFromFile(Device,PlanetFile.c_str(),&Texture)))
    {
    ::MessageBox(0,"Failed to load planet texture",0,0);
    return;
    }
    }
     
    void CPlanet::CreateModel(IDirect3DDevice9 *_Device,float _Radius,int _Faces,D3DXVECTOR3 &pos)
    {
    Device=_Device;
    Radius=_Radius;
    Faces=_Faces;
    SetPosition(&pos);
     
     
    Planet.CreateMeshSphere(Device,Radius,_Faces);
    }
     
    void CPlanet::SetAtmosphere(D3DMATERIAL9 *_material,float _radius)
    {
    AMaterial=*_material;
    ARadius=_radius;
     
    AtmosphereFlag=true;
     
    Atmosphere.CreateMeshSphere(Device,ARadius,Faces);
    }
     
    void CPlanet::Render(float timeDelta)
    {
     
    Device->SetRenderState(D3DRS_WRAP0,true);
    Device->SetRenderState(D3DRS_WRAP1,true);
    Device->SetRenderState(D3DRS_ALPHABLENDENABLE,true);
     
    Rotation.x+=(RotationVelocity.x*timeDelta);
    Rotation.y+=(RotationVelocity.y*timeDelta);
    Rotation.z+=(RotationVelocity.z*timeDelta);
     
    D3DXMATRIX rot;
    D3DXMatrixRotationYawPitchRoll(&rot,Rotation.x,Rotation.y,Rotation.z);
     
     
    rot._41=Pos.x;
    rot._42=Pos.y;
    rot._43=Pos.z;
     
    Device->SetTransform(D3DTS_WORLD,&rot);
    Device->SetMaterial(&Material);
    Device->SetTexture(0,Texture);
     
    Planet.GetMesh()->DrawSubset(0);
     
    if (AtmosphereFlag) 
    {
    Device->SetRenderState(D3DRS_ALPHABLENDENABLE,true);
    Device->SetRenderState(D3DRS_SRCBLEND,D3DBLEND_SRCALPHA);
    Device->SetRenderState(D3DRS_DESTBLEND,D3DBLEND_INVSRCALPHA);
    Device->SetTexture(0,NULL);
    Device->SetTextureStageState(0,D3DTSS_ALPHAOP,D3DTA_DIFFUSE);
    Device->SetTextureStageState(0,D3DTSS_ALPHAARG1,D3DTOP_SELECTARG1);
     
    Device->SetMaterial(&AMaterial);
    Atmosphere.GetMesh()->DrawSubset(0);
     
    Device->SetRenderState(D3DRS_ALPHABLENDENABLE,false);
    }
     
     
    Device->SetRenderState(D3DRS_WRAP0,false);
    Device->SetRenderState(D3DRS_WRAP1,false);
     
     
    }
    Sorry, the board screws up my formatting all the time. All my indents were removed when I edited the post. I'm not going to go and manually re-indent everything. Could we please, please, PLEASE, get this bug fixed!!!!!!!!!!!!!

    There are a lot of old functions and variables in the code right now and it needs cleaned up, but during dev I didn't want to erase any functions or variables until I made sure I didn't want to use that method or algo. I used to create the sphere's mathematically but now I just clone one from D3D.

    As you can see this project is quickly becoming very large. I'm going to need help from some of you later on down the road. It's impossible to do all of it myself.
    Last edited by VirtualAce; 10-13-2004 at 04:18 PM.

  7. #37
    Registered User VirtualAce's Avatar
    Join Date
    Aug 2001
    Posts
    9,607
    I enhanced the starfield code so that the stars are optimally arranged around the player at all times.

    Here is the new update function.
    Code:
    void Update(D3DXVECTOR3 _lookVector,float _speed)
    {
      for (int i=0;i<NumStars;i++)
      {
    	Vertices[i].Pos-=_lookVector*_speed;
     
    	float d=D3DXVec3Dot(&Vertices[i].Pos,&_lookVector);
    	
    	if (d<=0 )
    	{
    	  int r=(int)Radius;
    	  float x=(float)(-r+ rand() % (r<<1));
    	  float y=(float)(-r+ rand() % (r<<1));
    	  float z=(float)(-r+ rand() % (r<<1));
    	  Vertices[i].Pos=D3DXVECTOR3(x,y,z);
    	}
      }
    }
    The dot product seems to work much better. At a later date I will also use the dot product to determine which stars even need to be rendered. If the dot product of the lookVector and the star vertex is <0 then the star should not be rendered.

    I still cannot just regen stars in front of the player however because it does not look right. I tried regen'ing stars only along the lookVector, but the problem is when you turn the stars seem to disappear. It would take more time to test whether or not a star was in the frustrum than to just regen them at a random distance (up to a max dist specified by the programmer).
    Last edited by VirtualAce; 10-13-2004 at 04:58 PM.

  8. #38
    i dont know Vicious's Avatar
    Join Date
    May 2002
    Posts
    1,200
    Perhaps leaving the atmosphere the same size at increasing its alpha value would look better.

    Im thinking make the atmosphere a billboard..

    And simply have it the same size as it is now and increase the alpha value at the edges so it "fades" at the edges.
    What is C++?

  9. #39
    Registered User VirtualAce's Avatar
    Join Date
    Aug 2001
    Posts
    9,607
    The billboard wont work because you will clearly see in the sun pictures that the corona does not look like an atmosphere - the corona does look 2D because it is.

    Atmosphere billboards won't work - as soon as you rotate the camera to certain angles you will notice the billboarding effect.

  10. #40
    Registered User VirtualAce's Avatar
    Join Date
    Aug 2001
    Posts
    9,607
    Ok the skybox code is done. Now all I need are 6 seamless textures that approximate the universe or current system the player is in.

    Here is a very early screenshot. Atmosphere's have not been altered yet. Still working on vertex and pixel shader code for this.

  11. #41
    Registered User VirtualAce's Avatar
    Join Date
    Aug 2001
    Posts
    9,607
    Updated the stars.

    The second point for the stars is this:

    Vertices[i+1].Pos=Vertices[i].Pos+(_lookVector*_speed*2.0f);
    Vertices[i].Pos-=_lookVector*_speed;

  12. #42
    Registered User VirtualAce's Avatar
    Join Date
    Aug 2001
    Posts
    9,607
    Here is another screenie at full speed.

Popular pages Recent additions subscribe to a feed

Similar Threads

  1. Polymorphism; Update functions and accessibility
    By CaptainMaxxPow in forum C# Programming
    Replies: 2
    Last Post: 04-23-2009, 08:48 AM
  2. SQLite not performing update
    By OnionKnight in forum C Programming
    Replies: 0
    Last Post: 01-21-2009, 04:21 PM
  3. July 9 2008 MS XP Update
    By VirtualAce in forum A Brief History of Cprogramming.com
    Replies: 12
    Last Post: 07-18-2008, 05:14 AM
  4. ListView Refresh, Update
    By de4th in forum C++ Programming
    Replies: 1
    Last Post: 12-23-2006, 09:13 AM
  5. file index update error
    By daluu in forum C Programming
    Replies: 1
    Last Post: 04-28-2003, 02:47 AM