Skip to content

Commit

Permalink
Revert "Merge pull request #2 from abdelaziz-mahdy/optimize-rendering"
Browse files Browse the repository at this point in the history
This reverts commit f67bfa2, reversing
changes made to 56db1cd.
  • Loading branch information
abdelaziz-mahdy committed Dec 11, 2024
1 parent 0951d88 commit e4747e3
Show file tree
Hide file tree
Showing 9 changed files with 63 additions and 154 deletions.
16 changes: 7 additions & 9 deletions lib/ball.dart
Original file line number Diff line number Diff line change
Expand Up @@ -10,14 +10,12 @@ class Ball {
List<Offset> trail = [];
DateTime creationTime;

Ball({
required this.position,
required this.velocity,
required this.color,
required this.radius,
}) : creationTime = DateTime.now();

Rect get boundingBox => Rect.fromCircle(center: position, radius: radius);
Ball(
{required this.position,
required this.velocity,
required this.color,
required this.radius})
: creationTime = DateTime.now();

@override
bool operator ==(covariant Ball other) {
Expand All @@ -40,4 +38,4 @@ class Ball {
trail.hashCode ^
creationTime.hashCode;
}
}
}
122 changes: 17 additions & 105 deletions lib/ball_painter.dart
Original file line number Diff line number Diff line change
@@ -1,31 +1,20 @@
import 'package:bouncy_ball_physics/ball.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';

/// Enum representing the shape of the trail left by balls.
enum TrailShape {
line,
singleTriangle,
multipleTriangles,
}

/// A custom painter for rendering bouncing balls with optional trails.
class BallPainter extends CustomPainter {
/// The list of balls to be painted.
List<Ball> balls;

/// The shape of the trail for each ball.
TrailShape trailShape;

/// Cache for ball paints based on their colors.
final Map<Color, Paint> _paintCache = {};

/// Constructor to initialize the ball painter.
/// [balls] is the list of balls to be painted.
/// [trailShape] defines the trail style for the balls. Default is [TrailShape.line].
BallPainter({required this.balls, this.trailShape = TrailShape.line});

/// Retrieves or creates a [Paint] object for a given ball.
final Map<Color, Paint> _paintCache = {};

Paint _getPaintForBall(Ball ball) {
return _paintCache.putIfAbsent(
ball.color,
Expand All @@ -35,76 +24,36 @@ class BallPainter extends CustomPainter {

@override
void paint(Canvas canvas, Size size) {
int ballCount = balls.length;
int hiddenBalls = 0;
double totalHiddenPercentage = 0.0;

// Sort balls by Y-position (descending).
balls.sort((a, b) => b.position.dy.compareTo(a.position.dy));

// Store bounding boxes of drawn balls.
List<Rect> drawnBallsBounds = [];

for (var ball in balls) {
final paint = _getPaintForBall(ball);
paint.strokeWidth = ball.radius / 10;

/// Calculate the hidden percentage of the current ball's bounding box relative to previously drawn balls.
/// Iterates through the bounding boxes of previously drawn balls to determine how much of the current ball is hidden.
/// Stops early if the hidden percentage reaches or exceeds 40% (0.4) to optimize performance.
double hiddenPct = 0.0;
for (var drawnBounds in drawnBallsBounds) {
hiddenPct = ball.boundingBox.hiddenPercentage(drawnBounds);
if (hiddenPct >= 0.4) {
break; // Exit the loop early if the ball is sufficiently hidden.
}
switch (trailShape) {
case TrailShape.line:
// Draw the trail as a line
_drawLineTrail(canvas, ball, paint);
break;
case TrailShape.singleTriangle:
// Draw the trail as a single triangle
_drawSingleTriangleTrail(canvas, ball, paint);
break;
case TrailShape.multipleTriangles:
// Draw the trail as multiple triangles
_drawMultipleTrianglesTrail(canvas, ball, paint);
break;
}

// If the ball is less than 40% hidden, draw it.
if (hiddenPct < 0.4) {
switch (trailShape) {
case TrailShape.line:
_drawLineTrail(canvas, ball, paint);
break;
case TrailShape.singleTriangle:
_drawSingleTriangleTrail(canvas, ball, paint);
break;
case TrailShape.multipleTriangles:
_drawMultipleTrianglesTrail(canvas, ball, paint);
break;
}

canvas.drawCircle(ball.position, ball.radius, paint);

// If the ball is completely visible, add its bounds to the drawn list.
if (hiddenPct == 0.0) {
drawnBallsBounds.add(ball.boundingBox);
}
} else {
hiddenBalls++;
}
totalHiddenPercentage += hiddenPct;
}

// Calculate and log the overall hidden percentage.
double overallHiddenPercentage =
ballCount > 0 ? (totalHiddenPercentage / ballCount) * 100 : 0.0;

if (kDebugMode) {
print(
"ballCount: $ballCount, hiddenBalls: $hiddenBalls, hiddenPercentage: ${overallHiddenPercentage.toStringAsFixed(2)}%}");
canvas.drawCircle(ball.position, ball.radius, paint);
}
}

/// Draws a line trail for the ball.
void _drawLineTrail(Canvas canvas, Ball ball, Paint paint) {
for (var i = 0; i < ball.trail.length - 1; i++) {
canvas.drawLine(ball.trail[i], ball.trail[i + 1],
paint..strokeWidth = ball.radius / 10);
}
}

/// Draws a single triangle trail for the ball.
void _drawSingleTriangleTrail(Canvas canvas, Ball ball, Paint paint) {
var path = Path();
if (ball.trail.isNotEmpty) {
Expand All @@ -116,22 +65,15 @@ class BallPainter extends CustomPainter {
canvas.drawPath(path, paint);
}

/// Draws multiple triangles as a trail for the ball.
void _drawMultipleTrianglesTrail(Canvas canvas, Ball ball, Paint paint) {
for (int i = 0; i < ball.trail.length - 1; i++) {
_drawTriangle(
canvas, ball.trail[i], ball.trail[i + 1], ball.position, paint);
}
}

/// Draws a single triangle given three points.
void _drawTriangle(
Canvas canvas,
Offset point1,
Offset point2,
Offset point3,
Paint paint,
) {
Canvas canvas, Offset point1, Offset point2, Offset point3, Paint paint) {
var path = Path();
path.moveTo(point1.dx, point1.dy);
path.lineTo(point2.dx, point2.dy);
Expand All @@ -146,33 +88,3 @@ class BallPainter extends CustomPainter {
return true;
}
}

/// Extension methods for the [Rect] class.
extension RectExtensions on Rect {
/// Checks if this rectangle completely contains another rectangle.
bool containsRect(Rect other) {
return left <= other.left &&
right >= other.right &&
top <= other.top &&
bottom >= other.bottom;
}

/// Calculates the percentage of this rectangle that is hidden by another rectangle.
/// Returns 0.0 if there is no overlap, and 1.0 if this rect is fully contained.
double hiddenPercentage(Rect other) {
// If there is no overlap, return 0.0.
if (!overlaps(other)) {
return 0.0;
}

// Calculate the intersection rectangle.
Rect intersection = intersect(other);

// Calculate the area of this rectangle and the intersection rectangle.
double thisArea = width * height;
double intersectionArea = intersection.width * intersection.height;

// Calculate the hidden percentage (0 to 1 range).
return (intersectionArea / thisArea).clamp(0.0, 1.0);
}
}
6 changes: 2 additions & 4 deletions lib/ball_physics_manager.dart
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,8 @@ class BallPhysicsManager {
final ValueNotifier<double> fpsNotifier = ValueNotifier(0.0);
final ValueNotifier<int> ballLimitNotifier = ValueNotifier(100);
final ValueNotifier<int> tailLengthNotifier = ValueNotifier(100);
double slidersBallsMaxValue = 5000;
double slidersBallsMinValue = 1;
double slidersTailMaxValue = 500;
double slidersTailMinValue = 1;
double slidersMaxValue = 500;
double slidersMinValue = 1;
static const int fpsAverageCount = 60;
final List<double> _fpsValues = [];

Expand Down
58 changes: 30 additions & 28 deletions lib/ball_physics_widget.dart
Original file line number Diff line number Diff line change
Expand Up @@ -72,24 +72,26 @@ class BallPhysicsWidgetState extends State<BallPhysicsWidget>
flex: 2,
child: Container(
decoration: BoxDecoration(border: Border.all()),
child: LayoutBuilder(builder: (context, constraints) {
return ValueListenableBuilder<TrailShape>(
valueListenable: trailShapeNotifier,
builder: (context, trailShape, child) {
return AnimatedBuilder(
animation: _controller,
builder: (context, child) {
manager.updatePhysics(context, constraints.biggest);
return CustomPaint(
painter: BallPainter(
balls: manager.balls, trailShape: trailShape),
child: Container(),
);
},
);
},
);
}),
child: LayoutBuilder(
builder: (context, constraints) {
return ValueListenableBuilder<TrailShape>(
valueListenable: trailShapeNotifier,
builder: (context, trailShape, child) {
return AnimatedBuilder(
animation: _controller,
builder: (context, child) {
manager.updatePhysics(context, constraints.biggest);
return CustomPaint(
painter: BallPainter(
balls: manager.balls, trailShape: trailShape),
child: Container(),
);
},
);
},
);
}
),
),
),
],
Expand All @@ -111,11 +113,11 @@ class BallPhysicsWidgetState extends State<BallPhysicsWidget>
builder: (BuildContext context, int value, Widget? child) {
return Slider(
value: manager.ballLimitNotifier.value.toDouble(),
min: manager.slidersBallsMinValue,
max: manager.slidersBallsMaxValue,
divisions: (manager.slidersBallsMaxValue -
manager.slidersBallsMinValue)
.toInt(),
min: manager.slidersMinValue,
max: manager.slidersMaxValue,
divisions:
(manager.slidersMaxValue - manager.slidersMinValue)
.toInt(),
label: manager.ballLimitNotifier.value.toString(),
onChanged: (double value) {
manager.ballLimitNotifier.value = value.toInt();
Expand All @@ -128,11 +130,11 @@ class BallPhysicsWidgetState extends State<BallPhysicsWidget>
builder: (BuildContext context, int value, Widget? child) {
return Slider(
value: manager.tailLengthNotifier.value.toDouble(),
min: manager.slidersTailMinValue,
max: manager.slidersTailMaxValue,
divisions: (manager.slidersTailMaxValue -
manager.slidersTailMinValue)
.toInt(),
min: manager.slidersMinValue,
max: manager.slidersMaxValue,
divisions:
(manager.slidersMaxValue - manager.slidersMinValue)
.toInt(),
label: manager.tailLengthNotifier.value.toString(),
onChanged: (double value) {
manager.tailLengthNotifier.value = value.toInt();
Expand Down
2 changes: 1 addition & 1 deletion macos/Podfile.lock
Original file line number Diff line number Diff line change
Expand Up @@ -13,4 +13,4 @@ SPEC CHECKSUMS:

PODFILE CHECKSUM: 236401fc2c932af29a9fcf0e97baeeb2d750d367

COCOAPODS: 1.16.2
COCOAPODS: 1.14.3
2 changes: 1 addition & 1 deletion macos/Runner.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -258,7 +258,7 @@
isa = PBXProject;
attributes = {
LastSwiftUpdateCheck = 0920;
LastUpgradeCheck = 1510;
LastUpgradeCheck = 1430;
ORGANIZATIONNAME = "";
TargetAttributes = {
331C80D4294CF70F00263BE5 = {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
LastUpgradeVersion = "1510"
LastUpgradeVersion = "1430"
version = "1.3">
<BuildAction
parallelizeBuildables = "YES"
Expand Down
2 changes: 1 addition & 1 deletion macos/Runner/AppDelegate.swift
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import Cocoa
import FlutterMacOS

@main
@NSApplicationMain
class AppDelegate: FlutterAppDelegate {
override func applicationShouldTerminateAfterLastWindowClosed(_ sender: NSApplication) -> Bool {
return true
Expand Down
7 changes: 3 additions & 4 deletions pubspec.lock
Original file line number Diff line number Diff line change
Expand Up @@ -243,10 +243,10 @@ packages:
dependency: transitive
description:
name: test_api
sha256: "5b8a98dafc4d5c4c9c72d8b31ab2b23fc13422348d2997120294d3bac86b4ddb"
sha256: "5c2f730018264d276c20e4f1503fd1308dfbbae39ec8ee63c5236311ac06954b"
url: "https://pub.dev"
source: hosted
version: "0.7.2"
version: "0.6.1"
vector_math:
dependency: transitive
description:
Expand All @@ -272,5 +272,4 @@ packages:
source: hosted
version: "3.0.3"
sdks:
dart: ">=3.3.0 <4.0.0"
flutter: ">=3.18.0-18.0.pre.54"
dart: ">=3.2.3 <4.0.0"

0 comments on commit e4747e3

Please sign in to comment.