SDF terrain shape

This commit is contained in:
Benjamin Rosseaux 2026-07-11 10:56:07 +02:00
commit bd6dce9479
3 changed files with 339 additions and 177 deletions

View file

@ -24,6 +24,7 @@ Kraft Physics Engine has the following features:
- Optional additional support for speculative contacts as faster and more inaccurate fake continuous collision detection mode.
- Full one-shot contact manifold collision shapes (spheres, capsules, convex hulls, boxes, planes, signed distance fields, and for static geometries also triangle meshes) based on combinations of for-warm-start-simplex-caching-able GJK, Gauss-Map optimized Clipping-SAT and implicit collision algorithms.
- Signed distance field collision shapes: user-implementable implicit surfaces (analytic or heightfield-like, where the local origin may even lie outside the solid), with a dedicated sampling narrow phase (point-feature shapes sample their features against the field, signed distance field bodies sample surface-projected pseudo features against planes and other fields) producing full one-shot manifolds with warm-start-stable per-sample feature ids, plus sphere-tracing ray and sphere casts
- Built-in height field terrain (TKraftSignedDistanceFieldTerrain): a regular grid of height samples as a ready-to-use signed distance field, bilinearly interpolated with analytic gradients and a conservative Lipschitz slope bound, so ray, sphere and shape casts stay correct even on steep terrain; as a continuous surface it has no internal mesh edges to snag on, and it only stores the raw height grid (no triangles, no BVH). For exact triangle terrain collision through the mesh pipeline there is also a TKraftMesh.AddHeightField factory as the alternative
- Optional MPR-based (Minkowski Portal Refinement) incremental persistent contact manifold work mode (see TKraft.PersistentContactManifold boolean), but its usage isn't recommended, because the full one-shot contact manifold work mode is faster, more robust and more tested. It is implemented only as comparison reference (for example for debugging purposes), and for more reasons against incremental persistent contact manifold real usage, see http://media.steampowered.com/apps/valve/2015/DirkGregorius_Contacts.pdf .
- Multiple collision shapes per rigid body without the need for a compound shape
- Broadphase collision detection with a dynamic AABB tree

View file

@ -4,41 +4,14 @@ unit UnitDemoSceneSphereOnSDFTerrain;
interface
uses {$ifdef DebugDraw}
{$ifdef fpc}
GL,
GLext,
{$else}
OpenGL,
{$endif}
{$endif}
Math,
uses Math,
Kraft,
UnitDemoScene;
type { TTerrainSignedDistanceField }
TTerrainSignedDistanceField=class(TKraftSignedDistanceField)
private
fSize:TKraftScalar;
fHeight:TKraftScalar;
fResolution:Int32;
fHalfSize:Double;
fScale:Double;
fInverseScale:Double;
fData:TKraftScalarArray;
public
constructor Create(const APhysics:TKraft;const aSize,aHeight:TKraftScalar;const aResolution:Int32); reintroduce;
destructor Destroy; override;
function GetLocalSignedDistance(const Position:TKraftVector3):TKraftScalar; override;
{$ifdef DebugDraw}
procedure Draw(const WorldTransform,CameraMatrix:TKraftMatrix4x4); override;
{$endif}
end;
TDemoSceneSphereOnSDFTerrain=class(TDemoScene)
type TDemoSceneSphereOnSDFTerrain=class(TDemoScene)
public
RigidBodyFloor:TKraftRigidBody;
TerrainSignedDistanceField:TTerrainSignedDistanceField;
TerrainSignedDistanceField:TKraftSignedDistanceFieldTerrain;
ShapeTerrain:TKraftShapeSignedDistanceField;
RigidBodySphere:TKraftRigidBody;
ShapeSphere:TKraftShapeSphere;
@ -51,159 +24,27 @@ implementation
uses UnitFormMain;
{ TTerrainSignedDistanceField }
constructor TTerrainSignedDistanceField.Create(const APhysics:TKraft;const aSize,aHeight:TKraftScalar;const aResolution:Int32);
var AABB:TKraftAABB;
x,y:Int32;
k1,k2:TKraftScalar;
begin
fSize:=aSize;
fHeight:=aHeight;
fResolution:=aResolution;
fHalfSize:=fSize*0.5;
fScale:=fResolution/fSize;
fInverseScale:=fSize/fResolution;
fData:=nil;
SetLength(fData,fResolution*fResolution);
for y:=0 to fResolution-1 do begin
for x:=0 to fResolution-1 do begin
k1:=sin(x*pi*4/fResolution)*2;
k2:=cos(y*pi*4/fResolution)*2;
fData[(y*fResolution)+x]:=(Min(Max(((((cos(x*pi*k1/fResolution)*sin(y*pi*k2/fResolution)))+(k1*0.5)-(k2*0.5)))*64,-64),32)/64.0)*fHeight;
end;
end;
AABB.Min:=Vector3(-fHalfSize,-fHeight,-fHalfSize);
AABB.Max:=Vector3(fHalfSize,fHeight,fHalfSize);
inherited Create(APhysics,true,@AABB);
end;
destructor TTerrainSignedDistanceField.Destroy;
begin
fData:=nil;
inherited Destroy;
end;
function TTerrainSignedDistanceField.GetLocalSignedDistance(const Position:TKraftVector3):TKraftScalar;
var dx,dy:Double;
ix,iy,nx,ny:Int32;
fx,fy:Single;
begin
dx:=(Position.x+fHalfSize)*fScale;
dy:=(Position.z+fHalfSize)*fScale;
if dx<=0.0 then begin
dx:=0.0;
end else if dx>=fResolution then begin
dx:=fResolution;
end;
if dy<=0.0 then begin
dy:=0.0;
end else if dy>=fResolution then begin
dy:=fResolution;
end;
ix:=trunc(dx);
iy:=trunc(dy);
if ix>=(fResolution-1) then begin
ix:=fResolution-2;
end;
if iy>=(fResolution-1) then begin
iy:=fResolution-2;
end;
fx:=dx-ix;
fy:=dy-iy;
nx:=ix+1;
ny:=iy+1;
if nx>=fResolution then begin
nx:=fResolution-1;
end;
if ny>=fResolution then begin
ny:=fResolution-1;
end;
result:=Position.y-((((fData[(iy*fResolution)+ix]*(1.0-fx))+(fData[(iy*fResolution)+nx]*fx))*(1.0-fy))+
(((fData[(ny*fResolution)+ix]*(1.0-fx))+(fData[(ny*fResolution)+nx]*fx))*fy));
end;
{$ifdef DebugDraw}
procedure TTerrainSignedDistanceField.Draw(const WorldTransform,CameraMatrix:TKraftMatrix4x4);
var ix,iy,nx,ny:Int32;
v0,v1,v2,v3,n:TKraftVector3;
ModelViewMatrix:TKraftMatrix4x4;
begin
glPushMatrix;
glMatrixMode(GL_MODELVIEW);
ModelViewMatrix:=Matrix4x4TermMul(WorldTransform,CameraMatrix);
{$ifdef KraftUseDouble}
glLoadMatrixd(pointer(@ModelViewMatrix));
{$else}
glLoadMatrixf(pointer(@ModelViewMatrix));
{$endif}
if DrawDisplayList=0 then begin
DrawDisplayList:=glGenLists(1);
glNewList(DrawDisplayList,GL_COMPILE);
glBegin(GL_TRIANGLES);
for iy:=0 to fResolution-2 do begin
ny:=iy+1;
if ny>=fResolution then begin
ny:=fResolution-1;
end;
for ix:=0 to fResolution-2 do begin
nx:=ix+1;
if nx>=fResolution then begin
nx:=fResolution-1;
end;
v0.x:=(ix*fInverseScale)-fHalfSize;
v0.y:=fData[(iy*fResolution)+ix];
v0.z:=(iy*fInverseScale)-fHalfSize;
v1.x:=(nx*fInverseScale)-fHalfSize;
v1.y:=fData[(iy*fResolution)+nx];
v1.z:=(iy*fInverseScale)-fHalfSize;
v2.x:=(nx*fInverseScale)-fHalfSize;
v2.y:=fData[(ny*fResolution)+nx];
v2.z:=(ny*fInverseScale)-fHalfSize;
v3.x:=(ix*fInverseScale)-fHalfSize;
v3.y:=fData[(ny*fResolution)+ix];
v3.z:=(ny*fInverseScale)-fHalfSize;
n:=Vector3Norm(Vector3Cross(Vector3Sub(v2,v0),Vector3Sub(v1,v0)));
glNormal3f(n.x,n.y,n.z);
glVertex3f(v0.x,v0.y,v0.z);
glVertex3f(v2.x,v2.y,v2.z);
glVertex3f(v1.x,v1.y,v1.z);
n:=Vector3Norm(Vector3Cross(Vector3Sub(v3,v0),Vector3Sub(v2,v0)));
glNormal3f(n.x,n.y,n.z);
glVertex3f(v0.x,v0.y,v0.z);
glVertex3f(v3.x,v3.y,v3.z);
glVertex3f(v2.x,v2.y,v2.z);
end;
end;
glEnd;
glEndList;
end;
if DrawDisplayList<>0 then begin
glCallList(DrawDisplayList);
end;
glPopMatrix;
end;
{$endif}
constructor TDemoSceneSphereOnSDFTerrain.Create(const AKraftPhysics:TKraft);
const Resolution=128;
Size=512.0;
Height=16.0;
var x,z:Int32;
k1,k2:TKraftScalar;
begin
inherited Create(AKraftPhysics);
RigidBodyFloor:=TKraftRigidBody.Create(KraftPhysics);
RigidBodyFloor.SetRigidBodyType(krbtSTATIC);
TerrainSignedDistanceField:=TTerrainSignedDistanceField.Create(KraftPhysics,512,16,128);
TerrainSignedDistanceField:=TKraftSignedDistanceFieldTerrain.Create(KraftPhysics,Resolution,Resolution,Size,Size);
SignedDistanceFieldGarbageCollector.Add(TerrainSignedDistanceField);
for z:=0 to Resolution-1 do begin
for x:=0 to Resolution-1 do begin
k1:=sin(x*pi*4/Resolution)*2;
k2:=cos(z*pi*4/Resolution)*2;
TerrainSignedDistanceField.Heights[x,z]:=(Min(Max(((((cos(x*pi*k1/Resolution)*sin(z*pi*k2/Resolution)))+(k1*0.5)-(k2*0.5)))*64,-64),32)/64.0)*Height;
end;
end;
TerrainSignedDistanceField.UpdateData;
TerrainSignedDistanceField.Finish;
ShapeTerrain:=TKraftShapeSignedDistanceField.Create(KraftPhysics,RigidBodyFloor,TerrainSignedDistanceField);
ShapeTerrain.Restitution:=0.3;

View file

@ -1,7 +1,7 @@
(******************************************************************************
* KRAFT PHYSICS ENGINE *
******************************************************************************
* Version 2026-07-11-09-53-0000 *
* Version 2026-07-11-10-55-0000 *
******************************************************************************
* zlib license *
*============================================================================*
@ -2192,6 +2192,11 @@ type TKraftForceMode=(kfmForce, // The unit of the force parameter is app
function AddNormal(const aNormal:TKraftVector3;const aUnique:boolean=false):TKraftInt32;
function AddTriangle(const AVertexIndex0,AVertexIndex1,AVertexIndex2:TKraftInt32;const ANormalIndex0:TKraftInt32=-1;const ANormalIndex1:TKraftInt32=-1;ANormalIndex2:TKraftInt32=-1):TKraftInt32;
// Adds a centered regular height field grid (aCountX times aCountZ samples over aSizeX times aSizeZ
// in the local x/z plane, row major with x as the inner axis) as triangles, for when exact triangle
// terrain collision with the mesh pipeline is wanted; the signed distance field based
// TKraftSignedDistanceFieldTerrain is the memory friendlier alternative without internal mesh edges
procedure AddHeightField(const aHeights:TKraftScalarArray;const aCountX,aCountZ:TKraftInt32;const aSizeX,aSizeZ:TKraftScalar);
procedure Load(const AVertices:PKraftVector3;const ACountVertices:TKraftInt32;const ANormals:PKraftVector3;const ACountNormals:TKraftInt32;const AVertexIndices,ANormalIndices:pointer;const ACountIndices:TKraftInt32); overload;
procedure Load(const ASourceData:pointer;const ASourceSize:TKraftInt32); overload;
@ -2350,6 +2355,58 @@ type TKraftForceMode=(kfmForce, // The unit of the force parameter is app
TKraftSignedDistanceFields=array of TKraftSignedDistanceField;
{ TKraftSignedDistanceFieldTerrain }
// Built-in height field terrain as a signed distance field: a centered regular grid of height samples
// over the local x/z plane, bilinearly interpolated, with the solid below the height surface. The
// distance is scaled by a conservative Lipschitz bound over the terrain slope, so that sphere tracing
// (ray casts, sphere casts and shape casts) stays correct even on steep terrain (a plain y minus height
// difference oversteps there), and the gradient comes analytically from the bilinear patch. As a
// continuous surface it has no internal mesh edges, so bodies can't snag on tessellation seams like on
// a triangle mesh terrain. Fill the heights (constructor pointer or the Heights property), then call
// UpdateData (implicit when constructed with data) and Finish.
TKraftSignedDistanceFieldTerrain=class(TKraftSignedDistanceField)
private
fCountX:TKraftInt32;
fCountZ:TKraftInt32;
fSizeX:TKraftScalar;
fSizeZ:TKraftScalar;
fBottomMargin:TKraftScalar;
fHalfSizeX:TKraftScalar;
fHalfSizeZ:TKraftScalar;
fCellSizeX:TKraftScalar;
fCellSizeZ:TKraftScalar;
fInverseCellSizeX:TKraftScalar;
fInverseCellSizeZ:TKraftScalar;
fMinimumHeight:TKraftScalar;
fMaximumHeight:TKraftScalar;
fDistanceScale:TKraftScalar;
fHeights:TKraftScalarArray;
function GetHeight(const aX,aZ:TKraftInt32):TKraftScalar;
procedure SetHeight(const aX,aZ:TKraftInt32;const aValue:TKraftScalar);
procedure SampleHeightGradient(const aX,aZ:TKraftScalar;out aHeight,aGradientX,aGradientZ:TKraftScalar);
public
constructor Create(const aPhysics:TKraft;const aCountX,aCountZ:TKraftInt32;const aSizeX,aSizeZ:TKraftScalar;const aHeights:PKraftScalar=nil;const aBottomMargin:TKraftScalar=1.0;const aIsForStaticRigidBodies:Boolean=true); reintroduce;
destructor Destroy; override;
// Recomputes the height bounds, the Lipschitz distance scale and the AABB; call it after mutating
// heights over the Heights property and before Finish
procedure UpdateData;
function GetTerrainHeight(const aX,aZ:TKraftScalar):TKraftScalar;
function GetLocalSignedDistance(const Position:TKraftVector3):TKraftScalar; override;
function GetLocalSignedDistanceNormalizedGradient(const Position:TKraftVector3):TKraftVector3; override;
function GetLocalSignedDistanceNormal(const Position:TKraftVector3):TKraftVector3; override;
function GetLocalClosestPointTo(const Position:TKraftVector3):TKraftVector3; override;
{$ifdef DebugDraw}
procedure Draw(const WorldTransform,CameraMatrix:TKraftMatrix4x4); override;
{$endif}
property CountX:TKraftInt32 read fCountX;
property CountZ:TKraftInt32 read fCountZ;
property SizeX:TKraftScalar read fSizeX;
property SizeZ:TKraftScalar read fSizeZ;
property MinimumHeight:TKraftScalar read fMinimumHeight;
property MaximumHeight:TKraftScalar read fMaximumHeight;
property Heights[const aX,aZ:TKraftInt32]:TKraftScalar read GetHeight write SetHeight;
end;
PKraftContactPair=^TKraftContactPair;
TKraftShapeOnContactBeginHook=procedure(const ContactPair:PKraftContactPair;const WithShape:TKraftShape) of object;
@ -31911,6 +31968,50 @@ begin
end;
end;
procedure TKraftMesh.AddHeightField(const aHeights:TKraftScalarArray;const aCountX,aCountZ:TKraftInt32;const aSizeX,aSizeZ:TKraftScalar);
var IndexX,IndexZ,CountX,CountZ:TKraftInt32;
HalfSizeX,HalfSizeZ,CellSizeX,CellSizeZ,Height:TKraftScalar;
VertexIndices:array of TKraftInt32;
begin
CountX:=Max(2,aCountX);
CountZ:=Max(2,aCountZ);
HalfSizeX:=aSizeX*0.5;
HalfSizeZ:=aSizeZ*0.5;
CellSizeX:=aSizeX/(CountX-1);
CellSizeZ:=aSizeZ/(CountZ-1);
VertexIndices:=nil;
try
SetLength(VertexIndices,CountX*CountZ);
for IndexZ:=0 to CountZ-1 do begin
for IndexX:=0 to CountX-1 do begin
if ((IndexZ*CountX)+IndexX)<length(aHeights) then begin
Height:=aHeights[(IndexZ*CountX)+IndexX];
end else begin
Height:=0.0;
end;
VertexIndices[(IndexZ*CountX)+IndexX]:=AddVertex(Vector3((IndexX*CellSizeX)-HalfSizeX,Height,(IndexZ*CellSizeZ)-HalfSizeZ),false);
end;
end;
for IndexZ:=0 to CountZ-2 do begin
for IndexX:=0 to CountX-2 do begin
AddTriangle(VertexIndices[(IndexZ*CountX)+IndexX],VertexIndices[((IndexZ+1)*CountX)+IndexX],VertexIndices[(IndexZ*CountX)+IndexX+1]);
AddTriangle(VertexIndices[(IndexZ*CountX)+IndexX+1],VertexIndices[((IndexZ+1)*CountX)+IndexX],VertexIndices[((IndexZ+1)*CountX)+IndexX+1]);
end;
end;
finally
VertexIndices:=nil;
end;
end;
procedure TKraftMesh.Load(const AVertices:PKraftVector3;const ACountVertices:TKraftInt32;const ANormals:PKraftVector3;const ACountNormals:TKraftInt32;const AVertexIndices,ANormalIndices:pointer;const ACountIndices:TKraftInt32);
var i:TKraftInt32;
Triangle:PKraftMeshTriangle;
@ -35336,6 +35437,225 @@ end;
{$endif}
{$endif}
{ TKraftSignedDistanceFieldTerrain }
constructor TKraftSignedDistanceFieldTerrain.Create(const aPhysics:TKraft;const aCountX,aCountZ:TKraftInt32;const aSizeX,aSizeZ:TKraftScalar;const aHeights:PKraftScalar;const aBottomMargin:TKraftScalar;const aIsForStaticRigidBodies:Boolean);
var Index:TKraftInt32;
begin
inherited Create(aPhysics,aIsForStaticRigidBodies,nil);
fCountX:=Max(2,aCountX);
fCountZ:=Max(2,aCountZ);
fSizeX:=aSizeX;
fSizeZ:=aSizeZ;
fBottomMargin:=aBottomMargin;
fHalfSizeX:=fSizeX*0.5;
fHalfSizeZ:=fSizeZ*0.5;
fCellSizeX:=fSizeX/(fCountX-1);
fCellSizeZ:=fSizeZ/(fCountZ-1);
fInverseCellSizeX:=1.0/fCellSizeX;
fInverseCellSizeZ:=1.0/fCellSizeZ;
fHeights:=nil;
SetLength(fHeights,fCountX*fCountZ);
if assigned(aHeights) then begin
for Index:=0 to (fCountX*fCountZ)-1 do begin
fHeights[Index]:=PKraftScalars(pointer(aHeights))^[Index];
end;
UpdateData;
end else begin
for Index:=0 to (fCountX*fCountZ)-1 do begin
fHeights[Index]:=0.0;
end;
fMinimumHeight:=0.0;
fMaximumHeight:=0.0;
fDistanceScale:=1.0;
end;
end;
destructor TKraftSignedDistanceFieldTerrain.Destroy;
begin
fHeights:=nil;
inherited Destroy;
end;
function TKraftSignedDistanceFieldTerrain.GetHeight(const aX,aZ:TKraftInt32):TKraftScalar;
begin
result:=fHeights[(Min(Max(aZ,0),fCountZ-1)*fCountX)+Min(Max(aX,0),fCountX-1)];
end;
procedure TKraftSignedDistanceFieldTerrain.SetHeight(const aX,aZ:TKraftInt32;const aValue:TKraftScalar);
begin
fHeights[(Min(Max(aZ,0),fCountZ-1)*fCountX)+Min(Max(aX,0),fCountX-1)]:=aValue;
end;
procedure TKraftSignedDistanceFieldTerrain.UpdateData;
var IndexX,IndexZ,RowOffset:TKraftInt32;
h00,h10,h01,h11,GradientX,GradientZ,MaximumSlopeSquared,SlopeSquared:TKraftScalar;
begin
// Height bounds
fMinimumHeight:=fHeights[0];
fMaximumHeight:=fHeights[0];
for IndexX:=1 to (fCountX*fCountZ)-1 do begin
if fHeights[IndexX]<fMinimumHeight then begin
fMinimumHeight:=fHeights[IndexX];
end;
if fHeights[IndexX]>fMaximumHeight then begin
fMaximumHeight:=fHeights[IndexX];
end;
end;
// Conservative Lipschitz bound over the terrain slope: the plain height difference y-h(x,z) is no real
// distance on sloped terrain (its gradient magnitude is sqrt(1+slope^2)), so the distance gets scaled by
// the inverse of that bound, which makes it exact on planar patches and a safe lower bound everywhere,
// which is exactly what sphere tracing and conservative advancement need
MaximumSlopeSquared:=0.0;
for IndexZ:=0 to fCountZ-2 do begin
RowOffset:=IndexZ*fCountX;
for IndexX:=0 to fCountX-2 do begin
h00:=fHeights[RowOffset+IndexX];
h10:=fHeights[RowOffset+IndexX+1];
h01:=fHeights[RowOffset+fCountX+IndexX];
h11:=fHeights[RowOffset+fCountX+IndexX+1];
GradientX:=Max(abs(h10-h00),abs(h11-h01))*fInverseCellSizeX;
GradientZ:=Max(abs(h01-h00),abs(h11-h10))*fInverseCellSizeZ;
SlopeSquared:=sqr(GradientX)+sqr(GradientZ);
if SlopeSquared>MaximumSlopeSquared then begin
MaximumSlopeSquared:=SlopeSquared;
end;
end;
end;
fDistanceScale:=1.0/sqrt(1.0+MaximumSlopeSquared);
fAABB.Min:=Vector3(-fHalfSizeX,fMinimumHeight-fBottomMargin,-fHalfSizeZ);
fAABB.Max:=Vector3(fHalfSizeX,fMaximumHeight,fHalfSizeZ);
end;
procedure TKraftSignedDistanceFieldTerrain.SampleHeightGradient(const aX,aZ:TKraftScalar;out aHeight,aGradientX,aGradientZ:TKraftScalar);
var GridX,GridZ:TKraftScalar;
IndexX,IndexZ,RowOffset:TKraftInt32;
FractionX,FractionZ,h00,h10,h01,h11:TKraftScalar;
begin
GridX:=Min(Max((aX+fHalfSizeX)*fInverseCellSizeX,0.0),fCountX-1);
GridZ:=Min(Max((aZ+fHalfSizeZ)*fInverseCellSizeZ,0.0),fCountZ-1);
IndexX:=Min(trunc(GridX),fCountX-2);
IndexZ:=Min(trunc(GridZ),fCountZ-2);
FractionX:=GridX-IndexX;
FractionZ:=GridZ-IndexZ;
RowOffset:=IndexZ*fCountX;
h00:=fHeights[RowOffset+IndexX];
h10:=fHeights[RowOffset+IndexX+1];
h01:=fHeights[RowOffset+fCountX+IndexX];
h11:=fHeights[RowOffset+fCountX+IndexX+1];
aHeight:=(((h00*(1.0-FractionX))+(h10*FractionX))*(1.0-FractionZ))+
(((h01*(1.0-FractionX))+(h11*FractionX))*FractionZ);
aGradientX:=(((h10-h00)*(1.0-FractionZ))+((h11-h01)*FractionZ))*fInverseCellSizeX;
aGradientZ:=(((h01-h00)*(1.0-FractionX))+((h11-h10)*FractionX))*fInverseCellSizeZ;
end;
function TKraftSignedDistanceFieldTerrain.GetTerrainHeight(const aX,aZ:TKraftScalar):TKraftScalar;
var GradientX,GradientZ:TKraftScalar;
begin
SampleHeightGradient(aX,aZ,result,GradientX,GradientZ);
end;
function TKraftSignedDistanceFieldTerrain.GetLocalSignedDistance(const Position:TKraftVector3):TKraftScalar;
var Height,GradientX,GradientZ:TKraftScalar;
begin
SampleHeightGradient(Position.x,Position.z,Height,GradientX,GradientZ);
result:=(Position.y-Height)*fDistanceScale;
end;
function TKraftSignedDistanceFieldTerrain.GetLocalSignedDistanceNormalizedGradient(const Position:TKraftVector3):TKraftVector3;
var Height,GradientX,GradientZ:TKraftScalar;
begin
SampleHeightGradient(Position.x,Position.z,Height,GradientX,GradientZ);
result:=Vector3Norm(Vector3(-GradientX,1.0,-GradientZ));
end;
function TKraftSignedDistanceFieldTerrain.GetLocalSignedDistanceNormal(const Position:TKraftVector3):TKraftVector3;
var Height,GradientX,GradientZ:TKraftScalar;
begin
SampleHeightGradient(Position.x,Position.z,Height,GradientX,GradientZ);
result:=Vector3Norm(Vector3(-GradientX,1.0,-GradientZ));
end;
function TKraftSignedDistanceFieldTerrain.GetLocalClosestPointTo(const Position:TKraftVector3):TKraftVector3;
var Height,GradientX,GradientZ,Distance:TKraftScalar;
Normal:TKraftVector3;
begin
// Distance along the analytic patch normal, exact on planar patches thanks to the Lipschitz scaling
SampleHeightGradient(Position.x,Position.z,Height,GradientX,GradientZ);
Normal:=Vector3Norm(Vector3(-GradientX,1.0,-GradientZ));
Distance:=(Position.y-Height)*fDistanceScale;
result:=Vector3Sub(Position,Vector3ScalarMul(Normal,Distance));
end;
{$ifdef DebugDraw}
procedure TKraftSignedDistanceFieldTerrain.Draw(const WorldTransform,CameraMatrix:TKraftMatrix4x4);
{$ifdef NoOpenGL}
begin
end;
{$else}
var IndexX,IndexZ,RowOffset:TKraftInt32;
v0,v1,v2,v3,n:TKraftVector3;
ModelViewMatrix:TKraftMatrix4x4;
begin
glPushMatrix;
glMatrixMode(GL_MODELVIEW);
ModelViewMatrix:=Matrix4x4TermMul(WorldTransform,CameraMatrix);
{$ifdef KraftUseDouble}
glLoadMatrixd(pointer(@ModelViewMatrix));
{$else}
glLoadMatrixf(pointer(@ModelViewMatrix));
{$endif}
if DrawDisplayList=0 then begin
DrawDisplayList:=glGenLists(1);
glNewList(DrawDisplayList,GL_COMPILE);
glBegin(GL_TRIANGLES);
for IndexZ:=0 to fCountZ-2 do begin
RowOffset:=IndexZ*fCountX;
for IndexX:=0 to fCountX-2 do begin
v0:=Vector3((IndexX*fCellSizeX)-fHalfSizeX,fHeights[RowOffset+IndexX],(IndexZ*fCellSizeZ)-fHalfSizeZ);
v1:=Vector3(((IndexX+1)*fCellSizeX)-fHalfSizeX,fHeights[RowOffset+IndexX+1],(IndexZ*fCellSizeZ)-fHalfSizeZ);
v2:=Vector3(((IndexX+1)*fCellSizeX)-fHalfSizeX,fHeights[RowOffset+fCountX+IndexX+1],((IndexZ+1)*fCellSizeZ)-fHalfSizeZ);
v3:=Vector3((IndexX*fCellSizeX)-fHalfSizeX,fHeights[RowOffset+fCountX+IndexX],((IndexZ+1)*fCellSizeZ)-fHalfSizeZ);
n:=Vector3Norm(Vector3Cross(Vector3Sub(v2,v0),Vector3Sub(v1,v0)));
glNormal3f(n.x,n.y,n.z);
glVertex3f(v0.x,v0.y,v0.z);
glVertex3f(v2.x,v2.y,v2.z);
glVertex3f(v1.x,v1.y,v1.z);
n:=Vector3Norm(Vector3Cross(Vector3Sub(v3,v0),Vector3Sub(v2,v0)));
glNormal3f(n.x,n.y,n.z);
glVertex3f(v0.x,v0.y,v0.z);
glVertex3f(v3.x,v3.y,v3.z);
glVertex3f(v2.x,v2.y,v2.z);
end;
end;
glEnd;
glEndList;
end;
if DrawDisplayList<>0 then begin
glCallList(DrawDisplayList);
end;
glPopMatrix;
end;
{$endif}
{$endif}
constructor TKraftShape.Create(const aPhysics:TKraft;const ARigidBody:TKraftRigidBody);
begin
inherited Create;