Creating a 2D Interface in a 3D Application (FireMonkey 3D Tutorial)

From RAD Studio
Jump to: navigation, search

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.

Adding and Adjusting a 2D Surface (TLayer3D) and a TButton

  1. With the Form from the previous tutorial selected in either the Object Inspector or the Structure View, double-click TLayer3D in the Tool Palette.
    Layer 3D In Form.png
  2. 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).
    Layer 3D with Button In Form.png
  3. 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:
    Layer with project screen.png
  4. 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:
    • Align to MostRight (or any other alignment you choose)
    • Width to 150 (or any other width you choose, specified in pixels)
    For example:
    Aligned Layer.png

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.

  1. With TLayer3D in focus, add five TGroupBoxes.
    1. 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.
    2. 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).
    3. Select each TGroupBox and set:
      • The Text to Width, Height, Depth, Move, and Rotate
      • The Name to WidthGroupBox, HeightGroupBox, DepthGroupBox, MoveGroupBox, and RotateGroupBox. Make sure that the Name and Text are related.
      • The Enabled property to False.
    4. 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.
    5. 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.
    6. Add three TTrackBar components, used to rotate the 3D object on each axis, to the Rotate group box.
    7. Change the names of the TTrackBar to RotateXTrackBar, RotateYTrackBar, and RotateZTrackBar.
    8. In the Object Inspector set, for each track bar, the Min property to -360, and the Max property to 360.
  2. Add another TLayer3D to the form.
    1. With the new TLayer3D in focus, in the Object Inspector, set the Projection property to Screen, and the Align property to Top.
    2. Add two TGroupBox components to the new TLayer3D.
    3. Select each TGroupBox and set:
      • The Text to Item to be Manipulated, and Light On/off.
      • The Name to ManipulateItemGroupBox, and LightGroupBox. Make sure that the Name and Text are related.
    4. Add three TRadioButtons to the ManipulateItemGroupBox, one for each 3D object in the scene.
    5. Select each TRadioButton and set:
      • The Text to Cube1, Cube2, and Light.
      • The Name to Cube1RadioButton, Cube2RadioButton, and LigthRadioButton. Make sure that the Name and Text are related.
    6. Add a TButton to the LightGroupBox.
    7. Select the button and set:
      • The Text to OFF.
      • The Name to LightOnOffButton.

The final layout should look like this:

FinalInaterface.png

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:

LightON.png LightOFF.png

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.
LightON.png
  • Check Cube1Radiobutton. When you click the increase/decrease buttons, the size of the cube changes.
    Cub1Risezed.png
  • Check Cube1Radiobutton. When you click the Left / Right buttons, the position of the cube changes.
    Cub2Moved.png
  • 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.
    Cub2Rotates.png
  • Check LightRadiobutton. When you increase the value of the track bars, the light is rotated and its effect can be seen over Cube2.
    LightRotates.png

Previous

See Also

Samples