Watch, Follow, &
Connect with Us

Please visit our new home
community.embarcadero.com.

Public Report
Report From: Delphi-BCB/FireMonkey    [ Add a report in this area ]  
Report #:  117385   Status: Closed
Can't change the camera's angle of view.
Project:  Delphi Build #:  XE4, XE5
Version:    19.0 Submitted By:   Masanori Nakayama
Report Type:  Suggestion / Enhancement Request Date Reported:  7/24/2013 9:14:10 PM
Severity:    Critical / Show Stopper Last Updated: 4/15/2014 6:43:01 PM
Platform:    All platforms Internal Tracking #:   41320
Resolution: Fixed (Resolution Comments) Resolved in Build: : XE6
Duplicate of:  None
Voting and Rating
Overall Rating: (1 Total Rating)
5.00 out of 5
Total Votes: 30
Description
Camera's angle of view is defined by projection matrix.

Projection matrix is computed by TContext3D.GetProjectionMatrix.

However, there is no way to change the GetProjectionMatrix.

It is a fatal flaw as a 3DCG framework.
Steps to Reproduce:
Even if rewritten by the class helper, it does not take effect.

At the (1), projection matrix is calculated.

+------- FMX.Types3D --------
| function TContext3D.GetProjectionMatrix: TMatrix3D;
| var
|   M: TMatrix3D;
| begin
|   if FRecalcProjectionMatrix then
|   begin
|     if SameValue(FHeight, 0.0, Epsilon) then
|       Result := MatrixPerspectiveFovRH(cPI / 4, 1.0, 1.0, 1000.0)    <<---(1)
|     else
|       Result := MatrixPerspectiveFovRH(cPI / 4, FWidth / FHeight, 1.0, 1000.0);    <<---(1)
|     if (FRenderToMatrix.m41 <> 0) or (FRenderToMatrix.m11 <> 1) then
|       Result := Matrix3DMultiply(Result, FRenderToMatrix);
|     M := IdentityMatrix3D;
|     M.m41 := FCenterOffset.X;
|     M.m42 := FCenterOffset.Y;
|     FProjectionMatrix := Matrix3DMultiply(Result, M);
|     FInvProjectionMatrix := FProjectionMatrix;
|     InvertMatrix(FInvProjectionMatrix);
|     FRecalcProjectionMatrix := False;
|   end;
|   Result := FProjectionMatrix;
| end;
+-------
Workarounds
It should be reviewed fundamentally specification.
The following is one of the proposal.
You should explore a better way.

Angle of view should be different for each camera.
Therefore, projection matrix property should be included in the TCamera.

+------- FMX.Controls3D --------
| TCamera = class(TControl3D)
| protected
|   FProjectionMatrix :TMatrix3D;
|   function GetProjectionMatrix: TMatrix3D; virtual;
|   procedure SetProjectionMatrix( const ProjectionMatrix_:TMatrix3D ); virtual;
| public
|   property ProjectionMatrix :TMatrix3D read GetProjectionMatrix write SetProjectionMatrix;
| end;
+-------

Projection matrix should not be calculated in TContext3D.
CurrentProjectionMatrix property should be changed to writable.

+------- FMX.Types3D --------
| TContext3D = class abstract(TInterfacedPersistent, IFreeNotification)
| protected
|   function GetProjectionMatrix :TMatrix3D; virtual;
|   procedure SetProjectionMatrix( const ProjectionMatrix_:TMatrix3D ); virtual;
| public
|   property CurrentProjectionMatrix: TMatrix3D read GetProjectionMatrix write SetProjectionMatrix;
| end;
|
>
|
| function TContext3D.GetProjectionMatrix: TMatrix3D;
| begin
|   Result := FProjectionMatrix;
| end;
|
| procedure TContext3D.SetProjectionMatrix( const ProjectionMatrix_:TMatrix3D );
| begin
|   FProjectionMatrix := ProjectionMatrix_;
| end;
+-------

Each time the camera is switched, TContext3D should reacquire the projection matrix.

+------- FMX.Viewport3D --------
| procedure TViewport3D.Paint;
| var
|   R: TRectF;
|   i: Integer;
|   Control: TControl3D;
| begin
|   if (csDesigning in ComponentState) then
|   begin
|     R := LocalRect;
|     InflateRect(R, -0.5, -0.5);
|     Canvas.DrawDashRect(R, 0, 0, AllCorners, AbsoluteOpacity, $A0909090);
|   end;
|   if FDrawing then Exit;
|   FDrawing := True;
|   try
|     if Assigned(Context) then
|       if Context.BeginScene then
|       try
|         Context.SetContextState(TContextState.csScissorOff);
|         Context.Clear([TClearTarget.ctColor, TClearTarget.ctDepth], FFill, 1.0, 0);
|         Context.SetCameraMatrix(GetCurrentCamera.CameraMatrix);
*         Context.ProjectionMatrix := GetCurrentCamera.ProjectionMatrix;    <<---Added!
|         Context.Lights.Clear;
|         for I := 0 to FLights.Count - 1 do
|           Context.Lights.Add(FLights[I].LightDescription);
|         if Assigned(FRenderingList) and (FRenderingList.Count > 0) then
|         begin
|           for I := 0 to FRenderingList.Count - 1 do
|             if FRenderingList[i].Visible or (not FRenderingList[i].Visible and
|               (csDesigning in ComponentState) and not FRenderingList[i].Locked) then
|             begin
|               Control := TControl3D(FRenderingList[i]);
|               if (csDesigning in Control.ComponentState) and not Control.DesignVisible then
|                 Continue;
|               TOpenControl3D(Control).RenderInternal;
|             end;
|         end;
|       finally
|         Context.EndScene;
|       end;
|     Context.CopyToBitmap(FBitmap, Rect(0, 0, FBitmap.Width, FBitmap.Height));
|   finally
|     FDrawing := False;
|   end;
|   { draw }
|   inherited Canvas.DrawBitmap(FBitmap, RectF(0, 0, FBitmap.Width, FBitmap.Height),
|     RectF(0, 0, Round(FBitmap.Width / GetViewportScale), Round(FBitmap.Height / GetViewportScale)), AbsoluteOpacity, True);
| end;
+-------

+------- FMX.Forms3D --------
| procedure TCustomForm3D.ResizeHandle;
| begin
|   inherited;
|   if Assigned(Context) and (ClientWidth > 0) and (ClientHeight > 0) then
|   begin
|     Context.SetSize(ClientWidth, ClientHeight);
|     Context.SetCameraMatrix(GetCurrentCamera.CameraMatrix);
*     Context.ProjectionMatrix := GetCurrentCamera.ProjectionMatrix;    <<---Added!
|     Realign;
|   end;
| end;
|
>
|
| procedure TCustomForm3D.PaintRects(const UpdateRects: array of TRectF);
| var
|   I: Integer;
|   Ver: TVertexBuffer;
|   Ind: TIndexBuffer;
|   Mat: TTextureMaterial;
|   Control: TOpenControl3D;
|   P: TPointF;
| begin
|   if Not Assigned(Context) then
|     Exit;
|   if FDrawing then
|     Exit;
|   FDrawing := True;
|   try
|     if Context.BeginScene then
|     try
|       Context.Clear([TClearTarget.ctColor, TClearTarget.ctDepth], FFill, 1.0, 0);
|       Context.SetCameraMatrix(GetCurrentCamera.CameraMatrix);
*       Context.ProjectionMatrix := GetCurrentCamera.ProjectionMatrix;    <<---Added!
|       Context.Lights.Clear;
|       for I := 0 to FLights.Count - 1 do
|         Context.Lights.Add(FLights[I].LightDescription);
|       if Assigned(FOnRender) then
|         FOnRender(Self, Context);
|       if Assigned(FRenderingList) and (FRenderingList.Count > 0) then
|       begin
|         for I := 0 to FRenderingList.Count - 1 do
|           if FRenderingList[i].Visible or (not FRenderingList[i].Visible and
|             (csDesigning in ComponentState) and not FRenderingList[i].Locked) then
|           begin
|             Control := TOpenControl3D(FRenderingList[i]);
|             if (csDesigning in Control.ComponentState) and not Control.DesignVisible then
|               Continue;
|             Control.RenderInternal;
|           end;
|       end;
|       { post-processing }
|       if Assigned(Children) then
|       begin
|         for i := 0 to Children.Count - 1 do
|           if (TFmxObject(Children[i]) is TEffect) and (TEffect(Children[i]).Enabled) then
|           begin
|             if Not Assigned(FEffectBitmap) then
|               FEffectBitmap := TBitmap.Create(FContext.Width, FContext.Height);
|             FEffectBitmap.Assign(Context);
|             TEffect(Children[i]).ProcessEffect(nil, FEffectBitmap, 1);
|             // create quad
|             Ver := TVertexBuffer.Create([TVertexFormat.vfVertex, TVertexFormat.vfTexCoord0], 4);
|
|             P := Context.PixelToPixelPolygonOffset;
|             Ver.Vertices[0] := Point3D(P.X, P.Y, 0);
|             Ver.TexCoord0[0] := PointF(0.0, 0.0);
|             Ver.Vertices[1] := Point3D(FEffectBitmap.Width + P.X, P.Y, 0);
|             Ver.TexCoord0[1] := PointF(1.0, 0.0);
|             Ver.Vertices[2] := Point3D(FEffectBitmap.Width + P.X, FEffectBitmap.Height + P.Y, 0);
|             Ver.TexCoord0[2] := PointF(1.0, 1.0);
|             Ver.Vertices[3] := Point3D(P.X, FEffectBitmap.Height + P.Y, 0);
|             Ver.TexCoord0[3] := PointF(0.0, 1.0);
|
|             Ind := TIndexBuffer.Create(6);
|             Ind[0] := 0;
|             Ind[1] := 1;
|             Ind[2] := 3;
|             Ind[3] := 3;
|             Ind[4] := 1;
|             Ind[5] := 2;
|             // params
|             Context.SetMatrix(IdentityMatrix3D);
|             Context.SetContextState(TContextState.cs2DScene);
|             Context.SetContextState(TContextState.csAllFace);
|             Context.SetContextState(TContextState.csAlphaBlendOff);
|             Context.SetContextState(TContextState.csZWriteOff);
|             Context.SetContextState(TContextState.csZTestOff);
|             // texture
|             Mat := TTextureMaterial.Create;
|             Mat.Texture := FEffectBitmap.Texture;
|             // render quad
|             Context.DrawTriangles(Ver, Ind, Mat, 1);
|             if Assigned(Mat) then
|               Mat.Free;
|             if Assigned(Ind) then
|               Ind.Free;
|             if Assigned(Ver) then
|               Ver.Free;
|           end;
|       end;
|     finally
|       { buffer }
|       Context.EndScene;
|     end;
|   finally
|     { off flag }
|     FDrawing := False;
|   end;
| end;
+-------
Attachment
TrueCamera.zip
Comments

Douglas Rudd at 8/4/2013 1:15:16 PM -
Better than having the user make his own Projection Matrix would be to have Camera properties like "Angle of View" or "Focal Length" to adjust the camera view and amount of perspective. GLScene has this.

Most users are not going to figure out their own Projection Matrix.

Masanori Nakayama at 8/29/2013 7:30:43 PM -
Of course, the above code is just an example.
For more information, refer to the project attached.

Tomohiro Takahashi at 8/29/2013 8:45:51 PM -
Thanks for providing sample implementation!

Masanori Nakayama at 8/30/2013 12:06:04 AM -
Fix the bug, I've uploaded a new project file.

Masanori Nakayama at 12/1/2013 4:49:09 AM -
I've uploaded the sample program for XE5.
The sample program for XE4 is also included.

Tomohiro Takahashi at 12/1/2013 5:00:20 PM -
Thanks for the information. I updated the internal status of this report.

Server Response from: ETNACODE01