File: gamelocations.pas

package info (click to toggle)
castle-game-engine 6.4%2Bdfsg1-2
  • links: PTS, VCS
  • area: main
  • in suites: buster
  • size: 194,520 kB
  • sloc: pascal: 364,585; ansic: 8,606; java: 2,851; objc: 2,601; cpp: 1,412; xml: 851; makefile: 725; sh: 563; php: 26
file content (266 lines) | stat: -rw-r--r-- 8,973 bytes parent folder | download | duplicates (2)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
{
  Copyright 2008-2017 Michalis Kamburelis.

  This file is part of "The Rift".

  "The Rift" is free software; you can redistribute it and/or modify
  it under the terms of the GNU General Public License as published by
  the Free Software Foundation; either version 2 of the License, or
  (at your option) any later version.

  "The Rift" is distributed in the hope that it will be useful,
  but WITHOUT ANY WARRANTY; without even the implied warranty of
  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  GNU General Public License for more details.

  You should have received a copy of the GNU General Public License
  along with "The Rift"; if not, write to the Free Software
  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA

  ----------------------------------------------------------------------------
}

{ Locations in game (Locations). }
unit GameLocations;

interface

uses Classes, Generics.Collections,
  CastleUtils, CastleClassUtils, CastleScene, CastleVectors, CastleTransform,
  CastleGLImages, X3DNodes, CastleRectangles, CastleRenderer;

type
  TLocation = class
  private
  public
    type
      TLocationScene = class(TCastleScene)
      private
        Image, ShadowedImage: TGLImage;
      public
        SceneManagerRect: TRectangle;
        RenderInternalModel: boolean;
        procedure LocalRender(const Params: TRenderParams); override;
      end;
  private
    FName: string;
    FImageURL: string;
    FShadowedImageURL: string;
    FSceneURL: string;
    FScene: TLocationScene;
    FImage, FShadowedImage: TGLImage;
    FSceneCameraDescription: string;
    FPlayerPosition: TVector3;
    FPlayerDirection: TVector3;
    FPlayerUp: TVector3;
    Loaded: boolean;
  public
    destructor Destroy; override;

    { Create things necessary for playing (displaying this location). }
    procedure Load(const PrepareParams: TPrepareParams);

    { Location short name, must be simple (needs to be valid XML element
      name and X3D node name, for easy data reading). }
    property Name: string read FName;

    property ImageURL: string read FImageURL;
    property ShadowedImageURL: string read FShadowedImageURL;
    property SceneURL: string read FSceneURL;

    property SceneCameraDescription: string read FSceneCameraDescription;

    property PlayerPosition: TVector3 read FPlayerPosition;
    property PlayerDirection: TVector3 read FPlayerDirection;
    property PlayerUp: TVector3 read FPlayerUp;

    property Scene: TLocationScene read FScene;
    property Image: TGLImage read FImage;
    property ShadowedImage: TGLImage read FShadowedImage;
  end;

  TLocationList = class(specialize TObjectList<TLocation>)
  public
    StartLocation: TLocation;

    { Create locations and set their parameters from GameConfig. }
    constructor Create;
  end;

var
  { List of all locations, should be created in Application.OnInitialize. }
  Locations: TLocationList;

implementation

uses SysUtils, DOM,
  CastleProgress, CastleImages, CastleUIControls, CastleGLUtils, CastleXMLUtils,
  CastleSceneCore, CastleApplicationProperties, X3DLoad,
  GameConfiguration;

{ TLocation.TLocationScene --------------------------------------------------- }

procedure TLocation.TLocationScene.LocalRender(const Params: TRenderParams);

  { Draw Image centered on screen, to fit inside the scene manager rect,
    matching the 3D scene projection. }
  procedure DrawImage(const Image: TGLImage);
  var
    DrawRect: TRectangle;
  begin
    { Draw Image such that Image.Height always fills the SceneManagerRect.Height,
      because that is the field of view of 3D scene,
      and that was the field of view used to render the image in Blender. }
    DrawRect := Image.Rect.ScaleToHeight(SceneManagerRect.Height);
    { above calculated DrawRect size OK, but DrawRect position (Left, Bottom)
      should be fixed now. }
    DrawRect := SceneManagerRect.CenterInside(DrawRect.Width, DrawRect.Height);
    Image.Draw(DrawRect);
  end;

var
  SavedProjectionMatrix: TMatrix4;
begin
  if RenderInternalModel then
  begin
    Attributes.Mode := rmFull;
    inherited;
  end else
  begin
    { this makes a tiny (not important in case of our trivial location 3D model)
      optimization: since we only care about filling the depth buffer,
      rendering with rmDepth will not initialize some material stuff. }
    Attributes.Mode := rmDepth;

    RenderContext.ColorMask := false;
    inherited;
    RenderContext.ColorMask := true;

    { Render the 2D image covering the location.

      This cooperates correctly with shadow volumes, because the Image.Draw call
      is correctly masked with the stencil buffer settings of the shadow volume
      renderer.

      To work correctly, the location scene must be rendered before creatures
      (like Player) scenes (otherwise Image.Draw would unconditionally cover
      the the Player). This is satisfied, since CurrentLocation.Scene
      is first in SceneManager.Items. }

    if (not Params.Transparent) and
       (Params.ShadowVolumesReceivers = ReceiveShadowVolumes) then
    begin
      { Nole that the 3 lines that save, set and restore RenderContext.ProjectionMatrix
        are necessary only in case GLFeatures.EnableFixedFunction = true,
        which will be false on all modern GPUs and OpenGLES.
        When GLFeatures.EnableFixedFunction = false,
        then rendering Image doesn't need to have a projection matrix set. }
      SavedProjectionMatrix := RenderContext.ProjectionMatrix;
      OrthoProjection(FloatRectangle(SceneManagerRect)); // need 2D projection

      if Params.InShadow then
        DrawImage(ShadowedImage)
      else
        DrawImage(Image);

      RenderContext.ProjectionMatrix := SavedProjectionMatrix; // restore 3D projection
    end;
  end;
end;

{ TLocation ------------------------------------------------------------------ }

destructor TLocation.Destroy;
begin
  FreeAndNil(FScene);
  FreeAndNil(FImage);
  FreeAndNil(FShadowedImage);
  inherited;
end;

procedure TLocation.Load(const PrepareParams: TPrepareParams);
begin
  if Loaded then Exit;
  Loaded := true;

  FImage := TGLImage.Create(ImageURL, [TRGBImage]);
  FShadowedImage := TGLImage.Create(ShadowedImageURL, [TRGBImage]);

  FScene := TLocationScene.Create(nil);
  FScene.Spatial := [ssRendering, ssDynamicCollisions];
  // two-sided lighting
  FScene.Attributes.PhongShading := true;
  { The shadows are already drawn on location Image,
    so no need to cast them on location again.
    TODO: This also means that location cannot cast shadows on Player.
    A better approach would be to leave CastShadowVolumes = true (default),
    and change location Image to *not* contain location shadows "baked". }
  FScene.CastShadowVolumes := false;
  FScene.Load(SceneURL, true);
  FScene.PrepareResources([prRender, prBoundingBox], false, PrepareParams);
  FScene.Image := Image;
  FScene.ShadowedImage := ShadowedImage;
end;

{ TLocationList ------------------------------------------------------------- }

constructor TLocationList.Create;

  procedure MissingLocationAttribute(const AttrName: string);
  begin
    raise Exception.CreateFmt('Location doesn''t have a required attribute "%s"',
      [AttrName]);
  end;

var
  I: TXMLElementIterator;
  LocationsElement: TDOMElement;
  Location: TLocation;
  StartLocationName: string;
begin
  inherited Create(true);

  LocationsElement := GameConfig.PathElement('locations');
  if LocationsElement = nil then
    raise Exception.Create('Unable to find XML <locations> element');

  if not LocationsElement.AttributeString('start_name', StartLocationName) then
    raise Exception.Create(
      '<locations> doesn''t have a required attribute "start_name"');

  I := LocationsElement.ChildrenIterator;
  try
    while I.GetNext do
    begin
      Location := TLocation.Create;
      Add(Location);

      if not I.Current.AttributeString('name', Location.FName) then
        MissingLocationAttribute('name');
      if Location.Name = StartLocationName then
        StartLocation := Location;

      Location.FImageURL := I.Current.AttributeURL('image_url', GameConfig.URL);
      Location.FShadowedImageURL := I.Current.AttributeURL('shadowed_image_url', GameConfig.URL);
      Location.FSceneURL := I.Current.AttributeURL('scene_url', GameConfig.URL);

      I.Current.AttributeString('scene_camera_description',
        Location.FSceneCameraDescription);

      Location.FPlayerPosition := I.Current.AttributeVector3Def(
        'player_position', TVector3.Zero);
      Location.FPlayerDirection := I.Current.AttributeVector3Def(
        'player_direction', Vector3(1, 0, 0));
      Location.FPlayerUp := I.Current.AttributeVector3Def(
        'player_up', Vector3(0, 0, 1));
    end;
  finally FreeAndNil(I) end;

  if StartLocation = nil then
    raise Exception.CreateFmt('Start location name "%s" not found',
      [StartLocationName]);
end;

finalization
  FreeAndNil(Locations);
end.