このコードは、iOSアプリケーションで球体のシミュレーションを描画するためのSwiftUIビューを定義しています。具体的には、ランダムに配置された複数の球体を、重力の影響を受けながら運動させ、衝突検出を行い、最も近い球体に線を引くように設計されています。 このコードは、Ball構造体を使用して球体の状態を表し、CoreMotionフレームワークを使用してデバイスの加速度を監視し、加速度に応じて球体の運動を変更するようになっています。 また、Canvasビューを使用して球体の描画を行い、UIKitのタイマーを使用して定期的に球体の位置を更新しています。さらに、衝突検出には、距離と速度を計算するためのベクトル演算を使用しています。 このコードは、iOSアプリケーションの開発者が、球体の物理シミュレーションを実装するための役立つサンプルとして活用できます。

import SwiftUI
import CoreMotion

struct Ball {
    var position: CGPoint
    var velocity: CGVector
    var acceleration: CGVector
    var mass: CGFloat
}

extension CGVector {
    func normalized() -> CGVector {
        let magnitude = sqrt(dx * dx + dy * dy)
        return CGVector(dx: dx / magnitude, dy: dy / magnitude)
    }

    func dot(_ other: CGVector) -> CGFloat {
        return dx * other.dx + dy * other.dy
    }
}

func * (left: CGVector, right: CGFloat) -> CGVector {
    return CGVector(dx: left.dx * right, dy: left.dy * right)
}

func + (left: CGVector, right: CGVector) -> CGVector {
    return CGVector(dx: left.dx + right.dx, dy: left.dy + right.dy)
}


struct ContentView: View {
    @State var balls = [Ball]()
    @State var timer: Timer?
    @State var ballCount = 0
    let gravity = CGVector(dx: 0, dy: 0.2)
    let motionManager = CMMotionManager()

    func initializeBalls() {
        for _ in 0..<70 {
            let ball = Ball(
                position: CGPoint(x: CGFloat.random(in: 0..<UIScreen.main.bounds.width),
                                  y: CGFloat.random(in: 0..<UIScreen.main.bounds.height)),
                velocity: CGVector(dx: 0, dy: 0),
                acceleration: gravity,
                mass: 62
            )
            balls.append(ball)
        }
        ballCount = balls.filter { ball in
            let minX = ball.mass / 2
            let maxX = UIScreen.main.bounds.width - ball.mass / 2
            let minY = ball.mass / 2
            let maxY = UIScreen.main.bounds.height - ball.mass / 2
            return ball.position.x >= minX && ball.position.x <= maxX && ball.position.y >= minY && ball.position.y <= maxY
        }.count // update ball count state variable based on balls within screen bounds
    }

    func resetBallPosition(_ ball: inout Ball) {
        ball.position = CGPoint(x: CGFloat.random(in: 0..<UIScreen.main.bounds.width),
                                y: CGFloat.random(in: 0..<UIScreen.main.bounds.height))
        ball.velocity = CGVector(dx: 0, dy: 0)
    }


    func updatePositions() {
        for i in 0..<balls.count {
            var ball = balls[i]

            // Update velocity and position based on acceleration
            ball.velocity.dx += ball.acceleration.dx
            ball.velocity.dy += ball.acceleration.dy
            ball.position.x += ball.velocity.dx
            ball.position.y += ball.velocity.dy

            // Limit ball position within screen bounds
            let minX = ball.mass / 2
            let maxX = UIScreen.main.bounds.width - ball.mass / 2
            let minY = ball.mass / 2
            let maxY = UIScreen.main.bounds.height - ball.mass / 2

            let hanpatsu = -0.2

            // Check if the ball is out of screen bounds and reset its position
            if ball.position.x < -ball.mass || ball.position.x > maxX + ball.mass || ball.position.y < -ball.mass || ball.position.y > maxY + ball.mass {
                resetBallPosition(&ball)
            } else {
                if ball.position.x < minX {
                    ball.position.x = minX
                    ball.velocity.dx *= hanpatsu
                } else if ball.position.x > maxX {
                    ball.position.x = maxX
                    ball.velocity.dx *= hanpatsu
                }

                if ball.position.y < minY {
                    ball.position.y = minY
                    ball.velocity.dy *= hanpatsu
                } else if ball.position.y > maxY {
                    ball.position.y = maxY
                    ball.velocity.dy *= hanpatsu
                }
            }

            // Added for collision detection
            for j in (i+1)..<balls.count {
                var otherBall = balls[j]
                let distance = hypot(ball.position.x - otherBall.position.x, ball.position.y - otherBall.position.y)
                let combinedRadius = ball.mass / 2 + otherBall.mass / 2

                if distance < combinedRadius {
                    // Calculate the new velocities after collision
                    var v1 = ball.velocity
                    var v2 = otherBall.velocity
                    let m1 = ball.mass
                    let m2 = otherBall.mass
                    let d = CGVector(dx: ball.position.x - otherBall.position.x,
                                     dy: ball.position.y - otherBall.position.y)
                    let un = d.normalized()
                    let ut = CGVector(dx: -un.dy, dy: un.dx)
                    let v1n = un.dot(v1)
                    let v1t = ut.dot(v1)
                    let v2n = un.dot(v2)
                    let v2t = ut.dot(v2)
                    let v1nAfter = (v1n * (m1 - m2) + 2 * m2 * v2n) / (m1 + m2)
                    let v2nAfter = (v2n * (m2 - m1) + 2 * m1 * v1n) / (m1 + m2)
                    v1 = un * v1nAfter + ut * v1t
                    v2 = un * v2nAfter + ut * v2t

                    ball.velocity = v1
                    otherBall.velocity = v2
                    // Move balls apart so they're not overlapping
                    let overlap = combinedRadius - distance
                    ball.position.x += overlap * (ball.position.x - otherBall.position.x) / distance
                    ball.position.y += overlap * (ball.position.y - otherBall.position.y) / distance
                    otherBall.position.x -= overlap * (ball.position.x - otherBall.position.x) / distance
                    otherBall.position.y -= overlap * (ball.position.y - otherBall.position.y) / distance

                    balls[j] = otherBall
                }
            }

            balls[i] = ball
        }
    }

    func startDeviceMotionUpdates() {
        if motionManager.isDeviceMotionAvailable {
            motionManager.deviceMotionUpdateInterval = 0.02
            motionManager.startDeviceMotionUpdates(to: .main) { motion, error in
                if let gravityData = motion?.gravity {
                    let gravityVector = CGVector(dx: CGFloat(gravityData.y) * -1.0, dy: CGFloat(-gravityData.x))
                    for i in 0..<balls.count {
                        balls[i].acceleration = gravityVector
                    }
                }
            }
        }
    }

    var body: some View {
        VStack {
            Text("Number of balls: \(ballCount)")
                .padding()
            Canvas { context, size in
                for ball in balls {
                    let path = Path { path in
                        path.addEllipse(in: CGRect(x: ball.position.x - ball.mass / 2,
                                                   y: ball.position.y - ball.mass / 2,
                                                   width: ball.mass,
                                                   height: ball.mass))
                    }
                    context.fill(path, with: .color(.blue))
                }

                for i in 0..<balls.count {
                    let ball = balls[i]
                    var minDistance = CGFloat.greatestFiniteMagnitude
                    var closestBall: Ball?

                    for j in 0..<balls.count {
                        if i == j { continue }
                        let otherBall = balls[j]
                        let distance = hypot(ball.position.x - otherBall.position.x, ball.position.y - otherBall.position.y)

                        if distance < minDistance {
                            minDistance = distance
                            closestBall = otherBall
                        }
                    }

                    if let closestBall = closestBall {
                        let path = Path { path in
                            path.move(to: ball.position)
                            path.addLine(to: closestBall.position)
                        }
                        context.stroke(path, with: .color(.blue), lineWidth: 24)
                    }
                }



            }
            .onAppear {
                initializeBalls()
                startDeviceMotionUpdates()
                timer = Timer.scheduledTimer(withTimeInterval: 0.01, repeats: true) { _ in
                    updatePositions()
                    ballCount = balls.filter { ball in
                        let minX = ball.mass / 2
                        let maxX = UIScreen.main.bounds.width - ball.mass / 2
                        let minY = ball.mass / 2
                        let maxY = UIScreen.main.bounds.height - ball.mass / 2
                        return ball.position.x >= minX && ball.position.x <= maxX && ball.position.y >= minY && ball.position.y <= maxY
                    }.count // update ball count state variable based on balls within screen bounds
                }
            }
        }
    }
}