このコードは、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
}
}
}
}
}