Tutorials‎ > ‎

[PS Vita] Earth Explorer Program

posted Dec 19, 2012, 2:23 AM by William Surya Permana   [ updated Aug 15, 2016, 11:23 PM by Surya Wang ]


Using PSM Studio, we can create a earth explorer program. This program will load current time from the Internet, showing the Earth model with light intensity according to the time. User can also do rotate and zoom using touch screen. For more detail, look at this screenshots below:

For more detail, you can also download the solution below and try it for yourself.

Touch Input

Nearly similar with how to get gamepad input, to get touch screen input in our application, we can use ‘Touch.GetData(0)’ which will return a list of touch data. Each touch data represent each finger. For each touch data, you can get its ID, touch status, and its position. ID is the finger index, starting from 0.

When you touch the screen, there are three different touch status. The first is Down when you first pressed the screen. The second is Move when you hold or move your finger on the screen. The last is Up when you release your finger on the screen.

The position value of each touch data is not in pixels, but a coordinate where the center screen is (0, 0). The most upper left corner is (-0.5, 0.5) and the lower right corner is (0.5, -0.5). So, we must add 0.5 then multiply it with screen size to get the touch data in screen coordinate.


To create program like that, we can use this code:
using System; using System.Collections.Generic; using Sce.PlayStation.Core; using Sce.PlayStation.Core.Environment; using Sce.PlayStation.Core.Graphics; using Sce.PlayStation.Core.Input; using System.Net; using System.IO; using Sce.PlayStation.HighLevel.Model; using Sce.PlayStation.HighLevel.UI; namespace NewApp3 { public class AppMain { static GraphicsContext graphics; static MemoryStream readStream; static byte[] readBuffer; static string htmlContent; static bool isLoaded; static string timeMarker; static BasicModel model; static BasicProgram program; static Vector2 rotation; static int zoomLevel; static Scene scene; static Label lblCurrentTime; static DateTime currentTime; static Vector2 lastClickPosition; static Vector2 deltaClickPosition; static int timeSinceLastClick; static bool waitingSecondClick; public static void Main (string[] args) { Initialize (); while (true) { SystemEvents.CheckEvents (); Update (); Render (); } } public static void Initialize () { graphics = new GraphicsContext (); timeMarker = "<div id=i_time>"; var webRequest = HttpWebRequest.Create("http://www.timeanddate.com/worldclock/fullscreen.html?n=108"); webRequest.BeginGetResponse(new AsyncCallback(requestCallBack), webRequest); model = new BasicModel("/Application/Earth_3d.mdx", 0); program = new BasicProgram() ; lastClickPosition = new Vector2(); rotation = new Vector2(); zoomLevel = 1; UISystem.Initialize(graphics); scene = new Scene(); lblCurrentTime = new Label(); lblCurrentTime.Width = graphics.Screen.Width; scene.RootWidget.AddChildLast(lblCurrentTime); UISystem.SetScene(scene); } private static void requestCallBack(IAsyncResult ar) { var webRequest = (HttpWebRequest)ar.AsyncState; var webResponse = (HttpWebResponse)webRequest.EndGetResponse(ar); readBuffer = new byte[Int16.MaxValue]; readStream = new MemoryStream(); var stream = webResponse.GetResponseStream(); stream.BeginRead(readBuffer, 0, readBuffer.Length, new AsyncCallback(readCallBack), stream); } private static void readCallBack(IAsyncResult ar) { var stream = (Stream)ar.AsyncState; int readSize = stream.EndRead(ar); if(readSize>0){ readStream.Write(readBuffer, 0, readSize); stream.BeginRead(readBuffer, 0, readBuffer.Length, new AsyncCallback(readCallBack), stream); } else { foreach (var i in readStream.ToArray()) htmlContent += (char)i; isLoaded = true; stream.Close(); } } public static void Update () { var touchData = Touch.GetData(0); if(isLoaded) { var time = htmlContent.Substring(htmlContent.IndexOf(timeMarker)+timeMarker.Length,8); currentTime = DateTime.Parse(time); isLoaded = false; } if(currentTime == new DateTime()) lblCurrentTime.Text = "Retrieving time..."; else { currentTime = currentTime.AddSeconds(1/61f); lblCurrentTime.Text = currentTime.ToLongTimeString(); } Matrix4 proj = Matrix4.Perspective( 1, graphics.Screen.AspectRatio, 1, 100 ) ; Matrix4 view = Matrix4.LookAt( new Vector3( 0.0f, 0.0f, 15.0f ), new Vector3( 0.0f, 0.0f, 0.0f ), new Vector3( 0.0f, 0.1f, 0.0f ) ) ; Vector3 lightDirection = new Vector3( 1, -1, -1 ) ; var light = (float) Math.Sin(FMath.Radians((float)currentTime.TimeOfDay.TotalMinutes*180/1440)); Vector3 lightColor = new Vector3( light, light, light ) ; BasicParameters parameters = program.Parameters ; parameters.SetProjectionMatrix( ref proj ) ; parameters.SetViewMatrix( ref view ) ; parameters.Enable( BasicEnableMode.Lighting, true ) ; parameters.SetLightCount( 1 ) ; parameters.SetLightDiffuse( 0, ref lightColor ) ; parameters.SetLightAmbient( ref lightColor ) ; parameters.SetLightSpecular( 0, ref lightColor ) ; parameters.SetLightDirection( 0, ref lightDirection ) ; graphics.Enable( EnableMode.DepthTest ) ; graphics.SetDepthFunc( DepthFuncMode.LEqual, true ) ; Matrix4 world = Matrix4.RotationY(rotation.X); world *= Matrix4.RotationZ(rotation.Y); timeSinceLastClick++; if(timeSinceLastClick>60) waitingSecondClick=false; if(touchData.Count>0){ var currentclick = new Vector2(touchData[0].X, touchData[0].Y); var deltaclick = currentclick-lastClickPosition; if(deltaclick!=new Vector2()){ if(deltaclick.X < 0 && deltaClickPosition.X < 0) rotation.X-=10*Math.Abs(deltaclick.X); else if(deltaclick.X > 0 && deltaClickPosition.X > 0) rotation.X+=10*Math.Abs(deltaclick.X); if(deltaclick.Y < 0 && deltaClickPosition.Y < 0) rotation.Y-=10*Math.Abs(deltaclick.Y); else if(deltaclick.Y > 0 && deltaClickPosition.Y > 0) rotation.Y+=10*Math.Abs(deltaclick.Y); } lastClickPosition = currentclick; deltaClickPosition = deltaclick; if(touchData[0].Status == TouchStatus.Down){ if(timeSinceLastClick < 60 && waitingSecondClick == true){ if(zoomLevel<20) zoomLevel++; waitingSecondClick = false; } timeSinceLastClick = 0; waitingSecondClick = true; } } float scale = zoomLevel/model.BoundingSphere.W ; world *= Matrix4.Scale( scale, scale, scale ) ; model.SetWorldMatrix( ref world ) ; model.Update() ; model.Draw( graphics, program ) ; } public static void Render () { UISystem.Render(); graphics.SwapBuffers (); graphics.SetViewport( 0, 0, graphics.Screen.Width, graphics.Screen.Height ) ; graphics.Clear(); } } }


In line 14-33, we create the object and variable that we will use later, like:
  • GraphicsContext graphics as the main graphics
  • MemoryStream readStream to read the stream from the Internet
  • byte[] readBuffer to hold a fragment of the stream
  • string htmlContent to hold the whole response from the Internet
  • bool isLoaded as flag if the program already receive all of the web response
  • string timeMarker as mark where to find the time from the web response

  • BasicModel model and BasicProgram program for showing our Earth
  • Vector2 rotation for storing current Earth rotation
  • int zoomLevel for storing current Earth scaling

  • Scene scene as the main scene
  • Label lblCurrentTime for showing the time in the upper left corner
  • DateTime currentTime for storing current time
  • Vector2 lastClickPosition for storing the position of last touch
  • Vector2 deltaClickPosition for storing the difference between the position of current touch and the last touch
  • int timeSinceLastClick for storing how much frames already passed since the last touch
  • bool waitingSecondClick as flag if the program still wait the user to do the second touch
In Initialize function, we create new object, set the value and properties of the following object:
  • [Line 46] Intialize the graphics
  • [Line 47] Set timeMarker value to ‘<div id=i_time>’
  • [Line 48-50] Creating web request to and waiting web response from http://www.timeanddate.com/worldclock/fullscreen.html?n=108
  • [Line 52] Load the model from file ‘Earth_3d.mdx’
  • [Line 53] Create the object of BasicProgram
  • [Line 54-55] Create the object of lastClickPosition and rotation
  • [Line 56] Set zoomLevel value to 1
  • [Line 59] Create the object of scene
  • [Line 60-61] Create the object of lblCurrentTime, and set its width to screen width
  • [Line 62] Add lblCurrentTime in the most front layer of the scene.
  • [Line 63] Set scene as the scene of UISystem.
In Update function, we define all of the logic:
  • [Line 93] We get the touch data
  • [Line 95-99] If the program already receive the web response, we find the time in the web response, parse it, and store it in variable currentTime
  • [Line 101-106] If currentTime is still having the same value as when we initialize it, set the text of lblCurrentTime to ‘Retrieving time...’. Else, add 1/60 seconds each frame and show the currentTime value in lblCurrentTime
  • [Line 108-132] We set all parameter for showing the model
  • [Line 134-159] We check the touch data:
    • Always add the value of timeSinceLastClick
    • If timeSinceLastClick is greater than 60 the program will not waiting for second touch again
    • If user touch the screen
      • If user drag the screen to the left (deltaclick.X < 0 and deltaClickPosition.X < 0), rotate the world to the left according to user dragging speed (Math.Abs(deltaclick.X))
      • Else, if user drag the screen to the right (deltaclick.X > 0 and deltaClickPosition.X > 0), rotate the world to the right according to user dragging speed (Math.Abs(deltaclick.X))
      • If user drag the screen to the up (deltaclick.Y < 0 and deltaClickPosition.Y < 0), rotate the world to the up according to user dragging speed (Math.Abs(deltaclick.Y))
      • Else, if user drag the screen to the down (deltaclick.Y > 0 and deltaClickPosition.Y > 0), rotate the world to the down according to user dragging speed (Math.Abs(deltaclick.Y))
      • If user double touch the screen within 60 frames, add the zoomLevel except the zoomLevel already reach 20.
      • The program will wait user for the second touch and reset the timeSinceLastClick
    • [Line 161-162] We scale the world according the zoomLevel
    • [Line 163-165] We set the world matrix, and show the model
In Render function, we render all UI and set the whole screen as the viewport.

Getting data from the Internet

In line 66-89, we create function requestCallBack and ReadCallBack. These used when we want retrieve data from the Internet. In short, we create a web request, get the response, and store the response Header or Stream to an array of byte.

In Initialize function, we begin to get response from http://www.timeanddate.com/worldclock/fullscreen.html?n=108 and run function requestCallBack after it.

In requestCallBack, we define, we will get the web request response, both its header and content. In this case, we only check the content. Then, it we begin to read the response stream and run function readCallBack after it.

In readCallBack, we check the stream read size. If the read size is more than 0, then we write the content from the readBuffer to the readStream, then get the next readBuffer. If the read size is already 0, move all the readStream to variable HtmlContent, close the stream, and save it in variable htmlContent.

Showing 3D model

To show a 3D model in PSM application, we can use BasicModel and BasicProgram class. Basic model only support model with MDX format. So, first of all we must convert our model to MDX format using Model Converter application provided by the SDK in folder /tools/ModelConverter of the installation folder. Just open any FBX, XSI, X, COLLADA, or MDS format model using Model Converter, and it will create the MDX file in the same folder as the model.

After the model was prepared, add it as content resources in PSM Studio, then add ‘Sce.PlayStation.HighLevel.Model’ reference package, and add ‘using PlayStation.HighLevel.Model;’ at the beginning of the code.

In line 108-132, we do write all codes to show the model. Matrix4 proj is the matrix represent the perspective, including its scale, aspect ratio, and vanishing point (before and after). Matrix4 view is the matrix represent the position of our eye, and its viewing direction. We set the both matrixes as program parameter for its projection and viewing matrix.

In the program parameter, we also add some lights: diffuse and ambient light which come from any direction, and specular light which come from the direction as stated in Vector3 lightDirection. The light color is retrieved using sin function where 12.00PM is the brightest and 12.00AM is the darkest. Next, we enable the depth test, so not both rear and front model drawn at the same time.

Next, we rotate the world according to the value from Vector2 rotation which will change if user do dragging. We also scale the world according to the value from zoomLevel which will change everytime user double touch the screen. Lastly, we draw the model and in render function we set the whole screen as the viewport.

This is the end of the tutorial. Hope you can understand how to create a earth explorer program in PS Vita using PSM Studio.
[PS Vita] Earth Explorer.zip
William Surya Permana,
Dec 19, 2012, 2:23 AM