-
-
Notifications
You must be signed in to change notification settings - Fork 5
/
RotatingCube.swift
147 lines (112 loc) · 5.88 KB
/
RotatingCube.swift
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
/*
* Copyright © 2023-2024 Dustin Collins (Strega's Gate)
* All Rights Reserved.
*
* http://stregasgate.com
*/
import Foundation
import GateEngine
@main
final class RotatingCubeGameDelegate: GameDelegate {
// didFinishLaunching() is executed immediatley after the game is ready to start
func didFinishLaunching(game: Game, options: LaunchOptions) async {
// Add the cube update system to the game. System implementation is below
game.insertSystem(RotatingCubeSystem.self)
// Add the cube rendering system to the game. RenderingSystem implementation is below
game.insertSystem(RotatingCubeRenderingSystem.self)
// Create a new entity to store the camera
let camera = Entity()
// Add the camera component to the entity
camera.insert(CameraComponent.self)
// Unwrap a Transform3Component
camera.insert(Transform3Component.self) { component in
// Move the camera backward, relative to it's rotation, by 1 units
component.position.move(1, toward: component.rotation.backward)
}
// Add the camera entity to the game
game.insertEntity(camera)
// Set the main window's title
game.windowManager.mainWindow?.title = "Rotating Cube"
}
}
// System subclasses are used to manipulate the simulation. They can't be used to draw content.
class RotatingCubeSystem: System {
// setup() is executed a single time when the System is added to the game
override func setup(game: Game, input: HID) async {
// Create a new entity
let cube = Entity()
// Give the entity a 3D transform
cube.insert(Transform3Component.self) {component in
// Move 1 unit forward, so it's in front of the camera
component.position.move(1, toward: .forward)
}
// Give the entity 3D geometry
cube.insert(RenderingGeometryComponent.self) { component in
// Load the engine provided unit cube. A unit cube is 1x1x1 units
component.insert(Geometry(as: .unitCube))
}
// Give the entity a material
cube.insert(MaterialComponent.self) { material in
// Begin modifying material channel zero
material.channel(0) { channel in
// Load the engine provided placeholder texture
channel.texture = Texture(as: .checkerPattern)
}
}
// Add the entity to the game
game.insertEntity(cube)
}
// update() is executed every simulation tick, which may or may not be every frame
override func update(game: Game, input: HID, withTimePassed deltaTime: Float) async {
// Loop through all entites in the game
for entity in game.entities {
// Make sure the entity is not the camera
guard entity.hasComponent(CameraComponent.self) == false else {continue}
// Get the 3D transform component if one exists, otherwise skip to the next entity
entity.insert(Transform3Component.self) {component in
// Create an angle based on how much time has passed
let angle = Degrees(deltaTime * 15)
// Rotate around the forward axis
component.rotation *= Quaternion(angle, axis: .forward)
// Rotate around the up axis
component.rotation *= Quaternion(angle, axis: .up)
}
}
}
// phase determines at which point the system should be updated relative to other systems
override class var phase: System.Phase {.simulation}
}
// RenderingSystem subclasses can draw content
// However, updating the simulation from a RenderingSystem is a programming error
// GateEngine allows for frame drops and headless execution for servers
// In these cases RenderingSystems do not get updated
class RotatingCubeRenderingSystem: RenderingSystem {
// render() is called only wehn drawing needs to be done
override func render(game: Game, window: Window, withTimePassed deltaTime: Float) {
// To draw something in GateEngine you must create a container to store the renderable objects
// A Scene is a container for 3D renderable objects and it requires a Camera
// So we'll create a Camera from the game's cameraEntity
guard let camera = Camera(game.cameraEntity) else {return}
// Create a Scene with the scene camera
// Scene is light weight and you're meant to create a new one every frame
var scene = Scene(camera: camera)
// Loop through all entites in the game
for entity in game.entities {
// Make sure the entity has a material, otherwise move on
guard let material = entity.component(ofType: MaterialComponent.self)?.material else {continue}
// Make sure the entity has a 3D transform, otherwise move on
guard let transform = entity.component(ofType: Transform3Component.self)?.transform else {continue}
// Make sure the entity has a RenderingGeometryComponent and unwrap it
if let geometryComponent = entity.component(ofType: RenderingGeometryComponent.self) {
// Loop through all geometry in the RenderingGeometryComponent
for geometry in geometryComponent.geometries {
// Add the geometry to the scene with it's material and transform
scene.insert(geometry, withMaterial: material, at: transform)
}
}
}
// A framebuffer is a RenderTarget that represents the window
// The frameBuffer will automatically draw the scene
window.insert(scene)
}
}