Hello Earth - Building a CellSpeak Application

The module section

The application for this tutorial is a 3D application, showing a model of the solar system where the planets orbit around the sun.

A CellSpeak project starts with a module section. The module section contains information for the compiler where to find the components of the project: the packages and the other source files that are part of the project. In a module section you can also set the version for the compiled code. The module for this project is shown to the right. There is only one source file, so there are no other source files listed here. There are three packages that are used by this application. First there is the platform package that contains the basic functionality of the system. It contains the math lib, string functions etc. but also the system related services. It is the sort of equivalent of the standard lib.

The second package contains the functionality to use DirectX 11 in CellSpeak. It contains designs for cells like Camera, Light etc, that are built around the DirectX libraries.

For both these packages, we also need the symbol definitions so they are imported as 'symbols'.

The third package is a package that allows to create an editor window. The window can be used to send strings to, but it also offers standard editing capabilities. It is a small CellSpeak wrapper around the Scintilla library. Because we only need to instantate the designs in this package, and send messages, we do not have include the symbols, so we import only the cell-designs in that packages.

Module section
-- Module section for the Solar System example
module 
	
	-- The package files for this project - also load the symbols
	const symbols = [
		"..\\Packages\\Platform\\Platform.cellbyc",
		"..\\Packages\\D3D11Graphics\\D3D11Graphics.cellbyc"
	]
	
	-- Also needed - but only the cell-designs in the package are needed
	const cells = [
		"..\\Packages\\Editor\\Editor.cellbyc"
	]
	
	-- if we set a version it will be included in the resulting package
	version = "1.00"

end
			

Groups

The packages and source files contain designs, data and functions, that are normally organised in groups. Groups are just a collection of definitions and code that belong logically together.

At the beginning of a source file, you list the groups that are used in that file.

When a group is in the use list, you can refer to an item of the group just by its name, e.g. if you include use Light you can just use SpotLight instead of Light.SpotLight. Only when there is ambiguity, the group name needs to be used to make the distinction.

In this example we have split the groups that are used over three lines for clarity - we could have put them on one line.

The first line contains standard CellSpeak groups that contain code for math and string operations.

The second line contains a group that is an interface to the Windows OS and a group that allows to create a window with editor functionality - based on the Scintilla library. Both packages import C++ libraries to implement their functionality.

The third line contains the groups that are used to interface with DirectX. The DirectX CellSpeak package is a thin layer around the standard DirectX libraries. The most important cell design in the package is the Camera that does all the initialisation of the framework, but the package also contains designs that implement shapes, designs for lights etc.

Groups
-- External standard CellSpeak groups
use Math, Strings, Editor, Windows

-- External groups specific to DirectX
use D3D11, Camera, Light, Color, Material, Shape
			

Application Skeleton

Like any CellSpeak application, the Solar System application consists of a number of designs that will be instantiated at run time.

The first design, Sphere, is used for each of the planets and for the sun. Then there is a design for a cell for the entire solar system, called SolarSystemDesign, of which the planets and the sun are child-cells. We will discuss the details of both later.

Finally there is a design for the entire application, called HelloEarth. This is the design that will be instantiated to start the application.

In the HelloEarth design we create a cell for a text window - create MenuWindow -, a cell for the camera and a cell for the light we want to use in the scene. The camera is an important subsystem and we have to tell the camera which window it has to draw into and the light(s) that we are using.

Note that the window that we use for the camera is a C++ class (declared in the D3D11 package). We have not created a separate cell for this window, because we do not send messages directly to it, but rather to the camera, who 'owns' the window.

Application Skeleton
-- External standard CellSpeak groups
use Math, Strings

-- Groups linked to the OS
use Editor, Windows

-- External groups specific to DirectX
use D3D11, Camera, Light, Color, Material, Shape

-- A sphere, derived from the shared mesh design
design Sphere(float Radius,MeshClass cppMesh) like SharedMesh(cppMesh) is
	...
end

-- The design of the solar system
design SolarSystemDesign is
	...
end

-- This is the design that we will instatiate to get the program going
design HelloEarth is
	cell Camera						-- We need a camera
	cell Light						-- We need a light
	cell Window						-- A text output window
	cell SolarSystem				-- Our scene
	CanvasWindowClass CameraWindow	-- The camera needs a window
	Hwnd CameraWindowHandle			-- The handle of that window
	RectangleType Rectangle			-- The rectangle for that window
	...
end
			

Preparing the Stage

In the HelloEarth design we create a cell for a text window, a cell for the camera and a cell for the light we want to use in the scene. The camera is an important subsystem - it stands for the DirectX functionality - and we have to tell the camera which window it has to draw into and the light(s) that we are using.

Note that the window that we create for the camera is a C++ class (declared in the D3D11 package). We have not created a separate cell for this window, because we do not send messages directly to it, but rather to the camera, who 'owns' the window. We pass the cell-id of the camera also to the window, to allow the CameraWindow itself to send mouse and keyboard events generated by the user, directly to the camera. In this way the camera can rotate, pan and zoom under user control. This is an interface between the CameraWindow and the Camera, to which we do not have to pay any further attention in our application.

When all is set up - the window, the camera and the light - we create the scene that we want to render, in our case the solar system. Then we tell the camera what our scene is. We have to do this because the camera has to send messages to the scene at the start of each new frame.

Finally we are ready to start the camera, but first we have a look at how the solar system is built.

Application Skeleton
-- External standard CellSpeak groups
use Math, Strings, Editor, Window

-- External groups specific to DirectX
use D3D11, Camera, Light, Color, Material, Shape

-- A sphere, derived from the shared mesh design
design Sphere(float Radius,MeshClass cppMesh) like SharedMesh(cppMesh) is
	...
end

-- The design of the solar system
design SolarSystemDesign is
	...
end

-- This is the design that we will instatiate to get the program going
design HelloEarth is
	cell Camera						-- We need a camera
	cell Light						-- We need a light
	cell Window						-- A text output window
	CanvasWindowClass CameraWindow	-- The camera needs a window
	Hwnd CameraWindowHandle			-- The window handle
	RectangleType Rectangle			-- The window rectangle
	cell SolarSystem				-- Our scene
	constructor is
		
		-- The 'from' and 'to' vectors for the camera
		xyz From = [0,0,10*AU], To = [0,0,0]
		
		-- Create the output window
		Window = create MenuWindow("The Solar System")
		
		-- Create a camera
		Camera 	= create FromTo(From,To, [0,1,0], 100*AU, 0.1, pi_f/4)
		
		-- The camera needs a window to display in - define the size
		Rectangle = [500,0,2000,1000]
		
		-- Create the camera window class (calls a C++ method)
		CameraWindow = CameraWindow.Create("Camera",&Rectangle, 0, 0,null )
		
		-- Tell the camera which window it has to use
		Camera <- SetWindow(CameraWindow)
		
		-- Tell the window about the camera also
		CameraWindow.SetCamera(Camera)
		
		-- Create a light - we use a pointlight in the center
		Light = create Point( Color.White, Color.White, Color.White, 
							  [10,10,10], 50*AU, 0.0)
		
		-- Tell the camera about the light
		Camera <- SetLight(Light)
		
		-- Build the solar system
		SolarSystem = create SolarSystemDesign
		
		-- Tell the camera the scene we ar shooting
		Camera <- SetScene(SolarSystem)
		
		-- We start the camera
		Camera <- Roll
		
		-- and we print some feedback
		Window <- print("\nScene has been created")
	end
		...
end
			

The Planet

For each planet we create a sphere. The Sphere design is derived from the design SharedMesh that is part of the D3D11 package. Because all planets and the sun are spheres, we will use only one mesh - it saves some space, but we could equally have given each planet its own mesh. The shared mesh will be scaled and repositioned in the scene for each planet at each new frame.

When the camera is started it sends two messages to the scene at the start of each new frame: NewFrame and Draw. Typically static objects would ignore NewFrame, and invisible objects would ignore Draw.

The message Draw is handled by the ancestor SharedMesh, because drawing a mesh is the same for all meshes, so we do not have to handle that in the Sphere design. The NewFrame message however is not handled by SharedMesh and has to be handled here. In our case, each time we receive the message, we have to advance the planet a little bit in its orbit around the sun.

In order to reposition the planet at each frame tick, we have to multiply the WorldMatrix of the planet by a RotationMatrix. To do that we use a variable function with a local permanent variable - the rotation matrix - that is initialised when the orbit for the planet is set with the SetOrbit message. Note that we also could have used a global variable RotationMatrix like WorldMatrix, but by putting it inside the function it becomes more clear that it is only used there.

Each time the message NewFrame is received, all we have to do is to calculate the new WorldMatrix by calling the function FrameAction. That WorldMatrix is used in the message handler for the Draw message to reposition the planet.

The Planet Design
-- A sphere, derived from the shared mesh design
design Sphere(float Radius,MeshClass cppMesh) like SharedMesh(cppMesh) is
	
	-- The world matrix for the sphere
	WorldMatrix = [
			Radius,	0,		0,		Position.x,
			0,		Radius,	0,		Position.y,
			0,		0,		Radius,	Position.z,
			0,		0,		0,		1
		]
	
	-- a function variable for the frame action
	var function FrameAction (word Interval) to do
	
	-- A message that sets the particular frame action for the planet
	on SetOrbit( xyz Axis, float AngularSpeed) do
		
		-- Define the body for FrameAction
		body FrameAction with
			
			-- Calculate the rotation matrix once at definition
			matrix4 RotationMatrix =  Rotate.Axis(Axis,AngularSpeed)
		end is
			
			-- Calculate the world matrix each time the function is called
			WorldMatrix = RotationMatrix * WorldMatrix
		end
	end
	
	-- Message handler called for each new frame
	on NewFrame(word Interval) do
		
		-- ..just execute the frame action for the sphere
		FrameAction( Interval )
	end
	
	-- The message 'Draw' is handled by SharedMesh.
	-- Drawing is the same for all meshes.
end
			

The Solar System

In the design SolarSystemDesign we create a cell for each planet and one for the sun. In order to create the solar system, we initialize an array of records that contains all the relevant data for the planets. We also define some constants to make sure that the planets will be visible on screen, while respecting relative sizes and distances.

The distances of the planets to the sun and the sizes of the planets are correct relative to each other, but planet size and orbit are not to scale, because otherwise we would have tiny planets separated by large voids.

The orbital speeds of the planets are correct relative to each other, with the length of an earth day chosen to be 10ms. In that way the earth revolves around the sun in 3.65 sec. The color of the planet is chosen to look somewhat as expected.

When each planet is created, we send it also three messages to set additional parameters. Which parameters to include when the cell is constructed and which parameters to make settable with a message is up to the designer.

The Solar System
-- astronomical unit is 149 597 871 km - x 3 to spread the planets out
const float AU = 149.597871	* 3
-- the earth radius is 12 756 km
const float ER = 12.756
-- the duration of an earth year in seconds - 1 day = 10 msec
const float EY = 3.65

-- The design of the solar system
design SolarSystemDesign is
	
	-- The name of the planets - Pluto rehabilitated
	type PlanetEnum is [Mercury, Venus, Earth, Mars, Jupiter, 
						Saturn, Uranus, Neptune, Pluto]
	
	-- The mesh for a sphere - shared by all spheres
	Mesh.PolarSphere SphereMesh
	
	-- The data for a planet
	type PlanetRecord is record
		cell			Cell
		float 			Diameter 	-- in Earth diameters
		float			Mass		-- In Earth Masses
		float 			Distance	-- From Sun, in AU
		float			Period		-- in earth years
		Material.rgba 	Material 	-- material of the planet
	end
	
	-- The list of data for the planets
	PlanetRecord PlanetList[ PlanetEnum ] = [
	
			--	cell   Diameter	Mass   Distance Period	Material
	-.Mercury.-	[null,	0.382,	0.06, 	0.39,	0.24,	Material.White],
	-.Venus.-	[null,	0.949, 	0.82,	0.72,	0.62,	Material.Mustard],
	-.Earth.-	[null,	1.0,	1.0,	1.0,	1.0,	Material.Blue],
	-.Mars.-	[null,	0.532,	0.11, 	1.52,	1.88,	Material.Red],
	-.Jupiter.-	[null,	11.209,	317.8,	5.2,	11.86,	Material.Brown],
	-.Saturn.-	[null,	9.449,	95.2,	9.54,	29.46,	Material.Green],
	-.Uranus.-	[null,	4.007,	14.6, 	19.22,	84.01,	Material.Aqua],
	-.Neptune.-	[null,	3.883,	17.2,	30.06,	164.8,	Material.Blue],
	-.Pluto.-	[null,	0.1863,	0.00218,39.54,	248.25,	Material.Fuschia]
	]
	
	-- Define one record for the sun - no need to define a type
	var Sun is record
		cell 	Cell = null
		float 	Diameter = 109
		float 	Mass = 333000
		Material.rgba Stuff = Material.Yellow * 2.0
	end
	
	-- Constructing the solar system
	constructor is
		
		-- Build a sphere mesh with 16 slices and 8 stacks
		SphereMesh.Build(16,8)
		
		-- create the sun (at 10% of its size !) and set the material
		Sun.Cell = create Sphere( Sun.Diameter*ER/10, SphereMesh.cppMesh )
				   <- SetMaterial( Sun.Stuff )
		
		-- Create all the planets
		for each Planet in PlanetList do
			
		  -- create planet and send three messages
		  Planet.Cell = create Sphere(Planet.Diameter*ER,
									  SphereMesh.cppMesh)
			  <-  SetMaterial( Planet.Material  ),
				  MoveTo( [Planet.Distance*AU, 0, 0]),
				  SetOrbit([0,1,0], 2*pi_f/(25 * EY * Planet.Period))
		end
	end
end
			

Counting the years

As a final feature, we want the application to output the number of earth-years that have passed each time the earth has completed a revolution around the sun.

To do that we request a service from the system. The system is a standard cell that is created by the VM when it is started. As the name implies it provides all sorts of services that one would expect from the system. Note however that these services are not hardcoded in the VM, but can be extended, modified etc. More about this in the language documentation.

When requesting a service, the system will return a message with the name of the service and the id of the cell that handles the service. In our case we request for a timer-service. When the message Service.Provider is received from the system, it is always good to first check that the service is the timer service we have requested - we could have requested other services also.

The timer service has an interface Subscribe to ask for timer services. In our case we request to receive a message ReportYears every 3650 msec. When the message ReportYears is received, we send a println message to the system. The println message has one string parameter, and the system just prints that parameter to stdout, in our case the text window that we have set a stdout.

Counting the years

-- This is the design that we will instatiate to get the program going
design HelloEarth is

	-- A cell for the timer service
	cell Timer
	constructor is
		...
		-- Request a timer service from the system
		system <- Service.Get("Timer")
		...
	end
	
	-- The service we requested
	on Service.Provider( ansi Name, cell Provider) do
		
		-- we only requested a timer ..
		Name is "Timer" ? Timer = Provider	: yield
		
		-- Request a 'ReportYears' message every 3.65 seconds = EY*1000 
		Timer <- Subscribe.Interval( (EY * 1000) ,"ReportYears")
	end
	int YearCount = 0
	
	-- When the message is received, report the nr of years
	on ReportYears( word Time ) do
		system <- println("[YearCount += 1] earth years have passed")
	end
end
			

Completed application

Finally we can put it all together to form the finished application.

Completed Application
-- External standard CellSpeak groups
use Math, Strings, Editor, Windows

-- External groups specific to DirectX
use D3D11, Camera, Light, Color, Material, Shape

-- A sphere, derived from the shared mesh design
design Sphere(float Radius,MeshClass cppMesh) like SharedMesh(cppMesh) is
	
	-- The world matrix for the sphere
	WorldMatrix = [
			Radius,	0,		0,		Position.x,
			0,		Radius,	0,		Position.y,
			0,		0,		Radius,	Position.z,
			0,		0,		0,		1
		]
	
	-- a function variable for the frame action
	var function FrameAction (word Interval) to do
	
	-- Message handler called for each new frame
	on NewFrame(word Interval) do
		
		-- ..just execute the frame action for the sphere
		FrameAction( Interval )
	end
	
	-- A message that sets the particular frame action for the planet
	on SetOrbit( xyz Axis, float AngularSpeed) do
		
		-- Define the body for FrameAction
		body FrameAction with
			
			-- Calculate the rotation matrix to use once at definition
			matrix4 RotationMatrix =  Rotate.Axis( Axis , AngularSpeed)
		end is
			
			-- Calculate the world matrix each time the function is called
			WorldMatrix = RotationMatrix * WorldMatrix
		end
	end
	
	-- The message 'Draw' is handled by SharedMesh
end

-- astronomical unit is 149 597 871 km - x 3 to spread the planets out
const float AU = 149.597871	* 3

-- the earth radius is 12 756 km
const float ER = 12.756

-- the duration of an earth year in seconds - 1 day = 10 msec
const float EY = 3.65

-- The design of the solar system
design SolarSystemDesign is
	
	-- The name of the planets - Pluto rehabilitated
	type PlanetEnum is [Mercury, Venus, Earth, Mars, Jupiter, 
						Saturn, Uranus, Neptune, Pluto]
	
	-- The mesh for a sphere - shared by all spheres.
	Mesh.PolarSphere SphereMesh	
	
	-- The data for a planet
	type PlanetRecord is record
		cell			Cell
		float 			Diameter 	-- in Earth diameters
		float			Mass		-- In Earth Masses
		float 			Distance	-- From Sun, in AU
		float			Period		-- in earth years
		Material.rgba 	Material 	-- material of the planet
	end
	
	-- The list of data for the planets.
	PlanetRecord PlanetList[ PlanetEnum ] = [
	
			--	cell   Diameter	Mass   Distance Period	Material
	-.Mercury.-	[null,	0.382,	0.06, 	0.39,	0.24,	Material.White],
	-.Venus.-	[null,	0.949, 	0.82,	0.72,	0.62,	Material.Mustard],
	-.Earth.-	[null,	1.0,	1.0,	1.0,	1.0,	Material.Blue],
	-.Mars.-	[null,	0.532,	0.11, 	1.52,	1.88,	Material.Red],
	-.Jupiter.-	[null,	11.209,	317.8,	5.2,	11.86,	Material.Brown],
	-.Saturn.-	[null,	9.449,	95.2,	9.54,	29.46,	Material.Green],
	-.Uranus.-	[null,	4.007,	14.6, 	19.22,	84.01,	Material.Aqua],
	-.Neptune.-	[null,	3.883,	17.2,	30.06,	164.8,	Material.Blue],
	-.Pluto.-	[null,	0.1863,	0.00218,39.54,	248.25,	Material.Fuschia]
	]
	
	-- Define one record for the sun - no need to define a type
	var Sun is record
		cell 	Cell = null
		float 	Diameter = 109
		float 	Mass = 333000
		Material.rgba Stuff = Material.Yellow * 2.0
	end
	
	-- Constructing the solar system
	constructor is
		
		-- Build a sphere mesh
		SphereMesh.Build(16,8)
		
		-- create the sun (at 10% of its size !) and set the material
		Sun.Cell = create Sphere( Sun.Diameter*ER/10,SphereMesh.cppMesh)
				   <- SetMaterial( Sun.Stuff )
		
		-- Create all the planets
		for each Planet in PlanetList do
		  
		  -- create planet and send three messages
		  Planet.Cell = create Sphere(Planet.Diameter*ER , 
									  SphereMesh.cppMesh)
					  <- SetMaterial( Planet.Material  ),
					  	 MoveTo( [Planet.Distance*AU, 0, 0]),
						 SetOrbit([0,1,0],2*pi_f/(25*EY*Planet.Period))
		end
	end
end

-- This is the design that we will instatiate to get the program going
design HelloEarth is
	cell Camera						-- We need a camera
	cell Light						-- We need a light
	cell Window						-- A text output window
	cell Timer 						-- we need a timer
	CanvasWindowClass CameraWindow	-- The camera needs a window
	Hwnd CameraWindowHandle			-- The window handle
	RectangleType Rectangle			-- The window rectangle
	cell SolarSystem				-- Our scene
	constructor is
		
		-- The 'from' and 'to' vectors for the camera
		xyz From = [0,0,10*AU]
		xyz To = [0,0,0]
		
		-- Create the output window
		Window = create MenuWindow("The Solar System")
		
		-- set it also as stdout
		system <- stdout.Set( Window )
		
		-- Create a camera
		Camera 	= create FromTo(From,To, [0,1,0], 100*AU, 0.1, pi_f/4)
		
		-- The camera needs a window to display in
		Rectangle = [500,0,2000,1000]
		
		-- Create the camera window class (calls a C++ method)
		CameraWindow = CameraWindow.Create("Camera",&Rectangle, 0,0,null)
		
		-- Tell the window about the camera for keyboard and mouse events
		CameraWindow.SetCamera(Camera)
		
		-- Create a light for the scene
		Light = create Point( Color.White, Color.White, Color.White, 
							  [10,10,10], 50*AU, 0.0)
		
		-- Build the solar system
		SolarSystem = create SolarSystemDesign
		
		-- Tell the camera about the window, the light and the scene
		Camera <- SetWindow(CameraWindow), 
				  SetLight(Light), 
				  SetScene(SolarSystem)
		
		-- Request a timer service from the system
		system <- Service.Get("Timer")
		
		-- We start the camera
		Camera <- Roll
		
		-- and we print some feedback
		Window <- print("\nScene has been created")
	end
	
	-- The service we requested
	on Service.Provider( ansi Name, cell Provider) do
		
		-- we only requested a timer ..
		Name is "Timer" ? Timer = Provider : yield
		
		-- Request a 'ReportYears' message every 3.65 seconds = EY*1000 
		Timer <- Subscribe.Interval( (EY * 1000) ,"ReportYears")
	end
	int YearCount = 0
	
	-- When the message is received, report the nr of years
	on ReportYears( word Time ) do
		system <- println("[YearCount += 1] earth years have passed")
	end
end
			

Screen Shot of the running application

Solar System