11{
2- Copyright 2006-2022 Michalis Kamburelis.
2+ Copyright 2006-2025 Michalis Kamburelis.
33
44 This file is part of "castle-model-viewer".
55
2828
2929interface
3030
31- uses X3DNodes;
31+ uses X3DNodes, CastleSceneCore ;
3232
3333type
34+ { Visualize skeleton inside TSkinNode or THAnimHumanoidNode. }
3435 TSkeletonVisualize = class
3536 strict private
3637 { Assigned only between JointVisualizationBegin/End.
@@ -39,34 +40,43 @@ TSkeletonVisualize = class
3940 SphereGeometry: TSphereNode;
4041 SphereAppearance: TAppearanceNode;
4142 SphereMaterial: TMaterialNode;
43+ FontStyle: TFontStyleNode;
4244 { @groupEnd }
4345 procedure VisualizeHumanoid (Node: TX3DNode);
44- procedure VisualizeTransformation (Node: TX3DNode);
45- procedure JointVisualizationBegin ;
46- procedure JointVisualizationEnd ;
46+ procedure VisualizeHAnimJoint (Node: TX3DNode);
47+ procedure VisualizeSkin (Node: TX3DNode);
48+ procedure VisualizeTransformInSkin (Node: TX3DNode);
49+ procedure JointVisualizationBegin (const Scene: TCastleSceneCore);
50+ procedure JointVisualizationEnd (const Scene: TCastleSceneCore);
4751 { In case of H-Anim Joint, it is relative to humanoid root,
4852 and so should be placed in humanoid root.
4953 In case of other transformations, they are relative to parent transformation,
5054 and so should be placed in parent transformation. }
5155 function JointVisualization (const Joint: TX3DNode): TTransformNode;
56+ procedure MakeShapeTransparent (const Shape: TShapeNode);
5257 public
53- { Set before using VisualizeXxx }
58+ { Set before using VisualizeAllHumanoids or VisualizeAllSkins. }
5459 JointVisualizationSize: Single;
55- { Informatio available after using VisualizeAllHumanoids. }
56- HumanoidsProcessed: Cardinal;
60+
61+ { Information available after using VisualizeAllHumanoids or VisualizeAllSkins.
62+ @groupBegin }
63+ SkinsProcessed: Cardinal;
5764 JointsProcessed: Cardinal;
65+ { @groupEnd }
66+
5867 { Show H-Anim Humanoid joints. }
59- procedure VisualizeAllHumanoids (const Node: TX3DNode);
60- { Show all transformations. }
61- procedure VisualizeAllTransformations (const Node: TX3DNode);
68+ procedure VisualizeAllHumanoids (const Scene: TCastleSceneCore);
69+
70+ { Show Skin joints. }
71+ procedure VisualizeAllSkins (const Scene: TCastleSceneCore);
6272 end ;
6373
6474implementation
6575
66- uses SysUtils,
76+ uses SysUtils, Math,
6777 CastleUtils, X3DFields, CastleStringUtils;
6878
69- procedure TSkeletonVisualize.JointVisualizationBegin ;
79+ procedure TSkeletonVisualize.JointVisualizationBegin ( const Scene: TCastleSceneCore) ;
7080const
7181 MatName = ' HumanoidJointVisualizeMat' ;
7282begin
@@ -86,25 +96,56 @@ procedure TSkeletonVisualize.JointVisualizationBegin;
8696 SphereMaterial := TMaterialNode.Create(MatName);
8797 SphereMaterial.FdTransparency.Value := 0.3 ;
8898 SphereAppearance.FdMaterial.Value := SphereMaterial;
99+
100+ FontStyle := TFontStyleNode.Create;
101+ FontStyle.Size := JointVisualizationSize;
102+
103+ Scene.BeginChangesSchedule;
89104end ;
90105
91- procedure TSkeletonVisualize.JointVisualizationEnd ;
106+ procedure TSkeletonVisualize.JointVisualizationEnd ( const Scene: TCastleSceneCore) ;
92107begin
93108 SphereShape.FreeIfUnused;
94109 SphereShape := nil ;
110+
111+ FontStyle.FreeIfUnused;
112+ FontStyle := nil ;
113+
114+ Scene.EndChangesSchedule;
115+ end ;
116+
117+ procedure TSkeletonVisualize.MakeShapeTransparent (const Shape: TShapeNode);
118+ var
119+ MatInfo: TMaterialInfo;
120+ begin
121+ // create Shape.Appearance if necessary
122+ if Shape.Appearance = nil then
123+ Shape.Appearance := TAppearanceNode.Create;
124+ Assert(Shape.Appearance <> nil );
125+ Shape.Appearance.AlphaMode := amBlend;
126+
127+ // create Shape.Appearance.Material if necessary
128+ if Shape.Appearance.Material = nil then
129+ Shape.Appearance.Material := TMaterialNode.Create;
130+ Assert(Shape.Appearance.Material <> nil );
131+
132+ // make transparency at least 0.5
133+ MatInfo := Shape.Appearance.Material.MaterialInfo;
134+ Assert(MatInfo <> nil );
135+ MatInfo.Transparency := Max(0.5 , MatInfo.Transparency);
95136end ;
96137
97138function TSkeletonVisualize.JointVisualization (const Joint: TX3DNode): TTransformNode;
98139var
99140 TextShape: TShapeNode;
100141 TextGeometry: TTextNode;
101- FontStyle: TFontStyleNode;
102142 CenterRoute: TX3DRoute;
103143 JointCenter: TSFVec3f;
104144 JointName: String;
105145begin
106146 { Handle Joint being of THAnimJointNode or TTransformNode.
107- TODO: Abstract this using TTransformFunctionality in CGE. }
147+ Note: Not using TTransformFunctionality, as we need to differentiate
148+ name getting anyway. }
108149 if Joint is THAnimJointNode then
109150 begin
110151 JointCenter := THAnimJointNode(Joint).FdCenter;
@@ -131,8 +172,6 @@ function TSkeletonVisualize.JointVisualization(const Joint: TX3DNode): TTransfor
131172 TextGeometry.SetText([JointName]);
132173 TextShape.Geometry := TextGeometry;
133174
134- FontStyle := TFontStyleNode.Create;
135- FontStyle.Size := JointVisualizationSize;
136175 TextGeometry.FontStyle := FontStyle;
137176
138177 Result.AddChildren(SphereShape);
@@ -149,82 +188,108 @@ function TSkeletonVisualize.JointVisualization(const Joint: TX3DNode): TTransfor
149188end ;
150189
151190procedure TSkeletonVisualize.VisualizeHumanoid (Node: TX3DNode);
152-
153- { Change shape's material to be transparent.
154- This doesn't guarantee that material is changed, as we don't want to
155- change the way shape is displayed (so we don't add Appearance
156- or Material if they didn't exist etc.) }
157- procedure MakeShapeTransparent (const Shape: TShapeNode);
158- var
159- Mat: TMaterialNode;
160- begin
161- if Shape.Appearance <> nil then
162- begin
163- if (Shape.Appearance.FdMaterial.Value <> nil ) and
164- (Shape.Appearance.FdMaterial.Value is TMaterialNode) then
165- begin
166- Mat := TMaterialNode(Shape.Appearance.FdMaterial.Value );
167- if Mat.FdTransparency.Value = 0 then
168- Mat.FdTransparency.Value := 0.5 ;
169- end ;
170- end ;
171- end ;
172-
173191var
174192 HumanoidNode: THAnimHumanoidNode;
175- Joint: THAnimJointNode;
176- JointVis: TTransformNode;
177193 I: Integer;
178194begin
179195 HumanoidNode := Node as THAnimHumanoidNode;
180- Inc(HumanoidsProcessed );
196+ Inc(SkinsProcessed );
181197
182198 { make all existing skin shapes transparent.
183199 This helps to see joints and their names through }
184200 for I := 0 to HumanoidNode.FdSkin.Count - 1 do
185201 if HumanoidNode.FdSkin[I] is TShapeNode then
186202 MakeShapeTransparent(TShapeNode(HumanoidNode.FdSkin[I]));
203+ end ;
204+
205+ procedure TSkeletonVisualize.VisualizeHAnimJoint (Node: TX3DNode);
206+ var
207+ Joint: THAnimJointNode;
208+ JointVis: TTransformNode;
209+ begin
210+ Joint := Node as THAnimJointNode;
211+
212+ // avoid processing the same Joint many times
213+ if Joint.MetadataBoolean[' JointVisualization' ] then
214+ Exit;
215+ Joint.MetadataBoolean[' JointVisualization' ] := true;
216+
217+ if Joint.Humanoid = nil then
218+ Exit; // cannot visualize, joint not part of HAnimHumanoid
187219
188- { for each joint, add it's visualization }
189- for I := 0 to HumanoidNode.FdJoints.Count - 1 do
190- if HumanoidNode.FdJoints[I] is THAnimJointNode then
191- begin
192- Joint := THAnimJointNode(HumanoidNode.FdJoints[I]);
193- JointVis := JointVisualization(Joint);
194- HumanoidNode.FdSkin.Add(JointVis);
195- end ;
220+ JointVis := JointVisualization(Joint);
221+ Joint.Humanoid.FdSkin.Add(JointVis);
196222end ;
197223
198- procedure TSkeletonVisualize.VisualizeAllHumanoids (const Node: TX3DNode );
224+ procedure TSkeletonVisualize.VisualizeAllHumanoids (const Scene: TCastleSceneCore );
199225begin
200- JointVisualizationBegin;
226+ JointVisualizationBegin(Scene) ;
201227 try
202- Node .EnumerateNodes(THAnimHumanoidNode,
228+ Scene.RootNode .EnumerateNodes(THAnimHumanoidNode,
203229 { $ifdef FPC} @{ $endif} VisualizeHumanoid, false);
204- finally JointVisualizationEnd end ;
205- end ;
206230
231+ { Note: Don't depend on HumanoidNode.FdJoints list for this,
232+ as it became optional around X3D 4.1.
233+ So we need EnumerateNodes call for THAnimJointNode. }
234+ Scene.RootNode.EnumerateNodes(THAnimJointNode,
235+ { $ifdef FPC} @{ $endif} VisualizeHAnimJoint, false);
236+
237+ if (SkinsProcessed <> 0 ) or (JointsProcessed <> 0 ) then
238+ Scene.ChangedAll;
239+ finally JointVisualizationEnd(Scene) end ;
240+ end ;
207241
208- procedure TSkeletonVisualize.VisualizeTransformation (Node: TX3DNode);
242+ procedure TSkeletonVisualize.VisualizeTransformInSkin (Node: TX3DNode);
209243var
210244 Joint, JointVis: TTransformNode;
211245begin
246+ Joint := Node as TTransformNode;
247+
212248 // ignore adding debug visualization to debug visualization
213- if IsPrefix(' JointVisualization' , Node .X3DName, false) then
249+ if IsPrefix(' JointVisualization' , Joint .X3DName, false) then
214250 Exit;
215251
216- Joint := Node as TTransformNode;
252+ // avoid processing the same Joint many times, e.g. because it's in both Skin.skeleton and Skin.joints
253+ if Joint.MetadataBoolean[' JointVisualization' ] then
254+ Exit;
255+ Joint.MetadataBoolean[' JointVisualization' ] := true;
256+
217257 JointVis := JointVisualization(Joint);
218258 Joint.AddChildren(JointVis);
219259end ;
220260
221- procedure TSkeletonVisualize.VisualizeAllTransformations (const Node: TX3DNode);
261+ procedure TSkeletonVisualize.VisualizeSkin (Node: TX3DNode);
262+ var
263+ SkinNode: TSkinNode;
264+ I: Integer;
265+ begin
266+ SkinNode := Node as TSkinNode;
267+ Inc(SkinsProcessed);
268+
269+ for I := 0 to SkinNode.FdShapes.Count - 1 do
270+ if SkinNode.FdShapes[I] is TShapeNode then
271+ MakeShapeTransparent(TShapeNode(SkinNode.FdShapes[I]));
272+
273+ for I := 0 to SkinNode.FdJoints.Count - 1 do
274+ if SkinNode.FdJoints[I] is TTransformNode then
275+ VisualizeTransformInSkin(SkinNode.FdJoints[I]);
276+ end ;
277+
278+ procedure TSkeletonVisualize.VisualizeAllSkins (const Scene: TCastleSceneCore);
222279begin
223- JointVisualizationBegin;
280+ JointVisualizationBegin(Scene) ;
224281 try
225- Node.EnumerateNodes(TTransformNode,
226- { $ifdef FPC} @{ $endif} VisualizeTransformation, false);
227- finally JointVisualizationEnd end ;
282+ Scene.RootNode.EnumerateNodes(TSkinNode,
283+ { $ifdef FPC} @{ $endif} VisualizeSkin, false);
284+
285+ // No need to search for TTransformNode, VisualizeSkin will iterate over joints.
286+ // This is in contrast to VisualizeAllHumanoids, where we need to search
287+ // for THAnimJointNode.
288+
289+ // make sure to call ChangedAll
290+ if (SkinsProcessed <> 0 ) or (JointsProcessed <> 0 ) then
291+ Scene.ChangedAll;
292+ finally JointVisualizationEnd(Scene) end ;
228293end ;
229294
230295end .
0 commit comments