Creating a 2D Interface in a 3D Application (FireMonkey 3D Tutorial)
Go Up to Tutorial: Creating a FireMonkey 3D Application
A 3D application cannot directly use 2D components such as buttons or lists. If a 2D component is dragged and dropped on a 3D container, the component will not be rendered but it will appear in the Structure View as a child control. However, there is a simple way to deal with this problem, by creating a bridge between the 3D and 2D scenes. FireMonkey provides the FMX.Layers3D.TLayer3D component for this purpose.
Contents
Adding and Adjusting a 2D Surface (TLayer3D) and a TButton
- With the Form from the previous tutorial selected in either the Object Inspector or the Structure View, double-click TLayer3D in the Tool Palette.
- Now you can use the TLayer3D component as the surface for FireMonkey 2D components.
- With TLayer3D selected in either the Structure View or Object Inspector, add a TButton. In the Structure View, make sure the button is the child of the layer, not a child of the Form (if you need to correct the hierarchy, drag the TButton and drop it on the Layer).
- To simulate a 2D surface, change the Projection type of the TLayer3D component. In the Object Inspector, set the following property of the layer:
- Projection = Screen
- A screen projection is a screen plane pixel-to-pixel projection with a fixed (invisible) camera, and the front plane of the 3d-frustum is now a screen plane. Every object is still 3D, but can be manipulated as if 2D.
- Your 3D application should now look similar to the following figure:
- As you can see, the TLayer3D component has overshadowed the scene, to say the least. In Object Inspector, with TLayer3D in focus, set the following properties:
Adding and Arranging 2D Components
Now delete the added button, to add other components through which the 3D objects on the form are rotated, moved, or resized.
- With TLayer3D in focus, add five TGroupBoxes.
- Select all the group boxes in the Structure View by pressing CTRL and clicking the mouse, and in the Object Inspector, set Align to
Top
. - Select the first three group boxes from the top, and in the Object Inspector, set Height to
70
(or another value you consider to better fit the final layout). - Select each TGroupBox and set:
- Add pairs of two buttons to the first three TGroupBox. One button is used to increase and one, to decrease the width, height, and depth.
- Change the names of the button pairs as follows:
- IncreaseWidthButton and DecreaseWidthButton
- IncreaseHeightButton and DecreaseHeightButton
- IncreaseDepthButton and DecreaseDepthButton
- Change Text in concordance with the button name.
- Add three sets of two-button pairs to the Move group boxes.
- Change the names of the button pairs as follows:
- MoveRightButton and MoveLeftButton
- MoveDownButton and MoveUpButton
- MoveBackButton and MoveFrontButton
- Change Text in concordance with the button name.
- Add three TTrackBar components, used to rotate the 3D object on each axis, to the Rotate group box.
- Change the names of the TTrackBar to RotateXTrackBar, RotateYTrackBar, and RotateZTrackBar.
- In the Object Inspector set, for each track bar, the Min property to
-360
, and the Max property to360
.
- Select all the group boxes in the Structure View by pressing CTRL and clicking the mouse, and in the Object Inspector, set Align to
- Add another TLayer3D to the form.
- With the new TLayer3D in focus, in the Object Inspector, set the Projection property to
Screen
, and the Align property toTop
. - Add two TGroupBox components to the new TLayer3D.
- Select each TGroupBox and set:
- Add three TRadioButtons to the ManipulateItemGroupBox, one for each 3D object in the scene.
- Select each TRadioButton and set:
- Add a TButton to the LightGroupBox.
- Select the button and set:
- With the new TLayer3D in focus, in the Object Inspector, set the Projection property to
The final layout should look like this:
Adding Events, and Event Handlers
You now have a simple 3D scene in which you can easily assign events to objects.
- 1. To get what 3D object is to be manipulated, declare a global string variable to keep the name of the object.
The track bar offers the possibility to rotate the object in focus on the OnChange event. To avoid updating the rotation angles for the previous object when another object is selected, add a Boolean variable RotateFlag. If it is set to False, the code in the OnChange event handler will not occur.
//Delphi declaration
var
Form1: TForm1;
ManipulatedItem: String;
RotateFlag: boolean;
// C++ declaration
TForm3D1 *Form3D1;
System::UnicodeString ManipulatedItem;
bool RotateFlag;
- 2. Add OnChange event handlers to the radio buttons by double-clicking each one. Below is the Delphi and C++ code for Cube1RadioButton. The code for Cube2RadioButton is the same, only that Cube2 is used instead of Cube1.
// The Delphi implementation for the radio button OnChange event
procedure TForm1.Cube1RadioButtonChange(Sender: TObject);
begin
//enables the group boxes to manipulate Cube1
WidthGroupBox.Enabled := True;
HeightGroupBox.Enabled := True;
DepthGroupBox.Enabled := True;
MoveGroupBox.Enabled := True;
RotateGroupBox.Enabled := True;
//updates the values for the track bars to display the current value of the Cube1.RotationAngle
RotateFlag:=false;
RotateXTrackBar.Value := Cube1.RotationAngle.X;
RotateYTrackBar.Value := Cube1.RotationAngle.Y;
RotateZTrackBar.Value := Cube1.RotationAngle.Z;
RotateFlag:=True;
//Saves the name of the 3D object to be manipulated
ManipulatedItem:=Cube1.Name;
end;
// The C++ implementation for the radio button OnChange event
void __fastcall TForm3D1::Cube1RadioButtonChange(TObject *Sender)
{
//enables the group boxes to manipulate Cube1
WidthGroupBox->Enabled = true;
HeightGroupBox->Enabled = true;
DepthGroupBox->Enabled = true;
MoveGroupBox->Enabled = true;
RotateGroupBox->Enabled = true;
//updates the values for the track bars to display the current value of the Cube1.RotationAngle
RotateFlag = false;
RotateXTrackBar->Value = Cube1->RotationAngle->X;
RotateYTrackBar->Value = Cube1->RotationAngle->Y;
RotateZTrackBar->Value = Cube1->RotationAngle->Z;
RotateFlag = true;
//Saves the name of the 3D object to be manipulated
ManipulatedItem = Cube1->Name;
}
For LightRadioButton, the code does not enable all the manipulation group boxes. Moving and resizing the TLight has now visual effects over the scene. The implementation for LightRadioButton is:
// The Delphi implementation for the radio button OnChange event
procedure TForm1.LightRadioButtonChange(Sender: TObject);
begin
//disables the group boxes that move and resize the light
WidthGroupBox.Enabled := False;
HeightGroupBox.Enabled := False;
DepthGroupBox.Enabled := False;
MoveGroupBox.Enabled := False;
// the light is manipulated only if it is on
if(Light1.Enabled=True) then
begin
RotateGroupBox.Enabled := True;
//updates the values for the track bars to display the current value of the Light1.RotationAngle
RotateFlag:=false;
RotateXTrackBar.Value := Light1.RotationAngle.X;
RotateYTrackBar.Value := Light1.RotationAngle.Y;
RotateZTrackBar.Value := Light1.RotationAngle.Z;
RotateFlag:=True;
//saves the name of the 3D object to be manipulated
ManipulatedItem:=Cube1.Name;
end
else
RotateGroupBox.Enabled := false;
end;
end;
// The C++ implementation for the radio button OnChange event
void __fastcall TForm3D1::LightRadioButtonChange(TObject *Sender)
{
//disables the group boxes that move and resize the light
WidthGroupBox->Enabled = false;
HeightGroupBox->Enabled = false;
DepthGroupBox->Enabled = false;
MoveGroupBox->Enabled = false;
// the light is manipulated only if it is on
if (Light1->Enabled == true)
{
RotateGroupBox->Enabled = true;
//updates the values for the track bars to display the current value of the Light1.RotationAngle
RotateFlag = false;
RotateXTrackBar->Value = Light1->RotationAngle->X;
RotateYTrackBar->Value = Light1->RotationAngle->Y;
RotateZTrackBar->Value = Light1->RotationAngle->Z;
RotateFlag = true;
//saves the name of the 3D object to be manipulated
ManipulatedItem=Cube1->Name;
}
else
RotateGroupBox->Enabled = false;
}
- 3. Implementation of the LightOnOffButton button:
procedure TForm1.LightOnOffButtonClick(Sender: TObject);
begin
if Light1.Enabled = True then
begin
Light1.Enabled := False;
LightOnOffButton.Text := 'ON';
if(LightRadioButton.IsChecked) then
RotateGroupBox.Enabled:=False;
end
else
begin
Light1.Enabled := True;
LightOnOffButton.Text := 'OFF';
if(LightRadioButton.IsChecked) then
RotateGroupBox.Enabled:=True;
end;
end;
void __fastcall TForm3D1::LightOnOffButtonClick(TObject *Sender)
{
if (Light1->Enabled == true)
{
Light1->Enabled = false;
LightOnOffButton->Text = "ON";
if (LightRadioButton->IsChecked)
RotateGroupBox->Enabled = false;
}
else
{
Light1->Enabled = true;
LightOnOffButton->Text = "OFF";
if (LightRadioButton->IsChecked)
RotateGroupBox->Enabled = true;
}
}
Difference between light on and off:
- 4. Resizing buttons has a similar implementation. Below is the implementation for increasing and decreasing the Width of the 3D object.
//Increasing and decreasing the Width of the 3D object
procedure TForm1.IncreaseWidthButtonClick(Sender: TObject);
begin
TControl3D(FindComponent(ManipulatedItem)).Width:=TControl3D(FindComponent(ManipulatedItem)).Width+1;
end;
procedure TForm1.DecreaseWidthButtonClick(Sender: TObject);
begin
TControl3D(FindComponent(ManipulatedItem)).Width:=TControl3D(FindComponent(ManipulatedItem)).Width-1;
end;
void __fastcall TForm3D1::IncreaseWidthButtonClick(TObject *Sender)
{
((TControl3D*)(FindComponent(ManipulatedItem)))->Width=((TControl3D*)(FindComponent(ManipulatedItem)))->Width+1;
}
void __fastcall TForm3D1::DecreaseWidthButtonClick(TObject *Sender)
{
((TControl3D*)(FindComponent(ManipulatedItem)))->Width=((TControl3D*)(FindComponent(ManipulatedItem)))->Width-1;
}
- 5. Moving buttons has a similar implementation. Below is the implementation for moving the 3D object right and left.
procedure TForm1.MoveRightButtonClick(Sender: TObject);
begin
TControl3D(FindComponent(ManipulatedItem)).Position.X:=TControl3D(FindComponent(ManipulatedItem)).Position.X+1;
end;
procedure TForm1.MoveLeftButtonClick(Sender: TObject);
begin
TControl3D(FindComponent(ManipulatedItem)).Position.X:=TControl3D(FindComponent(ManipulatedItem)).Position.X-1;
end;
void __fastcall TForm3D1::MoveRightButtonClick(TObject *Sender)
{
((TControl3D*)(FindComponent(ManipulatedItem)))->Position->X=((TControl3D*)(FindComponent(ManipulatedItem)))->Position->X+1;
}
void __fastcall TForm3D1::MoveLeftButtonClick(TObject *Sender)
{
((TControl3D*)(FindComponent(ManipulatedItem)))->Position->X=((TControl3D*)(FindComponent(ManipulatedItem)))->Position->X-1;
}
- 6. Rotating track bars has a similar implementation. Below is the implementation for rotating the 3D object around the X-axis.
procedure TForm1.RotateXTrackBarChange(Sender: TObject);
begin
if (ManipulatedItem<>'') and (RotateFlag=True) then
TControl3D(FindComponent(ManipulatedItem)).RotationAngle.X:= RotateXTrackBar.Value;
end;
void __fastcall TForm3D1::RotateXTrackBarChange(TObject *Sender)
{
if (ManipulatedItem!="" && RotateFlag==true)
((TControl3D*)(FindComponent(ManipulatedItem)))->RotationAngle->X=RotateXTrackBar->Value;
}
- 7. Run the project by pressing F9.
-
- Check Cube1Radiobutton. When you click the increase/decrease buttons, the size of the cube changes.
- Check Cube1Radiobutton. When you click the Left / Right buttons, the position of the cube changes.
- Check Cube1Radiobutton. When you increase the value of the track bars, the cube is rotated clockwise, and if the value is decreased, the cube is rotated counterclockwise.
- Check LightRadiobutton. When you increase the value of the track bars, the light is rotated and its effect can be seen over Cube2.
Previous
See Also
- Creating a FireMonkey Component (Delphi)
- Creating a FireMonkey Component (C++)
- 3D Multi-Device Application
- Working with Events and Event Handlers
Samples
- FireMonkey 3D Arrows sample
- FireMonkey First App3D sample
- FireMonkey GUI3d sample
- FireMonkey Cameras Textures 3D sample