2
Copyright 2007-2013 Michalis Kamburelis.
4
This file is part of "Castle Game Engine".
6
"Castle Game Engine" is free software; see the file COPYING.txt,
7
included in this distribution, for details about the copyright.
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.
13
----------------------------------------------------------------------------
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)
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;
26
function EnabledCount: Integer;
27
function Enabled(Index: Integer): boolean;
29
procedure Assign(Source: TPointingDeviceSensorList);
33
TAbstractPointingDeviceSensorNode = class(TAbstractSensorNode)
35
procedure CreateNode; override;
37
private FFdDescription: TSFString;
38
public property FdDescription: TSFString read FFdDescription;
40
{ Event: SFBool, out } { }
41
private FEventIsOver: TX3DEvent;
42
public property EventIsOver: TX3DEvent read FEventIsOver;
44
{ Activate pointing device sensor. Used by the events engine
45
(like TCastleSceneCore) to notify this sensor.
47
OverPoint indicates 3D point (in global, that is TCastleSceneCore, coordinates)
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;
56
{ Deactivate pointing device sensor. Used by the events engine
57
(like TCastleSceneCore) to notify this sensor.
59
In TAbstractPointingDeviceSensorNode class,
60
this simply sends isActive := FALSE event. }
61
procedure Deactivate(const Time: TX3DTime); virtual;
64
TAbstractDragSensorNode = class(TAbstractPointingDeviceSensorNode)
66
procedure CreateNode; override;
68
private FFdAutoOffset: TSFBool;
69
public property FdAutoOffset: TSFBool read FFdAutoOffset;
71
{ Event: SFVec3f, out } { }
72
private FEventTrackPoint_changed: TX3DEvent;
73
public property EventTrackPoint_changed: TX3DEvent read FEventTrackPoint_changed;
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;
80
TAbstractTouchSensorNode = class(TAbstractPointingDeviceSensorNode)
82
procedure CreateNode; override;
84
{ Event: SFTime, out } { }
85
private FEventTouchTime: TX3DEvent;
86
public property EventTouchTime: TX3DEvent read FEventTouchTime;
89
TCylinderSensorNode = class(TAbstractDragSensorNode)
91
{ Are we in caps mode or cylinder mode (determined at activation) }
93
{ If Caps, then this is a plane parallel to Y=0 and coincident with
94
original intersection, in global coordinates }
95
CapsPlane: TVector4Single;
97
{ If not Caps, then these describe (infinitely tall) cylinder
98
used for dragging, in global coordinates }
99
CylinderAxisOrigin, CylinderAxis: TVector3Single;
100
CylinderRadius: Single;
102
{ First intersection (at activation), in local coordinates,
103
additionally with Y component set to 0. }
104
OriginalIntersection: TVector3Single;
106
{ Transformation from global coords to the local sensor coords. }
107
InvertedTransform: TMatrix4Single;
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;
114
procedure CreateNode; override;
115
class function ClassNodeTypeName: string; override;
116
class function URNMatching(const URN: string): boolean; override;
118
private FFdAxisRotation: TSFRotation;
119
public property FdAxisRotation: TSFRotation read FFdAxisRotation;
121
private FFdDiskAngle: TSFFloat;
122
public property FdDiskAngle: TSFFloat read FFdDiskAngle;
124
private FFdMaxAngle: TSFFloat;
125
public property FdMaxAngle: TSFFloat read FFdMaxAngle;
127
private FFdMinAngle: TSFFloat;
128
public property FdMinAngle: TSFFloat read FFdMinAngle;
130
private FFdOffset: TSFFloat;
131
public property FdOffset: TSFFloat read FFdOffset;
133
{ Event: SFRotation, out } { }
134
private FEventRotation_changed: TX3DEvent;
135
public property EventRotation_changed: TX3DEvent read FEventRotation_changed;
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;
144
TPlaneSensorNode = class(TAbstractDragSensorNode)
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;
156
{ Transformation from global coords to the local sensor coords. }
157
InvertedTransform: TMatrix4Single;
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;
164
procedure CreateNode; override;
165
class function ClassNodeTypeName: string; override;
166
class function URNMatching(const URN: string): boolean; override;
168
private FFdAxisRotation: TSFRotation;
169
public property FdAxisRotation: TSFRotation read FFdAxisRotation;
171
private FFdMaxPosition: TSFVec2f;
172
public property FdMaxPosition: TSFVec2f read FFdMaxPosition;
174
private FFdMinPosition: TSFVec2f;
175
public property FdMinPosition: TSFVec2f read FFdMinPosition;
177
private FFdOffset: TSFVec3f;
178
public property FdOffset: TSFVec3f read FFdOffset;
180
{ Event: SFVec3f, out } { }
181
private FEventTranslation_changed: TX3DEvent;
182
public property EventTranslation_changed: TX3DEvent read FEventTranslation_changed;
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;
191
TSphereSensorNode = class(TAbstractDragSensorNode)
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;
199
{ Transformation from global coords to the local sensor coords. }
200
InvertedTransform: TMatrix4Single;
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;
207
procedure CreateNode; override;
208
class function ClassNodeTypeName: string; override;
209
class function URNMatching(const URN: string): boolean; override;
211
private FFdOffset: TSFRotation;
212
public property FdOffset: TSFRotation read FFdOffset;
214
{ Event: SFRotation, out } { }
215
private FEventRotation_changed: TX3DEvent;
216
public property EventRotation_changed: TX3DEvent read FEventRotation_changed;
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;
225
TTouchSensorNode = class(TAbstractTouchSensorNode)
227
procedure CreateNode; override;
228
class function ClassNodeTypeName: string; override;
229
class function URNMatching(const URN: string): boolean; override;
231
{ Event: SFVec3f, out } { }
232
private FEventHitNormal_changed: TX3DEvent;
233
public property EventHitNormal_changed: TX3DEvent read FEventHitNormal_changed;
235
{ Event: SFVec3f, out } { }
236
private FEventHitPoint_changed: TX3DEvent;
237
public property EventHitPoint_changed: TX3DEvent read FEventHitPoint_changed;
239
{ Event: SFVec2f, out } { }
240
private FEventHitTexCoord_changed: TX3DEvent;
241
public property EventHitTexCoord_changed: TX3DEvent read FEventHitTexCoord_changed;
244
{$endif read_interface}
246
{$ifdef read_implementation}
248
{ TPointingDeviceSensorList ------------------------------------------------- }
250
function TPointingDeviceSensorList.EnabledCount: Integer;
255
for I := 0 to Count - 1 do
260
function TPointingDeviceSensorList.Enabled(Index: Integer): boolean;
262
Result := (not (Items[Index] is TAbstractPointingDeviceSensorNode)) or
263
TAbstractPointingDeviceSensorNode(Items[Index]).FdEnabled.Value;
266
procedure TPointingDeviceSensorList.Assign(Source: TPointingDeviceSensorList);
268
inherited Assign(Source);
269
Transform := Source.Transform;
270
InvertedTransform := Source.InvertedTransform;
273
{ Rest of nodes -------------------------------------------------------------- }
275
procedure TAbstractPointingDeviceSensorNode.CreateNode;
279
FFdDescription := TSFString.Create(Self, 'description', '');
280
Fields.Add(FFdDescription);
282
FEventIsOver := TX3DEvent.Create(Self, 'isOver', TSFBool, false);
283
Events.Add(FEventIsOver);
286
procedure TAbstractPointingDeviceSensorNode.Activate(const Time: TX3DTime;
287
const ATransform, AInvertedTransform: TMatrix4Single;
288
const OverPoint: TVector3Single);
290
EventIsActive.Send(true, Time);
293
procedure TAbstractPointingDeviceSensorNode.Deactivate(const Time: TX3DTime);
295
EventIsActive.Send(false, Time);
298
procedure TAbstractDragSensorNode.CreateNode;
302
FFdAutoOffset := TSFBool.Create(Self, 'autoOffset', true);
303
Fields.Add(FFdAutoOffset);
305
FEventTrackPoint_changed := TX3DEvent.Create(Self, 'trackPoint_changed', TSFVec3f, false);
306
Events.Add(FEventTrackPoint_changed);
308
Fdenabled.ChangesAlways := FdEnabled.ChangesAlways + [chDragSensorEnabled];
311
procedure TAbstractDragSensorNode.Drag(const Time: TX3DTime; const RayOrigin, RayDirection: TVector3Single);
313
{ Nothing to do in this class }
316
procedure TAbstractTouchSensorNode.CreateNode;
320
FEventTouchTime := TX3DEvent.Create(Self, 'touchTime', TSFTime, false);
321
Events.Add(FEventTouchTime);
324
procedure TCylinderSensorNode.CreateNode;
328
FFdAxisRotation := TSFRotation.Create(Self, 'axisRotation', Vector3Single(0, 1, 0), 0);
329
Fields.Add(FFdAxisRotation);
331
FFdDiskAngle := TSFFloat.Create(Self, 'diskAngle', Pi/12);
332
Fields.Add(FFdDiskAngle);
333
{ X3D specification comment: [0,Pi/2] }
335
FFdMaxAngle := TSFFloat.Create(Self, 'maxAngle', -1);
336
Fields.Add(FFdMaxAngle);
337
{ X3D specification comment: [-2Pi,2Pi] }
339
FFdMinAngle := TSFFloat.Create(Self, 'minAngle', 0);
340
Fields.Add(FFdMinAngle);
341
{ X3D specification comment: [-2Pi,2Pi] }
343
FFdOffset := TSFFloat.Create(Self, 'offset', 0);
344
Fields.Add(FFdOffset);
345
{ X3D specification comment: (-Inf,Inf) }
347
FEventRotation_changed := TX3DEvent.Create(Self, 'rotation_changed', TSFRotation, false);
348
Events.Add(FEventRotation_changed);
350
DefaultContainerField := 'children';
353
class function TCylinderSensorNode.ClassNodeTypeName: string;
355
Result := 'CylinderSensor';
358
class function TCylinderSensorNode.URNMatching(const URN: string): boolean;
360
Result := (inherited URNMatching(URN)) or
361
(URN = URNVRML97Nodes + ClassNodeTypeName) or
362
(URN = URNX3DNodes + ClassNodeTypeName);
365
procedure TCylinderSensorNode.Activate(const Time: TX3DTime;
366
const ATransform, AInvertedTransform: TMatrix4Single;
367
const OverPoint: TVector3Single);
369
Transform, M, IM: TMatrix4Single;
370
CapsPlaneDir: PVector3Single;
374
Transform := ATransform;
375
InvertedTransform := AInvertedTransform;
377
{ Do not apply rotation when it's 0. A small optimization for a common case. }
378
if FdAxisRotation.RotationRad <> 0 then
380
RotationMatricesRad(FdAxisRotation.RotationRad, FdAxisRotation.Axis, M, IM);
381
Transform := MatrixMult(Transform, M);
382
InvertedTransform := MatrixMult(IM, InvertedTransform);
385
OriginalIntersection := MatrixMultPoint(InvertedTransform, OverPoint);
386
Caps := AngleRadBetweenVectors(OriginalIntersection,
387
Vector3Single(0, 1, 0)) < FdDiskAngle.Value;
389
OriginalIntersection[1] := 0;
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);
401
CylinderAxisOrigin := MatrixMultPoint(Transform, ZeroVector3Single);
402
CylinderAxis := MatrixMultDirection(Transform, UnitVector3Single[1]);
403
CylinderRadius := Sqrt(PointToLineDistanceSqr(
404
CylinderAxisOrigin, CylinderAxis, OverPoint));
407
WasRotation := false;
410
procedure TCylinderSensorNode.Deactivate(const Time: TX3DTime);
412
if FdAutoOffset.Value and WasRotation then
413
FdOffset.Send(RotationAngle);
418
procedure TCylinderSensorNode.Drag(const Time: TX3DTime; const RayOrigin, RayDirection: TVector3Single);
420
Intersection, LocalIntersection: TVector3Single;
421
WasIntersection: boolean;
426
WasIntersection := TryPlaneRayIntersection(Intersection,
427
CapsPlane, RayOrigin, RayDirection) else
428
WasIntersection := TryCylinderRayIntersection(Intersection,
429
CylinderAxisOrigin, CylinderAxis, CylinderRadius, RayOrigin, RayDirection);
431
if WasIntersection then
434
LocalIntersection := MatrixMultPoint(InvertedTransform, Intersection);
436
on ETransformedResultInvalid do
439
WritelnLog('Drag sensor', 'Sensor transformation matrix^-1 cannot transform points.');
443
EventTrackPoint_changed.Send(LocalIntersection, Time);
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;
450
RotationAngle := RotationAngleRadBetweenVectors(
451
OriginalIntersection, LocalIntersection, UnitVector3Single[1]);
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);
458
EventRotation_changed.Send(Vector4Single(0, 1, 0, RotationAngle), Time);
463
procedure TPlaneSensorNode.CreateNode;
467
FFdAxisRotation := TSFRotation.Create(Self, 'axisRotation', Vector3Single(0, 0, 1), 0);
468
Fields.Add(FFdAxisRotation);
470
FFdMaxPosition := TSFVec2f.Create(Self, 'maxPosition', Vector2Single(-1, -1));
471
Fields.Add(FFdMaxPosition);
472
{ X3D specification comment: (-Inf,Inf) }
474
FFdMinPosition := TSFVec2f.Create(Self, 'minPosition', Vector2Single(0, 0));
475
Fields.Add(FFdMinPosition);
476
{ X3D specification comment: (-Inf,Inf) }
478
FFdOffset := TSFVec3f.Create(Self, 'offset', Vector3Single(0, 0, 0));
479
Fields.Add(FFdOffset);
480
{ X3D specification comment: (-Inf,Inf) }
482
FEventTranslation_changed := TX3DEvent.Create(Self, 'translation_changed', TSFVec3f, false);
483
Events.Add(FEventTranslation_changed);
485
DefaultContainerField := 'children';
488
class function TPlaneSensorNode.ClassNodeTypeName: string;
490
Result := 'PlaneSensor';
493
class function TPlaneSensorNode.URNMatching(const URN: string): boolean;
495
Result := (inherited URNMatching(URN)) or
496
(URN = URNVRML97Nodes + ClassNodeTypeName) or
497
(URN = URNX3DNodes + ClassNodeTypeName);
500
procedure TPlaneSensorNode.Activate(const Time: TX3DTime;
501
const ATransform, AInvertedTransform: TMatrix4Single;
502
const OverPoint: TVector3Single);
504
PlaneDir: PVector3Single;
505
Transform, M, IM: TMatrix4Single;
509
PlaneOrigin := OverPoint;
511
Transform := ATransform;
512
InvertedTransform := AInvertedTransform;
514
{ Do not apply rotation when it's 0. A small optimization for a common case. }
515
if FdAxisRotation.RotationRad <> 0 then
517
RotationMatricesRad(FdAxisRotation.RotationRad, FdAxisRotation.Axis, M, IM);
518
Transform := MatrixMult(Transform, M);
519
InvertedTransform := MatrixMult(IM, InvertedTransform);
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);
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]));
535
on ETransformedResultInvalid do
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;
545
WritelnLog('Drag sensor', 'Sensor transformation matrix cannot transform directions.');
549
WasTranslation := false;
552
procedure TPlaneSensorNode.Deactivate(const Time: TX3DTime);
554
if FdAutoOffset.Value and WasTranslation then
555
FdOffset.Send(Translation);
560
procedure TPlaneSensorNode.Drag(const Time: TX3DTime; const RayOrigin, RayDirection: TVector3Single);
562
Intersection, TranslationGlobal: TVector3Single;
566
{ If no intersection, it's Ok to do nothing? }
568
if TryPlaneRayIntersection(Intersection, Plane, RayOrigin, RayDirection) then
571
{ trackPoint_changed should be in the local sensor coordinates
572
(with axisRotation), without worrying about offset
573
(like for translation_changed).
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
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.
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);
592
on ETransformedResultInvalid do
594
EventTrackPoint_changed.Send(Intersection, Time);
596
WritelnLog('Drag sensor', 'Sensor transformation matrix^-1 cannot transform points.');
600
{ An alternative implementation would transform Intersection to
601
local coords (with axisRotation, and with origin at PointOrigin).
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]. }
607
TranslationGlobal := Intersection - PlaneOrigin;
609
{ map TranslationGlobal to the plane local coord system
610
(with axisRotation) }
611
Translation[0] := VectorDotProduct(TranslationGlobal, PlaneX);
612
Translation[1] := VectorDotProduct(TranslationGlobal, PlaneY);
615
Translation += FdOffset.Value;
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]);
622
EventTranslation_changed.Send(Translation, Time);
623
WasTranslation := true;
627
procedure TSphereSensorNode.CreateNode;
631
FFdOffset := TSFRotation.Create(Self, 'offset', Vector3Single(0, 1, 0), 0);
632
Fields.Add(FFdOffset);
633
{ X3D specification comment: [-1,1],(-Inf,Inf) }
635
FEventRotation_changed := TX3DEvent.Create(Self, 'rotation_changed', TSFRotation, false);
636
Events.Add(FEventRotation_changed);
638
DefaultContainerField := 'children';
641
class function TSphereSensorNode.ClassNodeTypeName: string;
643
Result := 'SphereSensor';
646
class function TSphereSensorNode.URNMatching(const URN: string): boolean;
648
Result := (inherited URNMatching(URN)) or
649
(URN = URNVRML97Nodes + ClassNodeTypeName) or
650
(URN = URNX3DNodes + ClassNodeTypeName);
653
procedure TSphereSensorNode.Activate(const Time: TX3DTime;
654
const ATransform, AInvertedTransform: TMatrix4Single;
655
const OverPoint: TVector3Single);
659
InvertedTransform := AInvertedTransform;
661
OriginalIntersection := MatrixMultPoint(AInvertedTransform, OverPoint);
663
SphereCenter := MatrixMultPoint(ATransform, ZeroVector3Single);
664
SphereRadius := PointsDistance(OverPoint, SphereCenter);
666
WasRotation := false;
669
procedure TSphereSensorNode.Deactivate(const Time: TX3DTime);
671
if FdAutoOffset.Value and WasRotation then
672
FdOffset.Send(Rotation);
677
procedure TSphereSensorNode.Drag(const Time: TX3DTime; const RayOrigin, RayDirection: TVector3Single);
679
Intersection, LocalIntersection, RotationAxis: TVector3Single;
680
RotationAngle: Single;
681
RotationQ: TQuaternion;
685
{ If no intersection, it's Ok to do nothing? }
687
if TrySphereRayIntersection(Intersection, SphereCenter, SphereRadius,
688
RayOrigin, RayDirection) then
691
LocalIntersection := MatrixMultPoint(InvertedTransform, Intersection);
693
on ETransformedResultInvalid do
696
WritelnLog('Drag sensor', 'Sensor transformation matrix^-1 cannot transform points.');
700
EventTrackPoint_changed.Send(LocalIntersection, Time);
702
{ Rotation always contains offset }
703
RotationQ := QuatFromAxisAngle(FdOffset.Axis, FdOffset.RotationRad);
705
{ Add to RotationQ rotation from OriginalIntersection to current. }
706
RotationAxis := OriginalIntersection >< LocalIntersection;
707
if not ZeroVector(RotationAxis) then
709
RotationAngle := RotationAngleRadBetweenVectors(
710
OriginalIntersection, LocalIntersection);
711
RotationQ := QuatFromAxisAngle(
712
Normalized(RotationAxis), RotationAngle) * RotationQ;
715
Rotation := RotationQ.ToAxisAngle;
717
EventRotation_changed.Send(Rotation, Time);
722
procedure TTouchSensorNode.CreateNode;
726
FEventHitNormal_changed := TX3DEvent.Create(Self, 'hitNormal_changed', TSFVec3f, false);
727
Events.Add(FEventHitNormal_changed);
729
FEventHitPoint_changed := TX3DEvent.Create(Self, 'hitPoint_changed', TSFVec3f, false);
730
Events.Add(FEventHitPoint_changed);
732
FEventHitTexCoord_changed := TX3DEvent.Create(Self, 'hitTexCoord_changed', TSFVec2f, false);
733
Events.Add(FEventHitTexCoord_changed);
735
DefaultContainerField := 'children';
738
class function TTouchSensorNode.ClassNodeTypeName: string;
740
Result := 'TouchSensor';
743
class function TTouchSensorNode.URNMatching(const URN: string): boolean;
745
Result := (inherited URNMatching(URN)) or
746
(URN = URNVRML97Nodes + ClassNodeTypeName) or
747
(URN = URNX3DNodes + ClassNodeTypeName);
750
procedure RegisterPointingDeviceSensorNodes;
752
NodesManager.RegisterNodeClasses([
760
{$endif read_implementation}