📄 unit1.pas
字号:
{: Verlet cloth simulation and verlet constraints controlled by an
actor's skeleton.<p>
Verlet physics is used to simulate a cloth-like effect on a mesh.
In this demo, the cape mesh is linked to the verlet world and the
verlet nodes control the surface of the mesh. Verlet constraints
define boundaries that the verlet nodes cannot enter.<p>
The skeleton colliders define the verlet constraints for the actor
using simple spheres and capsules (sphere-capped cylinders). The
constraints get updated each frame to match the actor's current
skeleton frame. Using simple primitives to approximate a mesh is
much quicker than determining mesh volume -> verlet interactions.<p>
A dynamic octree is used here to give a little performace boost to
the verlet testing.
}
unit Unit1;
interface
uses
Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs,
StdCtrls, ExtCtrls,
GLScene, GLVectorFileObjects, GLObjects, GLCadencer, GLTexture, GLWin32Viewer,
GLMisc, GLFileSMD, GLFile3DS, GLVerletClothify, GLVerletSkeletonColliders,
GLShadowVolume,
Keyboard, OpenGL1x, VectorGeometry, GeometryBB, JPEG, VerletClasses,
SpatialPartitioning;
type
TForm1 = class(TForm)
GLScene1: TGLScene;
GLSceneViewer1: TGLSceneViewer;
GLMaterialLibrary1: TGLMaterialLibrary;
GLCadencer1: TGLCadencer;
GLCamera1: TGLCamera;
GLActor1: TGLActor;
ActorDummy: TGLDummyCube;
Timer1: TTimer;
OctreeRenderer: TGLDirectOpenGL;
CheckBox_ShowOctree: TCheckBox;
GLLightSource1: TGLLightSource;
GLPlane1: TGLPlane;
GLShadowVolume1: TGLShadowVolume;
Cape: TGLActor;
GLLightSource2: TGLLightSource;
procedure FormCreate(Sender: TObject);
procedure FormDestroy(Sender: TObject);
procedure GLSceneViewer1MouseDown(Sender: TObject;
Button: TMouseButton; Shift: TShiftState; X, Y: Integer);
procedure GLSceneViewer1MouseMove(Sender: TObject; Shift: TShiftState;
X, Y: Integer);
procedure GLCadencer1Progress(Sender: TObject; const deltaTime,
newTime: Double);
procedure Timer1Timer(Sender: TObject);
procedure OctreeRendererRender(var rci: TRenderContextInfo);
private
{ Private declarations }
public
{ Public declarations }
mx,my : integer;
VerletWorld : TVerletWorld;
EdgeDetector : TEdgeDetector;
AirResistance : TVFAirResistance;
end;
var
Form1: TForm1;
implementation
{$R *.dfm}
// Mesh normal recalculation routines
procedure PrepareMeshForNormalsRecalc(BaseMesh: TGLBaseMesh);
var
i, j, k : Integer;
mo : TMeshObject;
fg : TFGVertexNormalTexIndexList;
begin
// update normals
// (not very efficient, could use some work...)
for i:=0 to BaseMesh.MeshObjects.Count-1 do begin
mo:=BaseMesh.MeshObjects[i];
for j:=0 to mo.FaceGroups.Count-1 do begin
if mo.FaceGroups[j] is TFGVertexNormalTexIndexList then begin
fg:=TFGVertexNormalTexIndexList(mo.FaceGroups[j]);
for k := 0 to fg.VertexIndices.Count-1 do begin
fg.NormalIndices.List[k] := fg.VertexIndices.List[k];
end;
end;
end;
end;
BaseMesh.StructureChanged;
end;
procedure RecalcMeshNormals(BaseMesh: TGLBaseMesh);
var
i, j, k : Integer;
mo : TMeshObject;
fg : TFGVertexIndexList;
n : TAffineVector;
begin
// update normals
// (not very efficient, could use some work...)
for i:=0 to BaseMesh.MeshObjects.Count-1 do begin
mo:=BaseMesh.MeshObjects[i];
FillChar(mo.Normals.List[0], SizeOf(TAffineVector)*mo.Normals.Count, 0);
for j:=0 to mo.FaceGroups.Count-1 do begin
if mo.FaceGroups[j] is TFGVertexIndexList then begin
fg:=TFGVertexIndexList(mo.FaceGroups[j]);
k:=0; while k<=fg.VertexIndices.Count-3 do begin
n:=CalcPlaneNormal(mo.Vertices.List[fg.VertexIndices.List[k]],
mo.Vertices.List[fg.VertexIndices.List[k+1]],
mo.Vertices.List[fg.VertexIndices.List[k+2]]);
mo.Normals.TranslateItem(fg.VertexIndices.List[k], n);
mo.Normals.TranslateItem(fg.VertexIndices.List[k+1], n);
mo.Normals.TranslateItem(fg.VertexIndices.List[k+2], n);//}
Inc(k, 3);
end;
end;
end;
mo.Normals.Normalize;
end;
BaseMesh.StructureChanged;
end;
procedure TForm1.FormCreate(Sender: TObject);
var
i : Integer;
begin
Randomize;
SetCurrentDir(ExtractFilePath(Application.ExeName)+'..\..\media');
// Load the actor and animations
GLActor1.LoadFromFile('trinityRAGE.smd');
GLActor1.AddDataFromFile('walk.smd');
GLActor1.Animations[1].MakeSkeletalTranslationStatic;
GLActor1.SwitchToAnimation('walk');
GLActor1.BuildSilhouetteConnectivityData;
// Load the cape
Cape.LoadFromFile('cape.3ds');
Cape.Position.Y:=GLActor1.BoundingSphereRadius*0.5-10;
PrepareMeshForNormalsRecalc(Cape);
Cape.BuildSilhouetteConnectivityData;
// Set up the floor texture and reposition to below the actors feet
with GLPlane1.Material.Texture do begin
Image.LoadFromFile('beigemarble.jpg');
Disabled:=False;
end;
GLPlane1.Position.Y:=-GLActor1.BoundingSphereRadius*0.5;
// Setting up the verlet world using the optional dynamic octree can
// give good perfamnce increases.
VerletWorld := TVerletWorld.Create;
VerletWorld.CreateOctree(
AffineVectorMake(0,0,0),
AffineVectorMake(0,0,0), 10, 6);
VerletWorld.UpdateSpacePartion := uspEveryFrame;
VerletWorld.Iterations := 3;
// 'Clothify' the cape and add it to the verlet world
EdgeDetector:=TEdgeDetector.Create(Cape);
EdgeDetector.ProcessMesh;
EdgeDetector.AddEdgesAsSticks(VerletWorld, 0.15);
EdgeDetector.AddEdgesAsSolidEdges(VerletWorld);
//EdgeDetector.AddOuterEdgesAsSolidEdges(VerletWorld);
// Set up verlet gravity and add the floor as a constraint
with TVFGravity.Create(VerletWorld) do
Gravity:=AffineVectorMake(0,-98.1,0);
with TVCFloor.Create(VerletWorld) do begin
Normal:=GLPlane1.Direction.AsAffineVector;
Location:=VectorAdd(GLPlane1.Position.AsAffineVector,
VectorScale(GLPlane1.Direction.AsAffineVector,0.1));
end;
// Load the skeleton colliders. Skeleton colliders define an
// approximate collision boundary for actors and are controlled
// by the actor's skeleton.
with GLActor1.Skeleton.Colliders do begin
LoadFromFile('trinityRAGE.glsc');
AlignColliders;
end;
// Add the collider's verlet constraints to the verlet world
AddSCVerletConstriantsToVerletWorld(GLActor1.Skeleton.Colliders,VerletWorld);
{AirResistance := TVFAirResistance.Create(VerletWorld);
AirResistance.DragCoeff := 0.001;
AirResistance.WindDirection := AffineVectorMake(0,0,1);
AirResistance.WindMagnitude := 15;
AirResistance.WindChaos := 2;//}
end;
procedure TForm1.FormDestroy(Sender: TObject);
begin
VerletWorld.Free;
end;
procedure TForm1.GLCadencer1Progress(Sender: TObject; const deltaTime,
newTime: Double);
var
i : integer;
begin
// Step the verlet world (this is where the magic happens)
VerletWorld.Progress(deltaTime, newTime);
// Recalculate the cape's normals
RecalcMeshNormals(Cape);
// Cycle the floor texture to make it look like it's moving
GLPlane1.YOffset:=GLPlane1.YOffset-0.25*deltaTime;
if GLPlane1.YOffset<0 then
GLPlane1.YOffset:=GLPlane1.YOffset+1;
// Orbit the light (to show off the pretty shadow volumes)
GLLightSource1.MoveObjectAround(GLActor1,0,-deltaTime*20);
GLLightSource1.PointTo(GLActor1,YHMGVector);
end;
procedure TForm1.OctreeRendererRender(var rci: TRenderContextInfo);
procedure RenderAABB(AABB : TAABB; w, r,g,b : single);
begin
glColor3f(r,g,b);
glLineWidth(w);
glBegin(GL_LINE_STRIP);
glVertex3f(AABB.min[0],AABB.min[1], AABB.min[2]);
glVertex3f(AABB.min[0],AABB.max[1], AABB.min[2]);
glVertex3f(AABB.max[0],AABB.max[1], AABB.min[2]);
glVertex3f(AABB.max[0],AABB.min[1], AABB.min[2]);
glVertex3f(AABB.min[0],AABB.min[1], AABB.min[2]);
glVertex3f(AABB.min[0],AABB.min[1], AABB.max[2]);
glVertex3f(AABB.min[0],AABB.max[1], AABB.max[2]);
glVertex3f(AABB.max[0],AABB.max[1], AABB.max[2]);
glVertex3f(AABB.max[0],AABB.min[1], AABB.max[2]);
glVertex3f(AABB.min[0],AABB.min[1], AABB.max[2]);
glEnd;
glBegin(GL_LINES);
glVertex3f(AABB.min[0],AABB.max[1], AABB.min[2]);
glVertex3f(AABB.min[0],AABB.max[1], AABB.max[2]);
glVertex3f(AABB.max[0],AABB.max[1], AABB.min[2]);
glVertex3f(AABB.max[0],AABB.max[1], AABB.max[2]);
glVertex3f(AABB.max[0],AABB.min[1], AABB.min[2]);
glVertex3f(AABB.max[0],AABB.min[1], AABB.max[2]);
glEnd;
end;
procedure RenderOctreeNode(Node : TSectorNode);
var
i : integer;
AABB : TAABB;
begin
if Node.NoChildren then
begin
AABB := Node.AABB;
if Node.RecursiveLeafCount > 0 then
RenderAABB(AABB, 1, 0, 0, 0)
else
RenderAABB(AABB, 1, 0.8, 0.8, 0.8)//}
end else
begin
for i := 0 to Node.ChildCount-1 do
RenderOctreeNode(Node.Children[i]);
end;
end;
begin
if CheckBox_ShowOctree.Checked then
begin
if VerletWorld.SpacePartition is TOctreeSpacePartition then
begin
glPushAttrib(GL_ENABLE_BIT or GL_CURRENT_BIT or GL_LINE_BIT or GL_COLOR_BUFFER_BIT);
glDisable(GL_LIGHTING);
RenderOctreeNode(TOctreeSpacePartition(VerletWorld.SpacePartition).RootNode);
glPopAttrib;
end;
end;
end;
procedure TForm1.Timer1Timer(Sender: TObject);
begin
Caption := Format('%2.1f FPS',[GLSceneViewer1.FramesPerSecond]);
GLSceneViewer1.ResetPerformanceMonitor;
end;
procedure TForm1.GLSceneViewer1MouseDown(Sender: TObject;
Button: TMouseButton; Shift: TShiftState; X, Y: Integer);
begin
mx:=x;
my:=y;
end;
procedure TForm1.GLSceneViewer1MouseMove(Sender: TObject;
Shift: TShiftState; X, Y: Integer);
begin
if ssLeft in Shift then
GLCamera1.MoveAroundTarget(my-y,mx-x);
mx:=x;
my:=y;
end;
end.
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -