~ubuntu-branches/ubuntu/utopic/castle-game-engine/utopic

« back to all changes in this revision

Viewing changes to src/x3d/x3d_pointingdevicesensor.inc

  • Committer: Package Import Robot
  • Author(s): Abou Al Montacir
  • Date: 2013-04-27 18:06:40 UTC
  • Revision ID: package-import@ubuntu.com-20130427180640-eink4nmwzuivez1c
Tags: upstream-4.0.1
ImportĀ upstreamĀ versionĀ 4.0.1

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
{
 
2
  Copyright 2007-2013 Michalis Kamburelis.
 
3
 
 
4
  This file is part of "Castle Game Engine".
 
5
 
 
6
  "Castle Game Engine" is free software; see the file COPYING.txt,
 
7
  included in this distribution, for details about the copyright.
 
8
 
 
9
  "Castle Game Engine" is distributed in the hope that it will be useful,
 
10
  but WITHOUT ANY WARRANTY; without even the implied warranty of
 
11
  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
 
12
 
 
13
  ----------------------------------------------------------------------------
 
14
}
 
15
 
 
16
{$ifdef read_interface}
 
17
  { List of pointing device sensors. Only nodes descending from
 
18
    X3DPointingDeviceSensorNode, and additionally an Anchor node. }
 
19
  TPointingDeviceSensorList = class(TX3DNodeList)
 
20
  public
 
21
    { Transformation (and inverse) of all the sensors on this list.
 
22
      VRML/X3D specification guarantees that all sensors within a single state
 
23
      have the same transform. }
 
24
    Transform, InvertedTransform: TMatrix4Single;
 
25
 
 
26
    function EnabledCount: Integer;
 
27
    function Enabled(Index: Integer): boolean;
 
28
 
 
29
    procedure Assign(Source: TPointingDeviceSensorList);
 
30
  end;
 
31
 
 
32
  { }
 
33
  TAbstractPointingDeviceSensorNode = class(TAbstractSensorNode)
 
34
  public
 
35
    procedure CreateNode; override;
 
36
 
 
37
    private FFdDescription: TSFString;
 
38
    public property FdDescription: TSFString read FFdDescription;
 
39
 
 
40
    { Event: SFBool, out } { }
 
41
    private FEventIsOver: TX3DEvent;
 
42
    public property EventIsOver: TX3DEvent read FEventIsOver;
 
43
 
 
44
    { Activate pointing device sensor. Used by the events engine
 
45
      (like TCastleSceneCore) to notify this sensor.
 
46
 
 
47
      OverPoint indicates 3D point (in global, that is TCastleSceneCore, coordinates)
 
48
      pointed by the mouse.
 
49
 
 
50
      In TAbstractPointingDeviceSensorNode class,
 
51
      this simply sends isActive := TRUE event. }
 
52
    procedure Activate(const Time: TX3DTime;
 
53
      const ATransform, AInvertedTransform: TMatrix4Single;
 
54
      const OverPoint: TVector3Single); virtual;
 
55
 
 
56
    { Deactivate pointing device sensor. Used by the events engine
 
57
      (like TCastleSceneCore) to notify this sensor.
 
58
 
 
59
      In TAbstractPointingDeviceSensorNode class,
 
60
      this simply sends isActive := FALSE event. }
 
61
    procedure Deactivate(const Time: TX3DTime); virtual;
 
62
  end;
 
63
 
 
64
  TAbstractDragSensorNode = class(TAbstractPointingDeviceSensorNode)
 
65
  public
 
66
    procedure CreateNode; override;
 
67
 
 
68
    private FFdAutoOffset: TSFBool;
 
69
    public property FdAutoOffset: TSFBool read FFdAutoOffset;
 
70
 
 
71
    { Event: SFVec3f, out } { }
 
72
    private FEventTrackPoint_changed: TX3DEvent;
 
73
    public property EventTrackPoint_changed: TX3DEvent read FEventTrackPoint_changed;
 
74
 
 
75
    { Called by events engine (like TCastleSceneCore) when you move your mouse
 
76
      over the @italic(active) drag sensor. }
 
77
    procedure Drag(const Time: TX3DTime; const RayOrigin, RayDirection: TVector3Single); virtual;
 
78
  end;
 
79
 
 
80
  TAbstractTouchSensorNode = class(TAbstractPointingDeviceSensorNode)
 
81
  public
 
82
    procedure CreateNode; override;
 
83
 
 
84
    { Event: SFTime, out } { }
 
85
    private FEventTouchTime: TX3DEvent;
 
86
    public property EventTouchTime: TX3DEvent read FEventTouchTime;
 
87
  end;
 
88
 
 
89
  TCylinderSensorNode = class(TAbstractDragSensorNode)
 
90
  private
 
91
    { Are we in caps mode or cylinder mode (determined at activation) }
 
92
    Caps: boolean;
 
93
    { If Caps, then this is a plane parallel to Y=0 and coincident with
 
94
      original intersection, in global coordinates }
 
95
    CapsPlane: TVector4Single;
 
96
 
 
97
    { If not Caps, then these describe (infinitely tall) cylinder
 
98
      used for dragging, in global coordinates }
 
99
    CylinderAxisOrigin, CylinderAxis: TVector3Single;
 
100
    CylinderRadius: Single;
 
101
 
 
102
    { First intersection (at activation), in local coordinates,
 
103
      additionally with Y component set to 0. }
 
104
    OriginalIntersection: TVector3Single;
 
105
 
 
106
    { Transformation from global coords to the local sensor coords. }
 
107
    InvertedTransform: TMatrix4Single;
 
108
 
 
109
    { Was a rotation_changed send during this activation of sensor. }
 
110
    WasRotation: boolean;
 
111
    { Last value of rotation_changed send, meaningful only if WasRotation. }
 
112
    RotationAngle: Single;
 
113
  public
 
114
    procedure CreateNode; override;
 
115
    class function ClassNodeTypeName: string; override;
 
116
    class function URNMatching(const URN: string): boolean; override;
 
117
 
 
118
    private FFdAxisRotation: TSFRotation;
 
119
    public property FdAxisRotation: TSFRotation read FFdAxisRotation;
 
120
 
 
121
    private FFdDiskAngle: TSFFloat;
 
122
    public property FdDiskAngle: TSFFloat read FFdDiskAngle;
 
123
 
 
124
    private FFdMaxAngle: TSFFloat;
 
125
    public property FdMaxAngle: TSFFloat read FFdMaxAngle;
 
126
 
 
127
    private FFdMinAngle: TSFFloat;
 
128
    public property FdMinAngle: TSFFloat read FFdMinAngle;
 
129
 
 
130
    private FFdOffset: TSFFloat;
 
131
    public property FdOffset: TSFFloat read FFdOffset;
 
132
 
 
133
    { Event: SFRotation, out } { }
 
134
    private FEventRotation_changed: TX3DEvent;
 
135
    public property EventRotation_changed: TX3DEvent read FEventRotation_changed;
 
136
 
 
137
    procedure Activate(const Time: TX3DTime;
 
138
      const ATransform, AInvertedTransform: TMatrix4Single;
 
139
      const OverPoint: TVector3Single); override;
 
140
    procedure Deactivate(const Time: TX3DTime); override;
 
141
    procedure Drag(const Time: TX3DTime; const RayOrigin, RayDirection: TVector3Single); override;
 
142
  end;
 
143
 
 
144
  TPlaneSensorNode = class(TAbstractDragSensorNode)
 
145
  private
 
146
    { Plane* stuff is in global (VRML scene) coordinates. }
 
147
    { Plane of the activated sensor. }
 
148
    Plane: TVector4Single;
 
149
    { Vectors indicating X and Y axis on the plane.
 
150
      Always normalized (do not really have to be strictly orthogonal,
 
151
      in case plane was transformed by some shearing; this is Ok). }
 
152
    PlaneX, PlaneY: TVector3Single;
 
153
    { Initial intersection point at activation. Always lies on Plane. }
 
154
    PlaneOrigin: TVector3Single;
 
155
 
 
156
    { Transformation from global coords to the local sensor coords. }
 
157
    InvertedTransform: TMatrix4Single;
 
158
 
 
159
    { Was a translation_changed send during this activation of sensor. }
 
160
    WasTranslation: boolean;
 
161
    { Last value of translation_changed send, meaningful only if WasTranslation. }
 
162
    Translation: TVector3Single;
 
163
  public
 
164
    procedure CreateNode; override;
 
165
    class function ClassNodeTypeName: string; override;
 
166
    class function URNMatching(const URN: string): boolean; override;
 
167
 
 
168
    private FFdAxisRotation: TSFRotation;
 
169
    public property FdAxisRotation: TSFRotation read FFdAxisRotation;
 
170
 
 
171
    private FFdMaxPosition: TSFVec2f;
 
172
    public property FdMaxPosition: TSFVec2f read FFdMaxPosition;
 
173
 
 
174
    private FFdMinPosition: TSFVec2f;
 
175
    public property FdMinPosition: TSFVec2f read FFdMinPosition;
 
176
 
 
177
    private FFdOffset: TSFVec3f;
 
178
    public property FdOffset: TSFVec3f read FFdOffset;
 
179
 
 
180
    { Event: SFVec3f, out } { }
 
181
    private FEventTranslation_changed: TX3DEvent;
 
182
    public property EventTranslation_changed: TX3DEvent read FEventTranslation_changed;
 
183
 
 
184
    procedure Activate(const Time: TX3DTime;
 
185
      const ATransform, AInvertedTransform: TMatrix4Single;
 
186
      const OverPoint: TVector3Single); override;
 
187
    procedure Deactivate(const Time: TX3DTime); override;
 
188
    procedure Drag(const Time: TX3DTime; const RayOrigin, RayDirection: TVector3Single); override;
 
189
  end;
 
190
 
 
191
  TSphereSensorNode = class(TAbstractDragSensorNode)
 
192
  private
 
193
    { Sphere center and radius, in world coordinates. }
 
194
    SphereCenter: TVector3Single;
 
195
    SphereRadius: Single;
 
196
    { First intersection (at activation), in local coordinates. }
 
197
    OriginalIntersection: TVector3Single;
 
198
 
 
199
    { Transformation from global coords to the local sensor coords. }
 
200
    InvertedTransform: TMatrix4Single;
 
201
 
 
202
    { Was a rotation_changed send during this activation of sensor. }
 
203
    WasRotation: boolean;
 
204
    { Last value of rotation_changed send, meaningful only if WasRotation. }
 
205
    Rotation: TVector4Single;
 
206
  public
 
207
    procedure CreateNode; override;
 
208
    class function ClassNodeTypeName: string; override;
 
209
    class function URNMatching(const URN: string): boolean; override;
 
210
 
 
211
    private FFdOffset: TSFRotation;
 
212
    public property FdOffset: TSFRotation read FFdOffset;
 
213
 
 
214
    { Event: SFRotation, out } { }
 
215
    private FEventRotation_changed: TX3DEvent;
 
216
    public property EventRotation_changed: TX3DEvent read FEventRotation_changed;
 
217
 
 
218
    procedure Activate(const Time: TX3DTime;
 
219
      const ATransform, AInvertedTransform: TMatrix4Single;
 
220
      const OverPoint: TVector3Single); override;
 
221
    procedure Deactivate(const Time: TX3DTime); override;
 
222
    procedure Drag(const Time: TX3DTime; const RayOrigin, RayDirection: TVector3Single); override;
 
223
  end;
 
224
 
 
225
  TTouchSensorNode = class(TAbstractTouchSensorNode)
 
226
  public
 
227
    procedure CreateNode; override;
 
228
    class function ClassNodeTypeName: string; override;
 
229
    class function URNMatching(const URN: string): boolean; override;
 
230
 
 
231
    { Event: SFVec3f, out } { }
 
232
    private FEventHitNormal_changed: TX3DEvent;
 
233
    public property EventHitNormal_changed: TX3DEvent read FEventHitNormal_changed;
 
234
 
 
235
    { Event: SFVec3f, out } { }
 
236
    private FEventHitPoint_changed: TX3DEvent;
 
237
    public property EventHitPoint_changed: TX3DEvent read FEventHitPoint_changed;
 
238
 
 
239
    { Event: SFVec2f, out } { }
 
240
    private FEventHitTexCoord_changed: TX3DEvent;
 
241
    public property EventHitTexCoord_changed: TX3DEvent read FEventHitTexCoord_changed;
 
242
  end;
 
243
 
 
244
{$endif read_interface}
 
245
 
 
246
{$ifdef read_implementation}
 
247
 
 
248
{ TPointingDeviceSensorList ------------------------------------------------- }
 
249
 
 
250
function TPointingDeviceSensorList.EnabledCount: Integer;
 
251
var
 
252
  I: Integer;
 
253
begin
 
254
  Result := 0;
 
255
  for I := 0 to Count - 1 do
 
256
    if Enabled(I) then
 
257
      Inc(Result);
 
258
end;
 
259
 
 
260
function TPointingDeviceSensorList.Enabled(Index: Integer): boolean;
 
261
begin
 
262
  Result := (not (Items[Index] is TAbstractPointingDeviceSensorNode)) or
 
263
    TAbstractPointingDeviceSensorNode(Items[Index]).FdEnabled.Value;
 
264
end;
 
265
 
 
266
procedure TPointingDeviceSensorList.Assign(Source: TPointingDeviceSensorList);
 
267
begin
 
268
  inherited Assign(Source);
 
269
  Transform := Source.Transform;
 
270
  InvertedTransform := Source.InvertedTransform;
 
271
end;
 
272
 
 
273
{ Rest of nodes -------------------------------------------------------------- }
 
274
 
 
275
procedure TAbstractPointingDeviceSensorNode.CreateNode;
 
276
begin
 
277
  inherited;
 
278
 
 
279
  FFdDescription := TSFString.Create(Self, 'description', '');
 
280
  Fields.Add(FFdDescription);
 
281
 
 
282
  FEventIsOver := TX3DEvent.Create(Self, 'isOver', TSFBool, false);
 
283
  Events.Add(FEventIsOver);
 
284
end;
 
285
 
 
286
procedure TAbstractPointingDeviceSensorNode.Activate(const Time: TX3DTime;
 
287
  const ATransform, AInvertedTransform: TMatrix4Single;
 
288
  const OverPoint: TVector3Single);
 
289
begin
 
290
  EventIsActive.Send(true, Time);
 
291
end;
 
292
 
 
293
procedure TAbstractPointingDeviceSensorNode.Deactivate(const Time: TX3DTime);
 
294
begin
 
295
  EventIsActive.Send(false, Time);
 
296
end;
 
297
 
 
298
procedure TAbstractDragSensorNode.CreateNode;
 
299
begin
 
300
  inherited;
 
301
 
 
302
  FFdAutoOffset := TSFBool.Create(Self, 'autoOffset', true);
 
303
  Fields.Add(FFdAutoOffset);
 
304
 
 
305
  FEventTrackPoint_changed := TX3DEvent.Create(Self, 'trackPoint_changed', TSFVec3f, false);
 
306
  Events.Add(FEventTrackPoint_changed);
 
307
 
 
308
  Fdenabled.ChangesAlways := FdEnabled.ChangesAlways + [chDragSensorEnabled];
 
309
end;
 
310
 
 
311
procedure TAbstractDragSensorNode.Drag(const Time: TX3DTime; const RayOrigin, RayDirection: TVector3Single);
 
312
begin
 
313
  { Nothing to do in this class }
 
314
end;
 
315
 
 
316
procedure TAbstractTouchSensorNode.CreateNode;
 
317
begin
 
318
  inherited;
 
319
 
 
320
  FEventTouchTime := TX3DEvent.Create(Self, 'touchTime', TSFTime, false);
 
321
  Events.Add(FEventTouchTime);
 
322
end;
 
323
 
 
324
procedure TCylinderSensorNode.CreateNode;
 
325
begin
 
326
  inherited;
 
327
 
 
328
  FFdAxisRotation := TSFRotation.Create(Self, 'axisRotation', Vector3Single(0, 1, 0), 0);
 
329
  Fields.Add(FFdAxisRotation);
 
330
 
 
331
  FFdDiskAngle := TSFFloat.Create(Self, 'diskAngle', Pi/12);
 
332
  Fields.Add(FFdDiskAngle);
 
333
  { X3D specification comment: [0,Pi/2] }
 
334
 
 
335
  FFdMaxAngle := TSFFloat.Create(Self, 'maxAngle', -1);
 
336
  Fields.Add(FFdMaxAngle);
 
337
  { X3D specification comment: [-2Pi,2Pi] }
 
338
 
 
339
  FFdMinAngle := TSFFloat.Create(Self, 'minAngle', 0);
 
340
  Fields.Add(FFdMinAngle);
 
341
  { X3D specification comment: [-2Pi,2Pi] }
 
342
 
 
343
  FFdOffset := TSFFloat.Create(Self, 'offset', 0);
 
344
  Fields.Add(FFdOffset);
 
345
  { X3D specification comment: (-Inf,Inf) }
 
346
 
 
347
  FEventRotation_changed := TX3DEvent.Create(Self, 'rotation_changed', TSFRotation, false);
 
348
  Events.Add(FEventRotation_changed);
 
349
 
 
350
  DefaultContainerField := 'children';
 
351
end;
 
352
 
 
353
class function TCylinderSensorNode.ClassNodeTypeName: string;
 
354
begin
 
355
  Result := 'CylinderSensor';
 
356
end;
 
357
 
 
358
class function TCylinderSensorNode.URNMatching(const URN: string): boolean;
 
359
begin
 
360
  Result := (inherited URNMatching(URN)) or
 
361
    (URN = URNVRML97Nodes + ClassNodeTypeName) or
 
362
    (URN = URNX3DNodes + ClassNodeTypeName);
 
363
end;
 
364
 
 
365
procedure TCylinderSensorNode.Activate(const Time: TX3DTime;
 
366
  const ATransform, AInvertedTransform: TMatrix4Single;
 
367
  const OverPoint: TVector3Single);
 
368
var
 
369
  Transform, M, IM: TMatrix4Single;
 
370
  CapsPlaneDir: PVector3Single;
 
371
begin
 
372
  inherited;
 
373
 
 
374
  Transform := ATransform;
 
375
  InvertedTransform := AInvertedTransform;
 
376
 
 
377
  { Do not apply rotation when it's 0. A small optimization for a common case. }
 
378
  if FdAxisRotation.RotationRad <> 0 then
 
379
  begin
 
380
    RotationMatricesRad(FdAxisRotation.RotationRad, FdAxisRotation.Axis, M, IM);
 
381
    Transform := MatrixMult(Transform, M);
 
382
    InvertedTransform := MatrixMult(IM, InvertedTransform);
 
383
  end;
 
384
 
 
385
  OriginalIntersection := MatrixMultPoint(InvertedTransform, OverPoint);
 
386
  Caps := AngleRadBetweenVectors(OriginalIntersection,
 
387
    Vector3Single(0, 1, 0)) < FdDiskAngle.Value;
 
388
 
 
389
  OriginalIntersection[1] := 0;
 
390
 
 
391
  if Caps then
 
392
  begin
 
393
    { CapsPlane is parallel to Y=0 plane
 
394
      (transformed by current sensor transform and by axisRotation),
 
395
      and passing though PlaneOrigin. }
 
396
    CapsPlaneDir := PVector3Single(@CapsPlane);
 
397
    CapsPlaneDir^ := MatrixMultDirection(Transform, UnitVector3Single[1]);
 
398
    CapsPlane[3] := -VectorDotProduct(CapsPlaneDir^, OverPoint);
 
399
  end else
 
400
  begin
 
401
    CylinderAxisOrigin := MatrixMultPoint(Transform, ZeroVector3Single);
 
402
    CylinderAxis := MatrixMultDirection(Transform, UnitVector3Single[1]);
 
403
    CylinderRadius := Sqrt(PointToLineDistanceSqr(
 
404
      CylinderAxisOrigin, CylinderAxis, OverPoint));
 
405
  end;
 
406
 
 
407
  WasRotation := false;
 
408
end;
 
409
 
 
410
procedure TCylinderSensorNode.Deactivate(const Time: TX3DTime);
 
411
begin
 
412
  if FdAutoOffset.Value and WasRotation then
 
413
    FdOffset.Send(RotationAngle);
 
414
 
 
415
  inherited;
 
416
end;
 
417
 
 
418
procedure TCylinderSensorNode.Drag(const Time: TX3DTime; const RayOrigin, RayDirection: TVector3Single);
 
419
var
 
420
  Intersection, LocalIntersection: TVector3Single;
 
421
  WasIntersection: boolean;
 
422
begin
 
423
  inherited;
 
424
 
 
425
  if Caps then
 
426
    WasIntersection := TryPlaneRayIntersection(Intersection,
 
427
      CapsPlane, RayOrigin, RayDirection) else
 
428
    WasIntersection := TryCylinderRayIntersection(Intersection,
 
429
      CylinderAxisOrigin, CylinderAxis, CylinderRadius, RayOrigin, RayDirection);
 
430
 
 
431
  if WasIntersection then
 
432
  begin
 
433
    try
 
434
      LocalIntersection := MatrixMultPoint(InvertedTransform, Intersection);
 
435
    except
 
436
      on ETransformedResultInvalid do
 
437
      begin
 
438
        if Log then
 
439
          WritelnLog('Drag sensor', 'Sensor transformation matrix^-1 cannot transform points.');
 
440
        Exit;
 
441
      end;
 
442
    end;
 
443
    EventTrackPoint_changed.Send(LocalIntersection, Time);
 
444
 
 
445
    { both OriginalIntersection and LocalIntersection have Y component set to 0
 
446
      for the sake of calculating RotationAngle. They are both assumed to lie
 
447
      nicely on a CapsPlane. }
 
448
    LocalIntersection[1] := 0;
 
449
 
 
450
    RotationAngle := RotationAngleRadBetweenVectors(
 
451
      OriginalIntersection, LocalIntersection, UnitVector3Single[1]);
 
452
 
 
453
    { add offset, and clamp by min/maxAngle }
 
454
    RotationAngle += FdOffset.Value;
 
455
    if FdMinAngle.Value <= FdMaxAngle.Value then
 
456
      Clamp(RotationAngle, FdMinAngle.Value, FdMaxAngle.Value);
 
457
 
 
458
    EventRotation_changed.Send(Vector4Single(0, 1, 0, RotationAngle), Time);
 
459
    WasRotation := true;
 
460
  end;
 
461
end;
 
462
 
 
463
procedure TPlaneSensorNode.CreateNode;
 
464
begin
 
465
  inherited;
 
466
 
 
467
  FFdAxisRotation := TSFRotation.Create(Self, 'axisRotation', Vector3Single(0, 0, 1), 0);
 
468
  Fields.Add(FFdAxisRotation);
 
469
 
 
470
  FFdMaxPosition := TSFVec2f.Create(Self, 'maxPosition', Vector2Single(-1, -1));
 
471
  Fields.Add(FFdMaxPosition);
 
472
  { X3D specification comment: (-Inf,Inf) }
 
473
 
 
474
  FFdMinPosition := TSFVec2f.Create(Self, 'minPosition', Vector2Single(0, 0));
 
475
  Fields.Add(FFdMinPosition);
 
476
  { X3D specification comment: (-Inf,Inf) }
 
477
 
 
478
  FFdOffset := TSFVec3f.Create(Self, 'offset', Vector3Single(0, 0, 0));
 
479
  Fields.Add(FFdOffset);
 
480
  { X3D specification comment: (-Inf,Inf) }
 
481
 
 
482
  FEventTranslation_changed := TX3DEvent.Create(Self, 'translation_changed', TSFVec3f, false);
 
483
  Events.Add(FEventTranslation_changed);
 
484
 
 
485
  DefaultContainerField := 'children';
 
486
end;
 
487
 
 
488
class function TPlaneSensorNode.ClassNodeTypeName: string;
 
489
begin
 
490
  Result := 'PlaneSensor';
 
491
end;
 
492
 
 
493
class function TPlaneSensorNode.URNMatching(const URN: string): boolean;
 
494
begin
 
495
  Result := (inherited URNMatching(URN)) or
 
496
    (URN = URNVRML97Nodes + ClassNodeTypeName) or
 
497
    (URN = URNX3DNodes + ClassNodeTypeName);
 
498
end;
 
499
 
 
500
procedure TPlaneSensorNode.Activate(const Time: TX3DTime;
 
501
  const ATransform, AInvertedTransform: TMatrix4Single;
 
502
  const OverPoint: TVector3Single);
 
503
var
 
504
  PlaneDir: PVector3Single;
 
505
  Transform, M, IM: TMatrix4Single;
 
506
begin
 
507
  inherited;
 
508
 
 
509
  PlaneOrigin := OverPoint;
 
510
 
 
511
  Transform := ATransform;
 
512
  InvertedTransform := AInvertedTransform;
 
513
 
 
514
  { Do not apply rotation when it's 0. A small optimization for a common case. }
 
515
  if FdAxisRotation.RotationRad <> 0 then
 
516
  begin
 
517
    RotationMatricesRad(FdAxisRotation.RotationRad, FdAxisRotation.Axis, M, IM);
 
518
    Transform := MatrixMult(Transform, M);
 
519
    InvertedTransform := MatrixMult(IM, InvertedTransform);
 
520
  end;
 
521
 
 
522
  try
 
523
    { Plane is parallel to Z=0 plane
 
524
      (transformed by current sensor transform and by axisRotation),
 
525
      and passing though PlaneOrigin. }
 
526
    PlaneDir := PVector3Single(@Plane);
 
527
    PlaneDir^ := MatrixMultDirection(Transform, UnitVector3Single[2]);
 
528
    Plane[3] := -VectorDotProduct(PlaneDir^, PlaneOrigin);
 
529
 
 
530
    { +X, +Y vectors, transformed by current sensor transform and
 
531
      by axisRotation, normalized. }
 
532
    PlaneX := Normalized(MatrixMultDirection(Transform, UnitVector3Single[0]));
 
533
    PlaneY := Normalized(MatrixMultDirection(Transform, UnitVector3Single[1]));
 
534
  except
 
535
    on ETransformedResultInvalid do
 
536
    begin
 
537
      { Transform matrix doesn't manage to transform directions.
 
538
        So just assume it's identity, nothing more sensible to do. }
 
539
      Plane := Vector4Single(0, 0, 1, -PlaneOrigin[2]);
 
540
      PlaneX := UnitVector3Single[0];
 
541
      PlaneY := UnitVector3Single[1];
 
542
      Transform := IdentityMatrix4Single;
 
543
      InvertedTransform := IdentityMatrix4Single;
 
544
      if Log then
 
545
        WritelnLog('Drag sensor', 'Sensor transformation matrix cannot transform directions.');
 
546
    end;
 
547
  end;
 
548
 
 
549
  WasTranslation := false;
 
550
end;
 
551
 
 
552
procedure TPlaneSensorNode.Deactivate(const Time: TX3DTime);
 
553
begin
 
554
  if FdAutoOffset.Value and WasTranslation then
 
555
    FdOffset.Send(Translation);
 
556
 
 
557
  inherited;
 
558
end;
 
559
 
 
560
procedure TPlaneSensorNode.Drag(const Time: TX3DTime; const RayOrigin, RayDirection: TVector3Single);
 
561
var
 
562
  Intersection, TranslationGlobal: TVector3Single;
 
563
begin
 
564
  inherited;
 
565
 
 
566
  { If no intersection, it's Ok to do nothing? }
 
567
 
 
568
  if TryPlaneRayIntersection(Intersection, Plane, RayOrigin, RayDirection) then
 
569
  begin
 
570
    try
 
571
      { trackPoint_changed should be in the local sensor coordinates
 
572
        (with axisRotation), without worrying about offset
 
573
        (like for translation_changed).
 
574
 
 
575
        This is sensible:
 
576
        - track point should visualize the underlying geometry of the drag sensor
 
577
          (plane, in this case)
 
578
        - this makes track point sensible also for Sphere/CylinderSensor
 
579
          (where offset is for rotations, and cannot be applied to
 
580
          trackPoint_changed).
 
581
 
 
582
        About using local coordinates:
 
583
        FreeWRL and OpenVRML seems to also do this in sources,
 
584
        and Octaga and InstantReality behavior suggests they do it too.
 
585
 
 
586
        About using local coordinates with axisRotation:
 
587
        That's more difficult, see
 
588
        http://castle-engine.sourceforge.net/x3d_implementation_pointingdevicesensor.php }
 
589
      EventTrackPoint_changed.Send(
 
590
        MatrixMultPoint(InvertedTransform, Intersection), Time);
 
591
    except
 
592
      on ETransformedResultInvalid do
 
593
      begin
 
594
        EventTrackPoint_changed.Send(Intersection, Time);
 
595
        if Log then
 
596
          WritelnLog('Drag sensor', 'Sensor transformation matrix^-1 cannot transform points.');
 
597
      end;
 
598
    end;
 
599
 
 
600
    { An alternative implementation would transform Intersection to
 
601
      local coords (with axisRotation, and with origin at PointOrigin).
 
602
 
 
603
      Then applying the PointOrigin, PlaneX, PlaneY would be much simpler:
 
604
      PointOrigin is just zero, and PlaneX = just (1, 0, 0).
 
605
      So instead of VectorDotProduct(V, PlaneX) just take V[0]. }
 
606
 
 
607
    TranslationGlobal := Intersection - PlaneOrigin;
 
608
 
 
609
    { map TranslationGlobal to the plane local coord system
 
610
      (with axisRotation) }
 
611
    Translation[0] := VectorDotProduct(TranslationGlobal, PlaneX);
 
612
    Translation[1] := VectorDotProduct(TranslationGlobal, PlaneY);
 
613
    Translation[2] := 0;
 
614
 
 
615
    Translation += FdOffset.Value;
 
616
 
 
617
    if FdMinPosition.Value[0] <= FdMaxPosition.Value[0] then
 
618
      Clamp(Translation[0], FdMinPosition.Value[0], FdMaxPosition.Value[0]);
 
619
    if FdMinPosition.Value[1] <= FdMaxPosition.Value[1] then
 
620
      Clamp(Translation[1], FdMinPosition.Value[1], FdMaxPosition.Value[1]);
 
621
 
 
622
    EventTranslation_changed.Send(Translation, Time);
 
623
    WasTranslation := true;
 
624
  end;
 
625
end;
 
626
 
 
627
procedure TSphereSensorNode.CreateNode;
 
628
begin
 
629
  inherited;
 
630
 
 
631
  FFdOffset := TSFRotation.Create(Self, 'offset', Vector3Single(0, 1, 0), 0);
 
632
  Fields.Add(FFdOffset);
 
633
  { X3D specification comment: [-1,1],(-Inf,Inf) }
 
634
 
 
635
  FEventRotation_changed := TX3DEvent.Create(Self, 'rotation_changed', TSFRotation, false);
 
636
  Events.Add(FEventRotation_changed);
 
637
 
 
638
  DefaultContainerField := 'children';
 
639
end;
 
640
 
 
641
class function TSphereSensorNode.ClassNodeTypeName: string;
 
642
begin
 
643
  Result := 'SphereSensor';
 
644
end;
 
645
 
 
646
class function TSphereSensorNode.URNMatching(const URN: string): boolean;
 
647
begin
 
648
  Result := (inherited URNMatching(URN)) or
 
649
    (URN = URNVRML97Nodes + ClassNodeTypeName) or
 
650
    (URN = URNX3DNodes + ClassNodeTypeName);
 
651
end;
 
652
 
 
653
procedure TSphereSensorNode.Activate(const Time: TX3DTime;
 
654
  const ATransform, AInvertedTransform: TMatrix4Single;
 
655
  const OverPoint: TVector3Single);
 
656
begin
 
657
  inherited;
 
658
 
 
659
  InvertedTransform := AInvertedTransform;
 
660
 
 
661
  OriginalIntersection := MatrixMultPoint(AInvertedTransform, OverPoint);
 
662
 
 
663
  SphereCenter := MatrixMultPoint(ATransform, ZeroVector3Single);
 
664
  SphereRadius := PointsDistance(OverPoint, SphereCenter);
 
665
 
 
666
  WasRotation := false;
 
667
end;
 
668
 
 
669
procedure TSphereSensorNode.Deactivate(const Time: TX3DTime);
 
670
begin
 
671
  if FdAutoOffset.Value and WasRotation then
 
672
    FdOffset.Send(Rotation);
 
673
 
 
674
  inherited;
 
675
end;
 
676
 
 
677
procedure TSphereSensorNode.Drag(const Time: TX3DTime; const RayOrigin, RayDirection: TVector3Single);
 
678
var
 
679
  Intersection, LocalIntersection, RotationAxis: TVector3Single;
 
680
  RotationAngle: Single;
 
681
  RotationQ: TQuaternion;
 
682
begin
 
683
  inherited;
 
684
 
 
685
  { If no intersection, it's Ok to do nothing? }
 
686
 
 
687
  if TrySphereRayIntersection(Intersection, SphereCenter, SphereRadius,
 
688
    RayOrigin, RayDirection) then
 
689
  begin
 
690
    try
 
691
      LocalIntersection := MatrixMultPoint(InvertedTransform, Intersection);
 
692
    except
 
693
      on ETransformedResultInvalid do
 
694
      begin
 
695
        if Log then
 
696
          WritelnLog('Drag sensor', 'Sensor transformation matrix^-1 cannot transform points.');
 
697
        Exit;
 
698
      end;
 
699
    end;
 
700
    EventTrackPoint_changed.Send(LocalIntersection, Time);
 
701
 
 
702
    { Rotation always contains offset }
 
703
    RotationQ := QuatFromAxisAngle(FdOffset.Axis, FdOffset.RotationRad);
 
704
 
 
705
    { Add to RotationQ rotation from OriginalIntersection to current. }
 
706
    RotationAxis := OriginalIntersection >< LocalIntersection;
 
707
    if not ZeroVector(RotationAxis) then
 
708
    begin
 
709
      RotationAngle := RotationAngleRadBetweenVectors(
 
710
        OriginalIntersection, LocalIntersection);
 
711
      RotationQ := QuatFromAxisAngle(
 
712
        Normalized(RotationAxis), RotationAngle) * RotationQ;
 
713
    end;
 
714
 
 
715
    Rotation := RotationQ.ToAxisAngle;
 
716
 
 
717
    EventRotation_changed.Send(Rotation, Time);
 
718
    WasRotation := true;
 
719
  end;
 
720
end;
 
721
 
 
722
procedure TTouchSensorNode.CreateNode;
 
723
begin
 
724
  inherited;
 
725
 
 
726
  FEventHitNormal_changed := TX3DEvent.Create(Self, 'hitNormal_changed', TSFVec3f, false);
 
727
  Events.Add(FEventHitNormal_changed);
 
728
 
 
729
  FEventHitPoint_changed := TX3DEvent.Create(Self, 'hitPoint_changed', TSFVec3f, false);
 
730
  Events.Add(FEventHitPoint_changed);
 
731
 
 
732
  FEventHitTexCoord_changed := TX3DEvent.Create(Self, 'hitTexCoord_changed', TSFVec2f, false);
 
733
  Events.Add(FEventHitTexCoord_changed);
 
734
 
 
735
  DefaultContainerField := 'children';
 
736
end;
 
737
 
 
738
class function TTouchSensorNode.ClassNodeTypeName: string;
 
739
begin
 
740
  Result := 'TouchSensor';
 
741
end;
 
742
 
 
743
class function TTouchSensorNode.URNMatching(const URN: string): boolean;
 
744
begin
 
745
  Result := (inherited URNMatching(URN)) or
 
746
    (URN = URNVRML97Nodes + ClassNodeTypeName) or
 
747
    (URN = URNX3DNodes + ClassNodeTypeName);
 
748
end;
 
749
 
 
750
procedure RegisterPointingDeviceSensorNodes;
 
751
begin
 
752
  NodesManager.RegisterNodeClasses([
 
753
    TCylinderSensorNode,
 
754
    TPlaneSensorNode,
 
755
    TSphereSensorNode,
 
756
    TTouchSensorNode
 
757
  ]);
 
758
end;
 
759
 
 
760
{$endif read_implementation}