Implementation of a circle impulse physics engine simulator

 


unit CBUnit;


interface


uses

  System.SysUtils, System.Types, System.UITypes, System.Classes, System.Variants,  System.Generics.Collections,

  FMX.Types, FMX.Controls, FMX.Forms, FMX.Graphics, FMX.Dialogs, FMX.Controls.Presentation, FMX.StdCtrls, FMX.Objects,

  FMX.Layouts;


type

  TCirclePhysics = record

    VX: Single;

    VY: Single;

    ProtectUntil: Cardinal; // 이 시각까지 생존 보호

  end;


type

  TForm1 = class(TForm)

    Button1: TButton;

    Circle1: TCircle;

    TimerPhysics: TTimer;

    LayoutRoot: TLayout;

    Circle2: TCircle;

    Circle3: TCircle;

    Circle4: TCircle;

    Timer1: TTimer;

    Text1: TText;

    procedure Button1Click(Sender: TObject);

    procedure FormCreate(Sender: TObject);

    procedure FormDestroy(Sender: TObject);

    procedure TimerPhysicsTimer(Sender: TObject);

    procedure Timer1Timer(Sender: TObject);

  private

    FCircleMap: TDictionary<TCircle, TCirclePhysics>;

     FRemoveQueue: TList<TCircle>;


    procedure UpdateCirclePhysics(ACircle: TCircle);

    procedure AddNewCircle(ASpeed: Single);

    procedure RegisterCircle(ACircle: TCircle; ASpeed: Single;  ADirection: Integer);

    procedure CheckCircleCollision(C1, C2: TCircle);

    procedure RemoveCircle(ACircle: TCircle);

    procedure SplitCircleOnWall(ACircle: TCircle; HitVerticalWall, HitHorizontalWall: Boolean);

    procedure Create_SpawnCircle;


  public

    { Public declarations }

    C_Width, C_Height : single;

  end;


var

  Form1: TForm1;


implementation


const

  C_Spped = 20;

  MIN_CIRCLE_SIZE = 40;  // ← 여기만 바꾸면 됨


  SPLIT_SPEED_FACTOR = 0.6;

  WALL_PROTECT_MS = 800; // 0.8초 (추천: 500~1200)

  MIN_AXIS_COMPONENT = 0.35; // 0.3~0.4 권장



{$R *.fmx}


procedure TForm1.Button1Click(Sender: TObject);

begin

  Button1.Visible := FALSE;


  Circle1.Position.X := LayoutRoot.Width / 3;

  Circle2.Position.X := LayoutRoot.Width / 3;

  Circle3.Position.X := LayoutRoot.Width / 3 * 2;

  Circle4.Position.X := LayoutRoot.Width / 3 * 2;


  Circle1.Position.Y := LayoutRoot.Height / 3;

  Circle3.Position.Y := LayoutRoot.Height / 3;

  Circle2.Position.Y := LayoutRoot.Height / 3 * 2;

  Circle4.Position.y := LayoutRoot.Height / 3 * 2;


  RegisterCircle(Circle1, C_Spped, 1 );  // 미리 만들어진 써클

  RegisterCircle(Circle2, C_Spped, 3 );  // 미리 만들어진 써클

  RegisterCircle(Circle3, C_Spped, 2 );  // 미리 만들어진 써클

  RegisterCircle(Circle4, C_Spped, 4 );  // 미리 만들어진 써클



  TimerPhysics.Enabled := True;

  Timer1.Enabled := TRUE;

end;


procedure TForm1.FormCreate(Sender: TObject);

begin

  C_Width  :=  200; //  120 + Random(240 - 120 + 1); // 120 ~ 260

  C_Height :=  C_Width;



  FCircleMap := TDictionary<TCircle, TCirclePhysics>.Create;


  FRemoveQueue := TList<TCircle>.Create;

end;



procedure TForm1.FormDestroy(Sender: TObject);

begin

   FCircleMap.Free;

  FRemoveQueue.Free;

end;


procedure TForm1.RegisterCircle(ACircle: TCircle; ASpeed: Single; ADirection: Integer );

var

  Phys: TCirclePhysics;

begin

  case ADirection of

    1: begin Phys.VX :=  ASpeed; Phys.VY :=  ASpeed; end;

    2: begin Phys.VX := -ASpeed; Phys.VY :=  ASpeed; end;

    3: begin Phys.VX :=  ASpeed; Phys.VY := -ASpeed; end;

    4: begin Phys.VX := -ASpeed; Phys.VY := -ASpeed; end;

  else

    Phys.VX := ASpeed;

    Phys.VY := ASpeed;

  end;


  Phys.ProtectUntil := 0; // ← 보호 초기화


  if FCircleMap.ContainsKey(ACircle) then

    FCircleMap.Remove(ACircle);


  FCircleMap.Add(ACircle, Phys);

end;


//----------------------------------------------------------

procedure TForm1.UpdateCirclePhysics(ACircle: TCircle);

const

  WALL_PUSH = 2.0;              // 벽에서 밀어내는 거리

  WALL_ANGLE_JITTER = 0.05;     // 벽 반사 시 미세 각도 교란 (라디안)

var

  Phys: TCirclePhysics;

  NextX, NextY: Single;

  HitV, HitH: Boolean;

begin

  if not FCircleMap.TryGetValue(ACircle, Phys) then

    Exit;


  HitV := False;

  HitH := False;


  // 다음 위치 예측

  NextX := ACircle.Position.X + Phys.VX;

  NextY := ACircle.Position.Y + Phys.VY;


  // -------- 좌 / 우 벽 --------

  if NextX <= 0 then

  begin

    Phys.VX := Abs(Phys.VX);

    ACircle.Position.X := WALL_PUSH; // 벽 밖으로 밀기

    HitV := True;

  end

  else if (NextX + ACircle.Width) >= LayoutRoot.Width then

  begin

    Phys.VX := -Abs(Phys.VX);

    ACircle.Position.X := LayoutRoot.Width - ACircle.Width - WALL_PUSH;

    HitV := True;

  end

  else

    ACircle.Position.X := NextX;


  // -------- 상 / 하 벽 --------

  if NextY <= 0 then

  begin

    Phys.VY := Abs(Phys.VY);

    ACircle.Position.Y := WALL_PUSH;

    HitH := True;

  end

  else if (NextY + ACircle.Height) >= LayoutRoot.Height then

  begin

    Phys.VY := -Abs(Phys.VY);

    ACircle.Position.Y := LayoutRoot.Height - ACircle.Height - WALL_PUSH;

    HitH := True;

  end

  else

    ACircle.Position.Y := NextY;


  // -------- 벽 반사 후 미세 각도 교란 --------

  if HitV or HitH then

  begin

    Phys.VX := Phys.VX + (Random - 0.5) * WALL_ANGLE_JITTER * Abs(Phys.VY);

    Phys.VY := Phys.VY + (Random - 0.5) * WALL_ANGLE_JITTER * Abs(Phys.VX);


    // 벽 충돌 시 분리 처리

    SplitCircleOnWall(ACircle, HitV, HitH);

  end;


  // 물리 정보 저장

  FCircleMap[ACircle] := Phys;

end;



//------------------------------------------------------------------

procedure TForm1.CheckCircleCollision(C1, C2: TCircle);

var

  P1, P2: TCirclePhysics;

  Dx, Dy: Single;

  Dist, MinDist: Single;

  NX, NY: Single;

  Overlap: Single;

  TempVX, TempVY: Single;

begin

  if not FCircleMap.TryGetValue(C1, P1) then Exit;

  if not FCircleMap.TryGetValue(C2, P2) then Exit;


  // 중심 거리 계산

  Dx := (C2.Position.X + C2.Width * 0.5) -

        (C1.Position.X + C1.Width * 0.5);

  Dy := (C2.Position.Y + C2.Height * 0.5) -

        (C1.Position.Y + C1.Height * 0.5);


  Dist := Sqrt(Dx*Dx + Dy*Dy);

  MinDist := (C1.Width + C2.Width) * 0.5;


  // 충돌 아님

  if (Dist <= 0) or (Dist >= MinDist) then

    Exit;


  // ---------- 1️⃣ 위치 분리 (가장 중요) ----------

  NX := Dx / Dist;

  NY := Dy / Dist;


  Overlap := MinDist - Dist;


  // 두 공을 반씩 밀어냄

  C1.Position.X := C1.Position.X - NX * (Overlap * 0.5);

  C1.Position.Y := C1.Position.Y - NY * (Overlap * 0.5);


  C2.Position.X := C2.Position.X + NX * (Overlap * 0.5);

  C2.Position.Y := C2.Position.Y + NY * (Overlap * 0.5);


  // ---------- 2️⃣ 속도 반사 (단순 교환) ----------

  TempVX := P1.VX;

  TempVY := P1.VY;


  P1.VX := P2.VX;

  P1.VY := P2.VY;


  P2.VX := TempVX;

  P2.VY := TempVY;


  FCircleMap[C1] := P1;

  FCircleMap[C2] := P2;

end;



//*********************************************************************

procedure TForm1.TimerPhysicsTimer(Sender: TObject);

var

  List: TArray<TCircle>;

  I, J: Integer;

  C: TCircle;

begin

  // 1) 스냅샷

  List := FCircleMap.Keys.ToArray;


  // 2) 이동 + 벽 처리

  for I := 0 to High(List) do

    if FCircleMap.ContainsKey(List[I]) then

      UpdateCirclePhysics(List[I]);


  // 3) 공–공 충돌

  for I := 0 to High(List) do

    for J := I + 1 to High(List) do

      if FCircleMap.ContainsKey(List[I]) and

         FCircleMap.ContainsKey(List[J]) then

        CheckCircleCollision(List[I], List[J]);


  //  4) 여기서만 실제 제거

  for C in FRemoveQueue do

  begin

    FCircleMap.Remove(C);

    C.Parent := nil;

    C.Free;

  end;


  FRemoveQueue.Clear;

end;


//-----------------------------------------------------------

// 미사용

procedure TForm1.AddNewCircle(ASpeed: Single);

var

  C: TCircle;

  OuterColor: TAlphaColor;

begin

  C := TCircle.Create(LayoutRoot);

  C.Parent := LayoutRoot;


  // ---- 아래 값들은 질문에 주신 Circle1 값을 그대로 적용 ----

  C.Fill.Kind := TBrushKind.Gradient;

  C.Fill.Gradient.Style := TGradientStyle.Radial;


  C.Width := C_Width;

  C.Height :=C_Height;


  C.Stroke.Kind := TBrushKind.None; // 원본에 Stroke 언급 없으니 일반적으로 없음 처리


  // 위치는 원본값 고정 대신 "생성용"이므로 랜덤

  C.Position.X := Random(Round(LayoutRoot.Width - C.Width));

  C.Position.Y := Random(Round(LayoutRoot.Height - C.Height));


  // ---- Gradient Points 구성 (포인트 '생성'은 필요함) ----

  C.Fill.Gradient.Points.Clear;

  C.Fill.Gradient.Points.Add; // item 0 생성

  C.Fill.Gradient.Points.Add; // item 1 생성


  // 원본 구조 그대로:

  // item0: 바깥색, Offset 0

  // item1: 흰색, Offset 1

  OuterColor :=

    $FF000000 or

    (Random(256) shl 16) or

    (Random(256) shl 8)  or

    Random(256);


  C.Fill.Gradient.Points[0].Offset := 0.0;

  C.Fill.Gradient.Points[0].Color  := OuterColor; //  바깥쪽 랜덤


  C.Fill.Gradient.Points[1].Offset := 1.0;

  C.Fill.Gradient.Points[1].Color  := $FFFFFFFF;


  // 물리 등록

  RegisterCircle(C, ASpeed, 1 ); // 일단 1

end;


//-----------------------------------------------------------------

procedure TForm1.RemoveCircle(ACircle: TCircle);

begin

  if not FRemoveQueue.Contains(ACircle) then

    FRemoveQueue.Add(ACircle);

end;


//--------------------------------------------------------------------------------------------------------------------

procedure TForm1.SplitCircleOnWall( ACircle: TCircle;  HitVerticalWall: Boolean;  HitHorizontalWall: Boolean );

var

  Phys: TCirclePhysics;

  NewCircle: TCircle;

  NewSize: Single;

  Speed: Single;

  Angle: Single;

  DX, DY: Single;

  NowTick: Cardinal;

begin

  // 물리 정보 확인

  if not FCircleMap.TryGetValue(ACircle, Phys) then

    Exit;


  NowTick := TThread.GetTickCount;


  //  보호 중이면 분리 금지

  if NowTick < Phys.ProtectUntil then

    Exit;


  // 최소 크기 도달 → 제거

  if ACircle.Width <= MIN_CIRCLE_SIZE then

  begin

    RemoveCircle(ACircle);

    Exit;

  end;


  // 현재 속도 크기 계산

  Speed := Sqrt(Phys.VX * Phys.VX + Phys.VY * Phys.VY);

  if Speed < 0.01 then

    Speed := 16;


  //  분리 시 감속 (전체 분위기 제어 포인트)

  Speed := Speed * 0.8;  // ← 원하면 0.5 ~ 0.8 조절


  // 크기 절반

  NewSize := ACircle.Width * 0.6;

  ACircle.Width  := NewSize;

  ACircle.Height := NewSize;


   // -------- 랜덤 분리 방향 --------

  repeat

    Angle := Random * 2 * Pi;

    DX := Cos(Angle);

    DY := Sin(Angle);

  until (Abs(DX) > MIN_AXIS_COMPONENT) and

        (Abs(DY) > MIN_AXIS_COMPONENT);


  // -------- 기존 공 --------

  // 위치 분리 (겹침 방지)

  ACircle.Position.X := ACircle.Position.X + DX * NewSize;

  ACircle.Position.Y := ACircle.Position.Y + DY * NewSize;


  // 속도: 방향만 변경, 크기 유지

  Phys.VX :=  Speed * DX;

  Phys.VY :=  Speed * DY;


  // 보호 시작

  Phys.ProtectUntil := NowTick + WALL_PROTECT_MS;

  FCircleMap[ACircle] := Phys;


  // -------- 새 공 --------

  NewCircle := TCircle.Create(LayoutRoot);

  NewCircle.Parent := LayoutRoot;


  NewCircle.Width  := NewSize;

  NewCircle.Height := NewSize;


  // 반대 방향으로 충분히 분리

  NewCircle.Position.X := ACircle.Position.X - DX * NewSize * 2;

  NewCircle.Position.Y := ACircle.Position.Y - DY * NewSize * 2;


  // 외형 유지 (그라디언트 그대로)

  NewCircle.Fill.Assign(ACircle.Fill);

  NewCircle.Stroke.Assign(ACircle.Stroke);


  // 새 공 물리 (정확히 반대 방향)

  Phys.VX := -Speed * DX;

  Phys.VY := -Speed * DY;

  Phys.ProtectUntil := NowTick + WALL_PROTECT_MS;


  FCircleMap.Add(NewCircle, Phys);

end;



//---------------------------------------------------------------

procedure TForm1.Create_SpawnCircle;

var

  C: TCircle;

  OuterColor: TAlphaColor;

begin

  C := TCircle.Create(LayoutRoot);

  C.Parent := LayoutRoot;


  // ---- 크기 ----

  C.Width  := 200;  // 120 + Random(240 - 120 + 1);

  C.Height := C.Width;


  // ---- 위치 (화면 안 랜덤) ----

  C.Position.X := Random(Round(LayoutRoot.Width  - C.Width));

  C.Position.Y := Random(Round(LayoutRoot.Height - C.Height));


  // ---- Gradient (Radial) ----

  C.Fill.Kind := TBrushKind.Gradient;

  C.Fill.Gradient.Style := TGradientStyle.Radial;


  // 포인트 초기화

  C.Fill.Gradient.Points.Clear;


  //  포인트 2개 "생성" (색 지정은 나중에 대입)

  C.Fill.Gradient.Points.Add;

  C.Fill.Gradient.Points.Add;


  // 바깥쪽 색상 (랜덤)

  OuterColor :=

    $FF000000 or

    (Random(256) shl 16) or

    (Random(256) shl 8)  or

    Random(256);


  // FMX 템플릿 구조 그대로:

  // item0: 바깥색, Offset 0

  // item1: 센터 흰색, Offset 1

  C.Fill.Gradient.Points[0].Offset := 0.0;

  C.Fill.Gradient.Points[0].Color  := OuterColor;


  C.Fill.Gradient.Points[1].Offset := 1.0;

  C.Fill.Gradient.Points[1].Color  := $FFFFFFFF; // white


  // ---- 테두리 제거 ----

  C.Stroke.Kind := TBrushKind.None;


  // ---- 물리 등록 ----

  RegisterCircle(C, C_Spped, 1 + Random(4));

end;


procedure TForm1.Timer1Timer(Sender: TObject);

begin

  Text1.Text := FCircleMap.Count.ToString;


  if FCircleMap.Count < 100 then // 전체 공 갯수 제한.

     Create_SpawnCircle();

end;




end.



Method to unpack zip files in Delphi

 procedure TMForm.UnzipFile(const ZipFileName, DestinationFolder: string);

var

  Zip: TZipFile;

begin

  Zip := TZipFile.Create;       // use System.Zip

  try

    Zip.Open(ZipFileName, zmRead);      // ZIP 파일 열기 (읽기 전용 모드)

    Zip.ExtractAll(DestinationFolder);  // 전체 파일 압축 해제

  finally

    Zip.Free;

  end;

end;

Windows 원격 데스크톱 연결 클라이언트에서 항목 제거

Windows 원격 데스크톱 연결 클라이언트에서 항목 제거

Windows 원격 데스크톱 연결 클라이언트의 원격 데스크톱 연결 컴퓨터 상자에서 항목을 제거하려면 레지스트리 편집기를 시작한 다음, 다음 레지스트리 키를 선택합니다.

HKEY_CURRENT_USER\Software\Microsoft\Terminal Server Client\Default

Drawing sine math function with FMX in Delphi

 





unit MUnit; interface uses System.SysUtils, System.Types, System.UITypes, System.Classes, System.Variants, System.Math, FMX.Types, FMX.Controls, FMX.Forms, FMX.Graphics, FMX.Dialogs, FMX.Controls.Presentation, FMX.StdCtrls, FMX.Layouts, FMX.Objects; type TMForm = class(TForm) MRect: TRectangle; Timer1: TTimer; Circle1: TCircle; Pie1: TPie; Text_Angle: TText; Text_Circle: TText; Text1: TText; StartTimer: TTimer; procedure FormShow(Sender: TObject); procedure Timer1Timer(Sender: TObject); procedure StartTimerTimer(Sender: TObject); private procedure OnePointCircle(x, y: single; setColor : cardinal); procedure Draw_Line2P(x1, y1, x2, y2: single; setColor: cardinal); procedure Sin_OnePoint(xD: single); procedure Init_Screen; { Private declarations } public { Public declarations } AngleXD : single; end; var MForm: TMForm; implementation {$R *.fmx} procedure TMForm.FormShow(Sender: TObject); begin Draw_Line2P( 0,0, 1320,0, $FF252525 ); // Pie Solid Color MForm.ClientWidth := 1920; MForm.ClientHeight := 1080; MForm.Fill.Color := $FF000000; MForm.FullScreen := TRUE; Init_Screen(); end; // 실행하고 3초 이후 작동. procedure TMForm.StartTimerTimer(Sender: TObject); begin StartTimer.Enabled := FALSE; Init_Screen(); Timer1.Enabled := TRUE; end; procedure TMForm.Init_Screen(); var i : integer; begin AngleXD := 0; Pie1.EndAngle := 0; for i := MRect.ComponentCount - 1 downto 0 do if MRect.Components[i] is TCircle then MRect.Components[i].Free; MForm.Realign; // MRect 밖으로 삐져나온 원 자국 제거. end; //------------------------------------------------------------------------------- procedure TMForm.Draw_Line2P( x1,y1, x2,y2 : single; setColor : cardinal ); var d, xtemp, ytemp, rAngle : single; drawLine : TLine; begin if x1 > x2 then begin xtemp := x1; ytemp := y1; x1 := x2; y1 := y2; x2 := xtemp; y2 := ytemp; end; d := SQRT( Power( x2-x1, 2 ) + Power( y2-y1, 2 ) ); // Uses System.Math rAngle := RadToDeg( ArcSin( (y2-y1)/d )); drawLine := TLine.Create( MRect ); drawLine.Parent := MRect; drawLine.LineLocation := TLineLocation.Inner; drawLine.LineType := TLineType.Bottom; drawLine.RotationCenter.X := 0; drawLine.RotationCenter.Y := 0; drawLine.Stroke.Thickness := 6; drawLine.Stroke.Color := setColor; drawLine.Height := 1; drawLine.Width := d; drawLine.Position.X := x1; drawLine.Position.Y := MRect.Height * 0.5 - y1; drawLine.RotationAngle := rAngle; end; //------------------------------------------------------------------------------- procedure TMForm.OnePointCircle( x, y : single; setColor : cardinal ); var cc : TCircle; xInterval : single; // X 간격 begin cc := TCircle.Create( MRect ); cc.Parent := MRect; cc.Fill.Color := setColor; cc.Stroke.Kind := TBrushKind.None; cc.Width := 6; cc.Height := 6; xInterval := 2; cc.Position.X := ( x * xInterval - cc.Width * 0.5 ); // * xInterval 는 x 간격을 더 넓게 cc.Position.y := MRect.Height * 0.5 - y - cc.Height * 0.5; // 500 - y Text_Angle.Position.X := cc.Position.X + 10; Text_Angle.Position.Y := cc.Position.y - 30; end; //--------------------------------------------- // xd : 0 ~ 360 도 Degree procedure TMForm.Sin_OnePoint( xD : single ); var radianA, yp : single; yStr : string; begin radianA := DegToRad( xD ); yp := SIN( radianA ) * MRect.Height * 0.5; yStr := Format( '%5f', [ SIN( radianA ) ] ); OnePointCircle( xD, yp, $FF00FFFF ); Text_Angle.Text := '( ' + AngleXD.ToString + ' ,' + yStr + ' )'; Text_Circle.Text := AngleXD.ToString; end; //***************************************************************** procedure TMForm.Timer1Timer(Sender: TObject); begin if AngleXD > 540 then begin Timer1.Enabled := FALSE; StartTimer.Enabled := TRUE; end else begin Sin_OnePoint( AngleXD ); Pie1.EndAngle := -1 * ( AngleXD - 360 * ( Round( AngleXD ) div 360 ) ); // 360도 초과 해도 360으로 인식하므로 360 주기로변환. end; AngleXD := AngleXD + 1; // 1도 씩 증가 end; end.