// // World.swift // Logic // // Created by Alex Loren on 8/26/26. // import SwiftGodot @Godot class World: Node { // MARK: - Properties private var lightColors: [String] = [ "#95FDDE", "#8029F8", "#ED4881" ] private var randomLightColor: Color { get { if let color = lightColors.randomElement(), Color.htmlIsValid(color: color) { return Color.html(rgba: color) } print("Failed to get random light color, returning white.") return .white } } // MARK: - Functions override func _enterTree() { assemble() } func assemble() { let camera = createCamera(at: Vector3(x: 0, y: -1.75, z: 4.5)) camera.environment = createEnvironment() addChild(node: camera) addChild(node: createRotatingWalls()) addChild(node: createPhysicsBounds()) for _ in 1...200 { let sphere = createSphereRigidBody() addChild(node: sphere) sphere.globalPosition = getRandomPosition() } for _ in 1...100 { let box = createBoxRigidBody(size: 0.2) addChild(node: box) box.globalPosition = getRandomPosition() } for index in 0...2 { let rotator = createNodeRotator() let light = createLight(range: 15, color: Color.html(rgba: lightColors[index])) rotator.addChild(node: light) light.position = getRandomPosition(lower: 3, upper: 4) addChild(node: rotator) } } @discardableResult private func createCamera(at position: Vector3 = Vector3(x: 0, y: 0, z: 5)) -> Camera3D { let camera = Camera3D() camera.current = true camera.position = position return camera } @discardableResult private func createRotatingWalls() -> Node3D { let rotator = PhysicsRotator() rotator.axis = Vector3(x: 1, y: 1, z: 1) rotator.speed = 0.5 // Top and bottom walls rotator.addChild(node: createCollidableWall(at: Vector3(x: 0, y: 2.5, z: 0))) rotator.addChild(node: createCollidableWall(at: Vector3(x: 0, y: -2.5, z: 0))) // Front and back walls rotator.addChild(node: createCollidableWall(at: Vector3(x: 2.5, y: 0, z: 0), rotation: Vector3(x: 0, y: 0, z: 90))) rotator.addChild(node: createCollidableWall(at: Vector3(x: -2.5, y: 0, z: 0), rotation: Vector3(x: 0, y: 0, z: 90))) // Left and right walls rotator.addChild(node: createCollidableWall(at: Vector3(x: 0, y: 0, z: 2.5), rotation: Vector3(x: 0, y: 90, z: 90))) rotator.addChild(node: createCollidableWall(at: Vector3(x: 0, y: 0, z: -2.5), rotation: Vector3(x: 0, y: 90, z: 90))) return rotator } @discardableResult private func createCollidableWall(at position: Vector3, rotation: Vector3 = .zero) -> CollisionShape3D { let shape = BoxShape3D() shape.size = Vector3(x: 5, y: 0.5, z: 5) let collider = CollisionShape3D() collider.shape = shape collider.position = position collider.rotationDegrees = rotation return collider } @discardableResult private func createSphereRigidBody(radius: Double = 0.1, randomize: Bool = true, randomScale: Double = 0.05) -> RigidBody3D { let definedRadius = !randomize ? radius : Double.random(in: (radius - randomScale)...(radius + randomScale)) let shape = SphereShape3D() shape.radius = definedRadius let collider = CollisionShape3D() collider.shape = shape let mesh = SphereMesh() mesh.radius = definedRadius mesh.height = definedRadius * 2 mesh.radialSegments = 8 mesh.rings = 8 let meshInstance = MeshInstance3D() meshInstance.mesh = mesh meshInstance.materialOverride = createMaterial() let material = PhysicsMaterial() material.bounce = 0.8 let body = RigidBody3D() body.physicsMaterialOverride = material body.addChild(node: collider) body.addChild(node: meshInstance) return body } @discardableResult private func createBoxRigidBody(size: Float = 0.1, randomize: Bool = true, randomScale: Float = 0.05) -> RigidBody3D { let definedSize = !randomize ? size : Float.random(in: (size - randomScale)...(size + randomScale)) let shape = BoxShape3D() shape.size = Vector3(x: definedSize, y: definedSize, z: definedSize) let collider = CollisionShape3D() collider.shape = shape let mesh = BoxMesh() mesh.size = Vector3(x: definedSize, y: definedSize, z: definedSize) let meshInstance = MeshInstance3D() meshInstance.mesh = mesh meshInstance.materialOverride = createMaterial() let material = PhysicsMaterial() material.bounce = 0.8 let body = RigidBody3D() body.physicsMaterialOverride = material body.addChild(node: collider) body.addChild(node: meshInstance) return body } @discardableResult private func createLight(range: Double = 5, color: Color = .white) -> OmniLight3D { let light = OmniLight3D() light.omniRange = range light.omniShadowMode = .cube light.shadowEnabled = true light.lightColor = color light.lightEnergy = 5 return light } @discardableResult private func createNodeRotator() -> NodeRotator { let randomOnAxis: [Float] = [ Float(Int.random(in: -1...1)), Float(Int.random(in: -1...1)), Float(Int.random(in: -1...1)) ] let rotator = NodeRotator() rotator.axis = Vector3(x: randomOnAxis[0], y: randomOnAxis[1], z: randomOnAxis[2]) rotator.speed = Double.random(in: 0.3...0.6) return rotator } private func createMaterial() -> StandardMaterial3D { let material = StandardMaterial3D() material.diffuseMode = .lambertWrap return material } @discardableResult private func createEnvironment() -> Environment { let environment = Environment() environment.backgroundMode = .color environment.backgroundColor = .black environment.glowEnabled = true environment.glowIntensity = 0.6 environment.glowStrength = 1.75 return environment } private func createPhysicsBounds() -> Area3D { let shape = SphereShape3D() shape.radius = 10 let collider = CollisionShape3D() collider.shape = shape let area = Area3D() area.addChild(node: collider) area.bodyExited.connect { [weak self] body in guard let self, let body = body as? RigidBody3D else { return } body.getChildren().forEach { child in if let collider = child as? CollisionShape3D { switch collider.shape { case is SphereShape3D: let sphere = self.createSphereRigidBody() addChild(node: sphere) sphere.globalPosition = self.getRandomPosition() break case is BoxShape3D: let box = self.createBoxRigidBody() addChild(node: box) box.globalPosition = self.getRandomPosition() default: break } } } body.queueFree() } return area } private func getRandomPosition(in range: Float = 2) -> Vector3 { return .init(x: Float.random(in: -range...range), y: Float.random(in: -range...range), z: Float.random(in: -range...range)) } private func getRandomPosition(lower: Float = 1, upper: Float = 3) -> Vector3{ return .init(x: .random(in: lower...upper), y: .random(in: lower...upper), z: .random(in: lower...upper)) } }