├── gallery ├── screenshot.jpg └── create-gameobject.jpg ├── .gitignore ├── Sources ├── Palico │ ├── Platform │ │ ├── GLFW │ │ │ ├── README.md │ │ │ ├── GlfwInput.swift │ │ │ ├── GlfwContext.swift │ │ │ └── GlfwWindow.swift │ │ └── Cocoa │ │ │ ├── CocoaWindow+View.swift │ │ │ ├── CocoaContext.swift │ │ │ ├── CocoaInput.swift │ │ │ ├── CocoaWindow+Event.swift │ │ │ ├── ViewController.swift │ │ │ ├── CocoaWindow.swift │ │ │ └── CocoaWindow+Callback.swift │ ├── Assets │ │ ├── Images │ │ │ └── dadan.png │ │ ├── Fonts │ │ │ ├── Ruda │ │ │ │ ├── Ruda-Black.ttf │ │ │ │ ├── Ruda-Bold.ttf │ │ │ │ ├── Ruda-Medium.ttf │ │ │ │ ├── Ruda-Regular.ttf │ │ │ │ ├── Ruda-SemiBold.ttf │ │ │ │ ├── Ruda-ExtraBold.ttf │ │ │ │ ├── README.txt │ │ │ │ └── OFL.txt │ │ │ ├── Roboto │ │ │ │ ├── Roboto-Bold.ttf │ │ │ │ ├── Roboto-Thin.ttf │ │ │ │ ├── Roboto-Black.ttf │ │ │ │ ├── Roboto-Italic.ttf │ │ │ │ ├── Roboto-Light.ttf │ │ │ │ ├── Roboto-Medium.ttf │ │ │ │ ├── Roboto-Regular.ttf │ │ │ │ ├── Roboto-BlackItalic.ttf │ │ │ │ ├── Roboto-BoldItalic.ttf │ │ │ │ ├── Roboto-LightItalic.ttf │ │ │ │ ├── Roboto-ThinItalic.ttf │ │ │ │ └── Roboto-MediumItalic.ttf │ │ │ ├── OpenSans │ │ │ │ ├── OpenSans-Bold.ttf │ │ │ │ ├── OpenSans-Light.ttf │ │ │ │ ├── OpenSans-Italic.ttf │ │ │ │ ├── OpenSans-Medium.ttf │ │ │ │ ├── OpenSans-Regular.ttf │ │ │ │ ├── OpenSans-BoldItalic.ttf │ │ │ │ ├── OpenSans-ExtraBold.ttf │ │ │ │ ├── OpenSans-SemiBold.ttf │ │ │ │ ├── OpenSans-LightItalic.ttf │ │ │ │ ├── OpenSans-MediumItalic.ttf │ │ │ │ ├── OpenSans-ExtraBoldItalic.ttf │ │ │ │ └── OpenSans-SemiBoldItalic.ttf │ │ │ └── FontAwesome5 │ │ │ │ ├── fa-solid-900.ttf │ │ │ │ ├── fa-brands-400.ttf │ │ │ │ ├── fa-regular-400.ttf │ │ │ │ └── LICENSE.txt │ │ └── Shaders │ │ │ ├── Common.metal │ │ │ └── Main.metal │ ├── Core │ │ ├── Log.swift │ │ ├── Window.swift │ │ ├── Time.swift │ │ ├── PlatformContext.swift │ │ ├── Layer.swift │ │ ├── Console.swift │ │ └── Application.swift │ ├── Renderer │ │ ├── Camera.swift │ │ ├── RenderPass+Action.swift │ │ ├── Shader.swift │ │ ├── RenderPassPool.swift │ │ ├── MetalContext.swift │ │ ├── ShaderDataBridge.swift │ │ ├── Color.swift │ │ ├── PipelineStatePool.swift │ │ ├── Light.swift │ │ ├── ShaderLibrary.swift │ │ ├── RenderPass.swift │ │ ├── Mesh.swift │ │ ├── TextureUtils.swift │ │ └── EditorCamera.swift │ ├── Scene │ │ ├── Component.swift │ │ ├── SceneCamera.swift │ │ ├── Component+Camera.swift │ │ ├── Component+Script.swift │ │ ├── Component+Light.swift │ │ ├── Component+Tag.swift │ │ ├── Scene+View.swift │ │ ├── Component+MeshRenderer.swift │ │ ├── Component+Transform.swift │ │ ├── Scene.swift │ │ ├── SceneLight.swift │ │ ├── Scene+Object.swift │ │ ├── GameObject.swift │ │ └── Primitive.swift │ ├── ImGui │ │ ├── ImGuiFontLibrary.swift │ │ ├── ImGuizmo+Enum.swift │ │ ├── ImGuiBackend+Cocoa.swift │ │ ├── Backends │ │ │ └── imgui_shaders.swift │ │ ├── ImGuiBackend.swift │ │ ├── ImGuiBackend+Metal.swift │ │ ├── ImGuiTheme.swift │ │ ├── ImGuiLayer.swift │ │ └── ImGui+Extra.swift │ ├── Input │ │ ├── MouseCode.swift │ │ ├── Input.swift │ │ └── KeyCode.swift │ ├── Utils │ │ └── FileUtils.swift │ ├── Event │ │ ├── AppEvent.swift │ │ ├── Event.swift │ │ ├── KeyEvent.swift │ │ └── MouseEvent.swift │ └── Script │ │ └── NativeScript.swift ├── Editor │ ├── Assets │ │ └── Images │ │ │ └── dadan.png │ ├── Log.swift │ ├── main.swift │ ├── Editor.swift │ ├── Panels │ │ ├── ImGuiDemoPanel.swift │ │ ├── Panel.swift │ │ ├── ScenePanel.swift │ │ ├── AssetPanel.swift │ │ ├── ConsolePanel.swift │ │ ├── ScenePanel+Hierarchy.swift │ │ └── DrawControls.swift │ ├── Scripts │ │ └── RotateScript.swift │ └── Utils │ │ └── FileUtils.swift └── Example │ └── main.swift ├── .github └── workflows │ ├── ci-macos.yml │ └── markdown-link-check.yml ├── docs └── UserGuide.md ├── LICENSE ├── Package.resolved ├── Package.swift └── README.md /gallery/screenshot.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/forkercat/PalicoEngine/HEAD/gallery/screenshot.jpg -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | /.build 3 | /Packages 4 | /*.xcodeproj 5 | xcuserdata/ 6 | DerivedData/ 7 | .swiftpm/* 8 | -------------------------------------------------------------------------------- /gallery/create-gameobject.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/forkercat/PalicoEngine/HEAD/gallery/create-gameobject.jpg -------------------------------------------------------------------------------- /Sources/Palico/Platform/GLFW/README.md: -------------------------------------------------------------------------------- 1 | # GLFW in Palico 2 | 3 | Not tested yet. Needs more work on ImGui Backend! 4 | -------------------------------------------------------------------------------- /Sources/Editor/Assets/Images/dadan.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/forkercat/PalicoEngine/HEAD/Sources/Editor/Assets/Images/dadan.png -------------------------------------------------------------------------------- /Sources/Palico/Assets/Images/dadan.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/forkercat/PalicoEngine/HEAD/Sources/Palico/Assets/Images/dadan.png -------------------------------------------------------------------------------- /Sources/Palico/Assets/Fonts/Ruda/Ruda-Black.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/forkercat/PalicoEngine/HEAD/Sources/Palico/Assets/Fonts/Ruda/Ruda-Black.ttf -------------------------------------------------------------------------------- /Sources/Palico/Assets/Fonts/Ruda/Ruda-Bold.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/forkercat/PalicoEngine/HEAD/Sources/Palico/Assets/Fonts/Ruda/Ruda-Bold.ttf -------------------------------------------------------------------------------- /Sources/Palico/Assets/Fonts/Roboto/Roboto-Bold.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/forkercat/PalicoEngine/HEAD/Sources/Palico/Assets/Fonts/Roboto/Roboto-Bold.ttf -------------------------------------------------------------------------------- /Sources/Palico/Assets/Fonts/Roboto/Roboto-Thin.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/forkercat/PalicoEngine/HEAD/Sources/Palico/Assets/Fonts/Roboto/Roboto-Thin.ttf -------------------------------------------------------------------------------- /Sources/Palico/Assets/Fonts/Ruda/Ruda-Medium.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/forkercat/PalicoEngine/HEAD/Sources/Palico/Assets/Fonts/Ruda/Ruda-Medium.ttf -------------------------------------------------------------------------------- /Sources/Palico/Assets/Fonts/Ruda/Ruda-Regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/forkercat/PalicoEngine/HEAD/Sources/Palico/Assets/Fonts/Ruda/Ruda-Regular.ttf -------------------------------------------------------------------------------- /Sources/Palico/Assets/Fonts/Ruda/Ruda-SemiBold.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/forkercat/PalicoEngine/HEAD/Sources/Palico/Assets/Fonts/Ruda/Ruda-SemiBold.ttf -------------------------------------------------------------------------------- /Sources/Palico/Assets/Fonts/Roboto/Roboto-Black.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/forkercat/PalicoEngine/HEAD/Sources/Palico/Assets/Fonts/Roboto/Roboto-Black.ttf -------------------------------------------------------------------------------- /Sources/Palico/Assets/Fonts/Roboto/Roboto-Italic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/forkercat/PalicoEngine/HEAD/Sources/Palico/Assets/Fonts/Roboto/Roboto-Italic.ttf -------------------------------------------------------------------------------- /Sources/Palico/Assets/Fonts/Roboto/Roboto-Light.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/forkercat/PalicoEngine/HEAD/Sources/Palico/Assets/Fonts/Roboto/Roboto-Light.ttf -------------------------------------------------------------------------------- /Sources/Palico/Assets/Fonts/Roboto/Roboto-Medium.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/forkercat/PalicoEngine/HEAD/Sources/Palico/Assets/Fonts/Roboto/Roboto-Medium.ttf -------------------------------------------------------------------------------- /Sources/Palico/Assets/Fonts/Ruda/Ruda-ExtraBold.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/forkercat/PalicoEngine/HEAD/Sources/Palico/Assets/Fonts/Ruda/Ruda-ExtraBold.ttf -------------------------------------------------------------------------------- /Sources/Palico/Assets/Fonts/OpenSans/OpenSans-Bold.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/forkercat/PalicoEngine/HEAD/Sources/Palico/Assets/Fonts/OpenSans/OpenSans-Bold.ttf -------------------------------------------------------------------------------- /Sources/Palico/Assets/Fonts/OpenSans/OpenSans-Light.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/forkercat/PalicoEngine/HEAD/Sources/Palico/Assets/Fonts/OpenSans/OpenSans-Light.ttf -------------------------------------------------------------------------------- /Sources/Palico/Assets/Fonts/Roboto/Roboto-Regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/forkercat/PalicoEngine/HEAD/Sources/Palico/Assets/Fonts/Roboto/Roboto-Regular.ttf -------------------------------------------------------------------------------- /Sources/Palico/Assets/Fonts/FontAwesome5/fa-solid-900.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/forkercat/PalicoEngine/HEAD/Sources/Palico/Assets/Fonts/FontAwesome5/fa-solid-900.ttf -------------------------------------------------------------------------------- /Sources/Palico/Assets/Fonts/OpenSans/OpenSans-Italic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/forkercat/PalicoEngine/HEAD/Sources/Palico/Assets/Fonts/OpenSans/OpenSans-Italic.ttf -------------------------------------------------------------------------------- /Sources/Palico/Assets/Fonts/OpenSans/OpenSans-Medium.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/forkercat/PalicoEngine/HEAD/Sources/Palico/Assets/Fonts/OpenSans/OpenSans-Medium.ttf -------------------------------------------------------------------------------- /Sources/Palico/Assets/Fonts/OpenSans/OpenSans-Regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/forkercat/PalicoEngine/HEAD/Sources/Palico/Assets/Fonts/OpenSans/OpenSans-Regular.ttf -------------------------------------------------------------------------------- /Sources/Palico/Assets/Fonts/Roboto/Roboto-BlackItalic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/forkercat/PalicoEngine/HEAD/Sources/Palico/Assets/Fonts/Roboto/Roboto-BlackItalic.ttf -------------------------------------------------------------------------------- /Sources/Palico/Assets/Fonts/Roboto/Roboto-BoldItalic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/forkercat/PalicoEngine/HEAD/Sources/Palico/Assets/Fonts/Roboto/Roboto-BoldItalic.ttf -------------------------------------------------------------------------------- /Sources/Palico/Assets/Fonts/Roboto/Roboto-LightItalic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/forkercat/PalicoEngine/HEAD/Sources/Palico/Assets/Fonts/Roboto/Roboto-LightItalic.ttf -------------------------------------------------------------------------------- /Sources/Palico/Assets/Fonts/Roboto/Roboto-ThinItalic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/forkercat/PalicoEngine/HEAD/Sources/Palico/Assets/Fonts/Roboto/Roboto-ThinItalic.ttf -------------------------------------------------------------------------------- /Sources/Palico/Assets/Fonts/FontAwesome5/fa-brands-400.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/forkercat/PalicoEngine/HEAD/Sources/Palico/Assets/Fonts/FontAwesome5/fa-brands-400.ttf -------------------------------------------------------------------------------- /Sources/Palico/Assets/Fonts/FontAwesome5/fa-regular-400.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/forkercat/PalicoEngine/HEAD/Sources/Palico/Assets/Fonts/FontAwesome5/fa-regular-400.ttf -------------------------------------------------------------------------------- /Sources/Palico/Assets/Fonts/OpenSans/OpenSans-BoldItalic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/forkercat/PalicoEngine/HEAD/Sources/Palico/Assets/Fonts/OpenSans/OpenSans-BoldItalic.ttf -------------------------------------------------------------------------------- /Sources/Palico/Assets/Fonts/OpenSans/OpenSans-ExtraBold.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/forkercat/PalicoEngine/HEAD/Sources/Palico/Assets/Fonts/OpenSans/OpenSans-ExtraBold.ttf -------------------------------------------------------------------------------- /Sources/Palico/Assets/Fonts/OpenSans/OpenSans-SemiBold.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/forkercat/PalicoEngine/HEAD/Sources/Palico/Assets/Fonts/OpenSans/OpenSans-SemiBold.ttf -------------------------------------------------------------------------------- /Sources/Palico/Assets/Fonts/Roboto/Roboto-MediumItalic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/forkercat/PalicoEngine/HEAD/Sources/Palico/Assets/Fonts/Roboto/Roboto-MediumItalic.ttf -------------------------------------------------------------------------------- /Sources/Palico/Assets/Fonts/OpenSans/OpenSans-LightItalic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/forkercat/PalicoEngine/HEAD/Sources/Palico/Assets/Fonts/OpenSans/OpenSans-LightItalic.ttf -------------------------------------------------------------------------------- /Sources/Palico/Assets/Fonts/OpenSans/OpenSans-MediumItalic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/forkercat/PalicoEngine/HEAD/Sources/Palico/Assets/Fonts/OpenSans/OpenSans-MediumItalic.ttf -------------------------------------------------------------------------------- /Sources/Palico/Assets/Fonts/OpenSans/OpenSans-ExtraBoldItalic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/forkercat/PalicoEngine/HEAD/Sources/Palico/Assets/Fonts/OpenSans/OpenSans-ExtraBoldItalic.ttf -------------------------------------------------------------------------------- /Sources/Palico/Assets/Fonts/OpenSans/OpenSans-SemiBoldItalic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/forkercat/PalicoEngine/HEAD/Sources/Palico/Assets/Fonts/OpenSans/OpenSans-SemiBoldItalic.ttf -------------------------------------------------------------------------------- /Sources/Editor/Log.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Log.swift 3 | // Editor 4 | // 5 | // Created by Junhao Wang on 12/16/21. 6 | // 7 | 8 | import OhMyLog 9 | 10 | typealias Log = OhMyLog.Log 11 | -------------------------------------------------------------------------------- /Sources/Palico/Core/Log.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Log.swift 3 | // Palico 4 | // 5 | // Created by Junhao Wang on 12/15/21. 6 | // 7 | 8 | import OhMyLog 9 | 10 | typealias Log = OhMyLog.Log 11 | -------------------------------------------------------------------------------- /Sources/Example/main.swift: -------------------------------------------------------------------------------- 1 | // 2 | // main.swift 3 | // Editor 4 | // 5 | // Created by Junhao Wang on 12/17/21. 6 | // 7 | 8 | import OhMyLog 9 | import MathLib 10 | 11 | func main() { 12 | Log.info("Hello!") 13 | } 14 | 15 | main() 16 | -------------------------------------------------------------------------------- /.github/workflows/ci-macos.yml: -------------------------------------------------------------------------------- 1 | name: macOS 2 | 3 | on: 4 | push: 5 | branches: [ main ] 6 | pull_request: 7 | branches: [ main ] 8 | 9 | jobs: 10 | macos-build-release: 11 | runs-on: macos-latest 12 | steps: 13 | - uses: actions/checkout@v2 14 | - name: Build 15 | run: swift build -------------------------------------------------------------------------------- /.github/workflows/markdown-link-check.yml: -------------------------------------------------------------------------------- 1 | name: Check markdown links 2 | 3 | on: push 4 | 5 | jobs: 6 | markdown-link-check: 7 | runs-on: ubuntu-latest 8 | steps: 9 | - name: Checkout 10 | uses: actions/checkout@v2 11 | - name: markdown-link-check 12 | uses: gaurav-nelson/github-action-markdown-link-check@master -------------------------------------------------------------------------------- /Sources/Palico/Renderer/Camera.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Camera.swift 3 | // Palico 4 | // 5 | // Created by Junhao Wang on 1/6/22. 6 | // 7 | 8 | import MathLib 9 | 10 | public protocol Camera: AnyObject { 11 | var position: Float3 { get } 12 | var viewMatrix: Float4x4 { get } 13 | var projectionMatrix: Float4x4 { get } 14 | } 15 | -------------------------------------------------------------------------------- /Sources/Palico/Scene/Component.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Component.swift 3 | // Palico 4 | // 5 | // Created by Junhao Wang on 1/2/22. 6 | // 7 | 8 | import MathLib 9 | import MothECS 10 | 11 | public protocol Component: MothComponent { 12 | var title: String { get } 13 | var enabled: Bool { get set } 14 | static var icon: String { get } 15 | } 16 | -------------------------------------------------------------------------------- /Sources/Palico/Scene/SceneCamera.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SceneCamera.swift 3 | // Palico 4 | // 5 | // Created by Junhao Wang on 1/6/22. 6 | // 7 | 8 | import MathLib 9 | 10 | class SceneCamera: Camera { 11 | var position: Float3 { [0, 0, 0] } 12 | var viewMatrix: Float4x4 { .identity } 13 | var projectionMatrix: Float4x4 { .identity } 14 | } 15 | -------------------------------------------------------------------------------- /Sources/Editor/main.swift: -------------------------------------------------------------------------------- 1 | // 2 | // main.swift 3 | // Editor 4 | // 5 | // Created by Junhao Wang on 12/17/21. 6 | // 7 | 8 | import Palico 9 | import MathLib 10 | 11 | // Log 12 | Log.registerLogger(name: "Editor", level: .trace) 13 | 14 | let app = Editor(name: "Palico Engine v1.0", arguments: CommandLine.arguments, size: Int2(1440, 810)) 15 | app.run() 16 | 17 | Log.info("Exit(0)") 18 | -------------------------------------------------------------------------------- /Sources/Palico/ImGui/ImGuiFontLibrary.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ImGuiFontLibrary.swift 3 | // Palico 4 | // 5 | // Created by Junhao Wang on 1/13/22. 6 | // 7 | 8 | import ImGui 9 | 10 | public enum ImGuiFontLibrary { 11 | public static var defaultFont: UnsafeMutablePointer! 12 | public static var regularIcon: UnsafeMutablePointer! 13 | public static var largeIcon: UnsafeMutablePointer! 14 | } 15 | -------------------------------------------------------------------------------- /Sources/Palico/ImGui/ImGuizmo+Enum.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ImGuizmo+Enum.swift 3 | // Palico 4 | // 5 | // Created by Junhao Wang on 1/14/22. 6 | // 7 | 8 | public enum ImGuizmoType: Int { 9 | case none = -1 10 | case translate = 0 11 | case rotate = 1 12 | case scale = 2 13 | case bounds = 3 14 | } 15 | 16 | public enum ImGuizmoMode: Int { 17 | case local = 0 18 | case world = 1 19 | } 20 | -------------------------------------------------------------------------------- /Sources/Palico/Scene/Component+Camera.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Component+Camera.swift 3 | // Palico 4 | // 5 | // Created by Junhao Wang on 1/9/22. 6 | // 7 | 8 | public class CameraComponent: Component { 9 | public var title: String { "Camera" } 10 | public var enabled: Bool = true 11 | public static var icon: String { FAIcon.camera } 12 | 13 | // TODO: SceneCamera 14 | 15 | public required init() { } 16 | } 17 | -------------------------------------------------------------------------------- /Sources/Palico/Platform/Cocoa/CocoaWindow+View.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CocoaWindow+View.swift 3 | // Palico 4 | // 5 | // Created by Junhao Wang on 1/2/22. 6 | // 7 | 8 | import MathLib 9 | 10 | extension CocoaWindow { 11 | func onViewDraw() { 12 | windowDelegate?.onUpdate() 13 | } 14 | 15 | func onViewResize(size: Int2) { 16 | let event = WindowViewResizeEvent(size: size) 17 | publishEvent(event) 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /docs/UserGuide.md: -------------------------------------------------------------------------------- 1 | # 📝 User Guide 2 | 3 | Mouse Controls: 4 | - `Command + Left`: Rotate camera 5 | - `Right`: Look around 6 | - `Middle`: Pan 7 | - `Scroll`: Zoom in/out 8 | 9 | Keyboard Controls: 10 | - `Tab`: Select next in-scene object 11 | - `F`: Focus on object 12 | - `Q`: No action 13 | - `W`: Translate 14 | - `E`: Rotate 15 | - `R`: Scale 16 | 17 | Create GameObject: 18 |

19 | Palico Engine Screenshot 20 |

21 | -------------------------------------------------------------------------------- /Sources/Palico/Input/MouseCode.swift: -------------------------------------------------------------------------------- 1 | // 2 | // MouseCode.swift 3 | // Palico 4 | // 5 | // Created by Junhao Wang on 12/19/21. 6 | // 7 | 8 | public typealias MouseCode = UInt16 9 | 10 | public enum Mouse: MouseCode { 11 | // Currently only supports Cocoa framework 12 | case left = 0 13 | case right = 1 14 | case middle = 2 15 | case button3 = 3 16 | case button4 = 4 17 | case button5 = 5 18 | case button6 = 6 19 | case button7 = 7 20 | 21 | case unknown = 9 22 | } 23 | -------------------------------------------------------------------------------- /Sources/Editor/Editor.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Editor.swift 3 | // Editor 4 | // 5 | // Created by Junhao Wang on 12/16/21. 6 | // 7 | 8 | import Palico 9 | import MathLib 10 | 11 | class Editor: Application { 12 | override init(name: String = "Palico Editor", arguments: [String] = [], size: Int2 = [1280, 720]) { 13 | super.init(name: name, arguments: arguments, size: size) 14 | 15 | let editorLayer = EditorLayer(name: "Editor Layer") 16 | pushLayer(editorLayer) 17 | 18 | editorLayer.showDebugScene() 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /Sources/Editor/Panels/ImGuiDemoPanel.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ImGuiDemoPanel.swift 3 | // Editor 4 | // 5 | // Created by Junhao Wang on 1/4/22. 6 | // 7 | 8 | import Palico 9 | import ImGui 10 | 11 | class ImGuiDemoPanel: Panel { 12 | var panelName: String { "ImGui Demo" } 13 | 14 | var open: Bool = true 15 | // var open: Bool = false 16 | 17 | func onImGuiRender() { 18 | if open { 19 | ImGuiShowDemoWindow(&open) 20 | // ImGuiShowMetricsWindow(nil) 21 | // ImGuiShowStyleEditor(nil) 22 | } 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /Sources/Palico/Scene/Component+Script.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Component+Script.swift 3 | // Palico 4 | // 5 | // Created by Junhao Wang on 1/13/22. 6 | // 7 | 8 | public class ScriptComponent: Component { 9 | public var title: String { "Script" } 10 | public var enabled: Bool = true 11 | public static var icon: String { FAIcon.code } 12 | 13 | public var nativeScript: NativeScript? = nil 14 | 15 | public required init() { } 16 | 17 | public init(_ nativeScript: NativeScript) { 18 | self.nativeScript = nativeScript 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /Sources/Palico/ImGui/ImGuiBackend+Cocoa.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ImGuiBackend+Cocoa.swift 3 | // Palico 4 | // 5 | // Created by Junhao Wang on 12/26/21. 6 | // 7 | 8 | import Cocoa 9 | 10 | class ImGuiBackendCocoaPlatform: ImGuiBackendPlatformDelegate { 11 | init() { } 12 | 13 | func implPlatformInit() { 14 | ImGui_ImplOSX_Init(MetalContext.view) 15 | } 16 | 17 | func implPlatformNewFrame() { 18 | ImGui_ImplOSX_NewFrame(MetalContext.view) 19 | } 20 | 21 | func implPlatformShutdown() { 22 | ImGui_ImplOSX_Shutdown() 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /Sources/Editor/Panels/Panel.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Panel.swift 3 | // Editor 4 | // 5 | // Created by Junhao Wang on 1/4/22. 6 | // 7 | 8 | import Palico 9 | 10 | protocol Panel: AnyObject { 11 | var panelName: String { get } 12 | 13 | func onAttach() 14 | func onUpdate(deltaTime ts: Timestep) 15 | func onImGuiRender() 16 | func onEvent(event: Event) 17 | } 18 | 19 | // Optional 20 | extension Panel { 21 | func onAttach() { 22 | 23 | } 24 | 25 | func onUpdate(deltaTime ts: Timestep) { 26 | 27 | } 28 | 29 | func onEvent(event: Event) { 30 | 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /Sources/Palico/Platform/GLFW/GlfwInput.swift: -------------------------------------------------------------------------------- 1 | // 2 | // GlfwInput.swift 3 | // Palico 4 | // 5 | // Created by Junhao Wang on 12/26/21. 6 | // 7 | 8 | /* 9 | import CGLFW3 10 | import MathLib 11 | 12 | class GlfwInput: InputDelegate { 13 | var mousePos: Float2 { 14 | return Float2(0, 0) 15 | } 16 | 17 | init() { } 18 | 19 | func isPressed(key: Key) -> Bool { 20 | return false 21 | } 22 | 23 | func isPressed(mouse: Mouse) -> Bool { 24 | return false 25 | } 26 | 27 | func updateKeyMap(with flag: Bool, on keyCode: KeyCode) { 28 | 29 | } 30 | 31 | func updateMouseMap(with flag: Bool, on mouseCode: MouseCode) { 32 | 33 | } 34 | } 35 | */ 36 | -------------------------------------------------------------------------------- /Sources/Palico/Core/Window.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Window.swift 3 | // Palico 4 | // 5 | // Created by Junhao Wang on 12/15/21. 6 | // 7 | 8 | struct WindowDescriptor { 9 | var title: String 10 | var width: Int 11 | var height: Int 12 | 13 | init(title: String = "Palico Engine", width: Int, height: Int) { 14 | self.title = title 15 | self.width = width 16 | self.height = height 17 | } 18 | } 19 | 20 | /* Conformed by Cocoa, GLFW, etc */ 21 | protocol Window: AnyObject { 22 | var title: String { get } 23 | var width: Int { get } 24 | var height: Int { get } 25 | var isMinimized: Bool { get } 26 | 27 | var windowDelegate: WindowDelegate? { get set } 28 | 29 | init(descriptor: WindowDescriptor) 30 | } 31 | 32 | protocol WindowDelegate: AnyObject { 33 | func onEvent(event: Event) 34 | func onUpdate() 35 | } 36 | -------------------------------------------------------------------------------- /Sources/Palico/Renderer/RenderPass+Action.swift: -------------------------------------------------------------------------------- 1 | // 2 | // RenderPass+Action.swift 3 | // Palico 4 | // 5 | // Created by Junhao Wang on 1/5/22. 6 | // 7 | 8 | import Metal 9 | 10 | public enum RenderPassBeginAction { 11 | case clear 12 | case keep 13 | case dontCare 14 | } 15 | 16 | public enum RenderPassEndAction { 17 | case store 18 | case dontCare 19 | } 20 | 21 | func convertMTLLoadAction(_ action: RenderPassBeginAction) -> MTLLoadAction { 22 | switch action { 23 | case .clear: 24 | return .clear 25 | case .keep: 26 | return .load 27 | case .dontCare: 28 | return .dontCare 29 | } 30 | } 31 | 32 | func convertMTLStoreAction(_ action: RenderPassEndAction) -> MTLStoreAction { 33 | switch action { 34 | case .store: 35 | return .store 36 | case .dontCare: 37 | return .dontCare 38 | } 39 | } 40 | 41 | -------------------------------------------------------------------------------- /Sources/Editor/Panels/ScenePanel.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ScenePanel.swift 3 | // Editor 4 | // 5 | // Created by Junhao Wang on 1/4/22. 6 | // 7 | 8 | import Palico 9 | import ImGui 10 | import MothECS 11 | 12 | class ScenePanel: Panel { 13 | var panelName: String { "No Name" } // This panel contains two sub-panels 14 | var hierarchyPanelName: String { "Scene Hierarchy" } 15 | var inspectorPanelName: String { "Inspector" } 16 | 17 | var debugCursor: Int = -1 18 | 19 | var scene: Scene = Scene() 20 | var selectedEntityID: MothEntityID = .invalid 21 | 22 | func onUpdate(deltaTime ts: Timestep) { 23 | scene.onUpdateRuntime(deltaTime: ts) 24 | } 25 | 26 | func onUpdateEditor(deltaTime ts: Timestep) { 27 | scene.onUpdateEditor(deltaTime: ts) 28 | } 29 | 30 | func onImGuiRender() { 31 | drawHierarchyPanel() 32 | drawInspectorPanel() 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /Sources/Palico/Core/Time.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Time.swift 3 | // Palico 4 | // 5 | // Created by Junhao Wang on 12/17/21. 6 | // 7 | 8 | import Foundation 9 | 10 | public typealias Timestep = Float 11 | 12 | extension Timestep { 13 | public var toMilliSeconds: Timestep { 14 | get { self * 1000.0 } 15 | } 16 | } 17 | 18 | public enum Time { 19 | public private(set) static var lastFrameTime: Double = currentTime 20 | 21 | public static var currentTime: Double { get { 22 | PlatformContext.currentTime 23 | 24 | }} 25 | 26 | public static var deltaTime: Timestep { get { 27 | _deltaTime 28 | }} 29 | 30 | private static var _deltaTime: Timestep = 0.0 31 | 32 | // Used only internally - cannot be called by client (eg. Editor) 33 | internal static func update() { 34 | let time: Double = currentTime 35 | _deltaTime = Timestep(time - lastFrameTime) 36 | lastFrameTime = time 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /Sources/Editor/Scripts/RotateScript.swift: -------------------------------------------------------------------------------- 1 | // 2 | // RotateScript.swift 3 | // Editor 4 | // 5 | // Created by Junhao Wang on 1/15/22. 6 | // 7 | 8 | import Palico 9 | 10 | class RotateScript: NativeScript { 11 | var rotateSpeed: Float = 0.5 12 | 13 | override init(name: String = "RotateScript") { 14 | super.init(name: name) 15 | } 16 | 17 | init(name: String = "RotateScript", speed: Float) { 18 | rotateSpeed = speed 19 | super.init(name: name) 20 | } 21 | 22 | override func onCreate() { 23 | Console.info("[\(name)] Called onCreate() in native script on \(gameObjectName) [EntityID: \(entityID)]") 24 | } 25 | 26 | override func onUpdate(deltaTime ts: Timestep) { 27 | 28 | } 29 | 30 | override func onUpdateEditor(deltaTime ts: Timestep) { 31 | let transform = getComponent(TransformComponent.self) 32 | transform.rotation.y += rotateSpeed * Time.deltaTime // or you can use ts 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /Sources/Editor/Utils/FileUtils.swift: -------------------------------------------------------------------------------- 1 | // 2 | // FileUtils.swift 3 | // Editor 4 | // 5 | // Created by Junhao Wang on 12/28/21. 6 | // 7 | 8 | import Foundation 9 | 10 | public enum FileUtils { 11 | public static func getURL(path: String) -> URL? { 12 | guard let filePathURL = URL(string: path) else { 13 | assertionFailure("Invalid filepath: \(path)") 14 | return nil 15 | } 16 | 17 | let directory = filePathURL.deletingLastPathComponent().path 18 | let filename = filePathURL.deletingPathExtension().lastPathComponent 19 | let filenameExtension = filePathURL.pathExtension 20 | 21 | guard let shaderURL = Bundle.module.url(forResource: filename, 22 | withExtension: filenameExtension, 23 | subdirectory: directory) else { 24 | assertionFailure("Invalid filepath: \(path)") 25 | return nil 26 | } 27 | 28 | return shaderURL 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /Sources/Palico/Utils/FileUtils.swift: -------------------------------------------------------------------------------- 1 | // 2 | // FileUtils.swift 3 | // Palico 4 | // 5 | // Created by Junhao Wang on 12/28/21. 6 | // 7 | 8 | import Foundation 9 | 10 | public enum FileUtils { 11 | public static func getURL(path: String) -> URL? { 12 | guard let filePathURL = URL(string: path) else { 13 | assertionFailure("Invalid filepath: \(path)") 14 | return nil 15 | } 16 | 17 | let directory = filePathURL.deletingLastPathComponent().path 18 | let filename = filePathURL.deletingPathExtension().lastPathComponent 19 | let filenameExtension = filePathURL.pathExtension 20 | 21 | guard let shaderURL = Bundle.module.url(forResource: filename, 22 | withExtension: filenameExtension, 23 | subdirectory: directory) else { 24 | assertionFailure("Invalid filepath: \(path)") 25 | return nil 26 | } 27 | 28 | return shaderURL 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /Sources/Palico/Scene/Component+Light.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Component+Light.swift 3 | // Palico 4 | // 5 | // Created by Junhao Wang on 1/9/22. 6 | // 7 | 8 | public class LightComponent: Component { 9 | public var title: String { "Light" } 10 | public var enabled: Bool = true 11 | public static var icon: String { FAIcon.lightbulb } 12 | 13 | public var light: Light = DirectionalLight() 14 | 15 | public required init() { } 16 | 17 | public init(type: LightType) { 18 | setLightType(type) 19 | } 20 | 21 | public func setLightType(_ type: LightType) { 22 | let color = light.color 23 | let intensity = light.intensity 24 | 25 | switch type { 26 | case .dirLight: 27 | light = DirectionalLight() 28 | case .pointLight: 29 | light = PointLight() 30 | case .spotLight: 31 | light = SpotLight() 32 | case .ambientLight: 33 | light = AmbientLight() 34 | } 35 | 36 | light.color = color 37 | light.intensity = intensity 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 Junhao Wang (Forkercat) 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /Sources/Palico/Scene/Component+Tag.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Component+Tag.swift 3 | // Palico 4 | // 5 | // Created by Junhao Wang on 1/9/22. 6 | // 7 | 8 | public class TagComponent: Component { 9 | public var title: String { "Tag" } 10 | public var enabled: Bool = true { 11 | didSet { 12 | enabled = true 13 | Log.error("You cannot set tag component status! Skipping") 14 | } 15 | } 16 | public static var icon: String { FAIcon.tag } 17 | 18 | public enum Tag: Int { 19 | case `default` = 0 20 | case player = 1 21 | case enemy = 2 22 | case light = 3 23 | case camera = 4 24 | case script = 5 25 | case skybox = 6 26 | 27 | public static let tagStrings: [String] = [ 28 | "Default", "Player", "Enemy", "Light", "Camera", "Script", "Skybox" 29 | ] 30 | public static var tagStringsWithIcon: [String] { 31 | return tagStrings.map { "\(FAIcon.tag) \($0)" } 32 | } 33 | } 34 | 35 | public var tag: Tag = .default 36 | 37 | public required init() { } 38 | } 39 | -------------------------------------------------------------------------------- /Sources/Palico/Renderer/Shader.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Shader.swift 3 | // Palico 4 | // 5 | // Created by Junhao Wang on 12/28/21. 6 | // 7 | 8 | import Metal 9 | 10 | public class Shader { 11 | let name: String 12 | let filepath: String 13 | var source: String? = nil 14 | var isCompiled: Bool = false 15 | 16 | init(name: String, url: URL) { 17 | self.name = name 18 | self.filepath = url.path 19 | 20 | do { 21 | let source = try String(contentsOf: url) 22 | self.source = source 23 | } catch let error { 24 | assertionFailure(error.localizedDescription) 25 | } 26 | } 27 | 28 | required init(name: String, source: String) { 29 | self.name = name 30 | self.filepath = "" 31 | self.source = source 32 | } 33 | 34 | static func compile(source: String) { 35 | do { 36 | let library = try MetalContext.device.makeLibrary(source: source, options: nil) 37 | MetalContext.updateShaderLibrary(library) 38 | } catch let error { 39 | assertionFailure("Failed shader compilation: \(error.localizedDescription)") 40 | } 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /Sources/Palico/Platform/Cocoa/CocoaContext.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CocoaContext.swift 3 | // Palico 4 | // 5 | // Created by Junhao Wang on 12/25/21. 6 | // 7 | 8 | import Cocoa 9 | 10 | class AppDelegate: NSObject, NSApplicationDelegate { 11 | func applicationWillFinishLaunching(_ notification: Notification) { } 12 | func applicationShouldTerminateAfterLastWindowClosed(_ sender: NSApplication) -> Bool { true } 13 | } 14 | 15 | class CocoaContext: PlatformContextDelegate { 16 | let appDelegate: AppDelegate = AppDelegate() 17 | 18 | init() { } 19 | 20 | var osName: String { "macOS" } 21 | var platformName: String { "Cocoa" } 22 | var isAppRunning: Bool { NSApp.isRunning } 23 | var isAppActive: Bool { NSApp.isActive } 24 | var currentTime: Double { CFAbsoluteTimeGetCurrent() } 25 | 26 | func initialize() { 27 | _ = NSApplication.shared 28 | NSApp.delegate = appDelegate 29 | NSApp.setActivationPolicy(.regular) 30 | } 31 | 32 | func activate() { 33 | NSApp.activate(ignoringOtherApps: true) 34 | NSApp.run() 35 | } 36 | 37 | func deinitialize() { 38 | NSApp.deactivate() 39 | NSApp.stop(nil) 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /Sources/Palico/Event/AppEvent.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AppEvent.swift 3 | // Palico 4 | // 5 | // Created by Junhao Wang on 12/19/21. 6 | // 7 | 8 | import MathLib 9 | 10 | internal protocol AppEvent: Event { } 11 | 12 | // WindowViewResize 13 | public class WindowViewResizeEvent: AppEvent { 14 | public let size: Int2 15 | 16 | public static var staticEventType: EventType { .windowViewResize } 17 | 18 | public var eventType: EventType { Self.staticEventType } 19 | public var categoryFlags: EventCategory { [.application] } 20 | public var handled: Bool = false 21 | 22 | public init(size: Int2) { 23 | self.size = size 24 | } 25 | 26 | public var description: String { 27 | "[Event] type=WindowViewResize, size=(\(size.width) x \(size.height)), handled=\(handled)" 28 | } 29 | } 30 | 31 | // WindowClose 32 | public class WindowCloseEvent: AppEvent { 33 | public static var staticEventType: EventType { .windowClose } 34 | 35 | public var categoryFlags: EventCategory { [.application] } 36 | public var eventType: EventType { Self.staticEventType } 37 | public var handled: Bool = false 38 | 39 | public init() { } 40 | 41 | public var description: String { 42 | "[Event] type=WindowClose, handled=\(handled)" 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /Sources/Palico/Input/Input.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Input.swift 3 | // Palico 4 | // 5 | // Created by Junhao Wang on 12/19/21. 6 | // 7 | 8 | import MathLib 9 | 10 | protocol InputDelegate: AnyObject { 11 | var mousePos: Float2 { get } 12 | 13 | func isPressed(key: Key) -> Bool 14 | func isPressed(mouse: Mouse) -> Bool 15 | 16 | func updateKeyMap(with flag: Bool, on keyCode: KeyCode) 17 | func updateMouseMap(with flag: Bool, on mouseCode: MouseCode) 18 | } 19 | 20 | public struct Input { 21 | private static let inputDelegate = CocoaInput() 22 | 23 | public static var mousePos: Float2 { get { 24 | return Self.inputDelegate.mousePos 25 | }} 26 | 27 | public static func isPressed(key: Key) -> Bool { 28 | return Self.inputDelegate.isPressed(key: key) 29 | } 30 | 31 | public static func isPressed(mouse: Mouse) -> Bool { 32 | return Self.inputDelegate.isPressed(mouse: mouse) 33 | } 34 | 35 | // Internal only 36 | static func updateKeyMap(with flag: Bool, on keyCode: KeyCode) { 37 | Self.inputDelegate.updateKeyMap(with: flag, on: keyCode) 38 | } 39 | 40 | static func updateMouseMap(with flag: Bool, on mouseCode: MouseCode) { 41 | Self.inputDelegate.updateMouseMap(with: flag, on: mouseCode) 42 | } 43 | 44 | private init() { } 45 | } 46 | -------------------------------------------------------------------------------- /Sources/Palico/Platform/GLFW/GlfwContext.swift: -------------------------------------------------------------------------------- 1 | // 2 | // GlfwContext.swift 3 | // Palico 4 | // 5 | // Created by Junhao Wang on 12/26/21. 6 | // 7 | 8 | /* 9 | import CGLFW3 10 | 11 | fileprivate struct GLFWInt32 { 12 | static var FALSE: Int32 = 0 13 | static var TRUE: Int32 = 1 14 | } 15 | 16 | class GlfwContext: PlatformContextDelegate { 17 | init() { } 18 | 19 | var osName: String { "macOS" } 20 | var osVersion: String { "" } 21 | var platformName: String { "GLFW" } 22 | var isAppRunning: Bool { isRunning } 23 | var isAppActive: Bool { isActive } 24 | var currentTime: Double { glfwGetTime() } 25 | 26 | private var isRunning: Bool = false 27 | private var isActive: Bool = false 28 | 29 | func initialize() { 30 | let success = glfwInit() 31 | assert(success != 0, "Could not initialize GLFW!") 32 | glfwSetErrorCallback{ (error: Int32, description: UnsafePointer?) in 33 | let str = String(cString: description!) 34 | assertionFailure("GLFW Error \(error): \(str)") 35 | } 36 | 37 | glfwWindowHint(GLFW_CLIENT_API, GLFW_NO_API) 38 | glfwWindowHint(GLFW_RESIZABLE, GLFWInt32.TRUE) // by default 39 | } 40 | 41 | func activate() { 42 | isRunning = true 43 | } 44 | 45 | func deinitialize() { 46 | isRunning = false 47 | glfwTerminate() 48 | } 49 | } 50 | */ 51 | -------------------------------------------------------------------------------- /Sources/Palico/Renderer/RenderPassPool.swift: -------------------------------------------------------------------------------- 1 | // 2 | // RenderPassPool.swift 3 | // Palico 4 | // 5 | // Created by Junhao Wang on 1/3/22. 6 | // 7 | 8 | import Metal 9 | import MathLib 10 | 11 | public enum RenderPassType { 12 | case colorPass 13 | case shadowPass 14 | case geometryPass 15 | } 16 | 17 | class RenderPassPool { 18 | static var shared: RenderPassPool = RenderPassPool() 19 | 20 | private let colorPass: RenderPass 21 | private let shadowPass: RenderPass 22 | private let geometryPass: RenderPass 23 | 24 | private init(size: Int2 = [1, 1]) { 25 | colorPass = RenderPass(name: "ColorPass", size: size, targets: [.color, .depth]) 26 | shadowPass = RenderPass(name: "ShadowPass", size: size, targets: [.depth]) 27 | geometryPass = RenderPass(name: "GeometryPass", size: size, targets: [.normal, .position]) 28 | } 29 | 30 | /* 31 | func resizeAllRenderPasses(size: Int2) { 32 | colorPass.resizeTextures(size: size) 33 | shadowPass.resizeTextures(size: size) 34 | geometryPass.resizeTextures(size: size) 35 | } 36 | */ 37 | 38 | func fetchRenderPass(type: RenderPassType) -> RenderPass { 39 | switch type { 40 | case .colorPass: 41 | return colorPass 42 | case .shadowPass: 43 | return shadowPass 44 | case .geometryPass: 45 | return geometryPass 46 | } 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /Sources/Palico/Scene/Scene+View.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Scene+View.swift 3 | // Palico 4 | // 5 | // Created by Junhao Wang on 1/13/22. 6 | // 7 | 8 | extension Scene { 9 | public func view(_ type: T.Type) -> [GameObject] { 10 | return moth.view(type).map({ gameObjectMap[$0]! }) 11 | } 12 | 13 | public func view(_ type1: T1.Type, _ type2: T2.Type) 14 | -> [GameObject] { 15 | return moth.view(type1, type2).map({ gameObjectMap[$0]! }) 16 | } 17 | 18 | public func view(_ type1: T1.Type, _ type2: T2.Type, _ type3: T3.Type) 19 | -> [GameObject] { 20 | return moth.view(type1, type2, type3).map({ gameObjectMap[$0]! }) 21 | } 22 | 23 | // + Excepts 24 | public func view(excepts exceptType: T.Type) 25 | -> [GameObject] { 26 | return moth.view(excepts: exceptType).map({ gameObjectMap[$0]! }) 27 | } 28 | 29 | public func view(_ type: T1.Type, excepts exceptType: T2.Type) 30 | -> [GameObject] { 31 | return moth.view(type, type, excepts: exceptType).map({ gameObjectMap[$0]! }) 32 | } 33 | 34 | public func view(_ type1: T1.Type, _ type2: T2.Type, excepts exceptType: T3.Type) 35 | -> [GameObject] { 36 | return moth.view(type1, type2, excepts: exceptType).map({ gameObjectMap[$0]! }) 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /Sources/Palico/Platform/Cocoa/CocoaInput.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CocoaInput.swift 3 | // Palico 4 | // 5 | // Created by Junhao Wang on 12/25/21. 6 | // 7 | 8 | import Cocoa 9 | import MathLib 10 | 11 | class CocoaInput: InputDelegate { 12 | private var keyMap: [Bool] = [Bool](repeating: false, count: 512) 13 | private var mouseMap: [Bool] = [Bool](repeating: false, count: 10) 14 | 15 | var mousePos: Float2 { 16 | // Becomes (0, 0) when app is not active (hidden) - not sure if it causes issue yet 17 | let locationInWindow = NSApp.mainWindow?.convertPoint(fromScreen: NSEvent.mouseLocation) ?? NSPoint(x: 0, y: 0) 18 | return Float2(locationInWindow.x, locationInWindow.y) 19 | } 20 | 21 | init() { } 22 | 23 | func isPressed(key: Key) -> Bool { 24 | let keycode = Int(key.rawValue) 25 | keyMap[keycode] = keyMap[keycode] && NSApp.isActive // set false when the app is hidden 26 | return keyMap[keycode] 27 | } 28 | 29 | func isPressed(mouse: Mouse) -> Bool { 30 | let mousecode = Int(mouse.rawValue) 31 | mouseMap[mousecode] = mouseMap[mousecode] && NSApp.isActive // set false when the app is hidden 32 | return mouseMap[mousecode] 33 | } 34 | 35 | func updateKeyMap(with flag: Bool, on keyCode: KeyCode) { 36 | keyMap[Int(keyCode)] = flag 37 | } 38 | 39 | func updateMouseMap(with flag: Bool, on mouseCode: MouseCode) { 40 | mouseMap[Int(mouseCode)] = flag 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /Sources/Palico/Renderer/MetalContext.swift: -------------------------------------------------------------------------------- 1 | // 2 | // MetalContext.swift 3 | // Palico 4 | // 5 | // Created by Junhao Wang on 12/26/21. 6 | // 7 | 8 | import MetalKit 9 | 10 | class MetalContext { 11 | private(set) static var device: MTLDevice! = nil 12 | private(set) static var commandQueue: MTLCommandQueue! = nil 13 | private(set) static var library: MTLLibrary! = nil 14 | 15 | private(set) static var view: MTKView! = nil 16 | 17 | static var apiName: String { get { "Metal" }} 18 | static var deviceName: String { get { device?.name ?? "Unknown" } } 19 | static var dpi: Float { Float(Self.view?.window?.screen?.backingScaleFactor ?? 1.0) } 20 | 21 | static func initialize() { 22 | guard let device = MTLCreateSystemDefaultDevice(), 23 | let queue = device.makeCommandQueue() 24 | else { 25 | assertionFailure("Device and command queue are not properly initialized!") 26 | return 27 | } 28 | Self.device = device 29 | Self.commandQueue = queue 30 | } 31 | 32 | func deinitialize() { 33 | Self.device = nil 34 | Self.commandQueue = nil 35 | Self.library = nil 36 | } 37 | 38 | static func updateShaderLibrary(_ library: MTLLibrary) { 39 | Self.library = library 40 | } 41 | 42 | static func updateMTKView(_ mtkView: MTKView) { 43 | Self.view = mtkView 44 | } 45 | 46 | static func setPreferredFps(_ fps: Int) { 47 | Self.view?.preferredFramesPerSecond = fps 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /Sources/Palico/Core/PlatformContext.swift: -------------------------------------------------------------------------------- 1 | // 2 | // PlatformContext.swift 3 | // Palico 4 | // 5 | // Created by Junhao Wang on 12/25/21. 6 | // 7 | 8 | protocol PlatformContextDelegate: Any { 9 | var osName: String { get } 10 | var platformName: String { get } 11 | var isAppRunning: Bool { get } 12 | var isAppActive: Bool { get } 13 | var currentTime: Double { get } 14 | 15 | func initialize() 16 | func activate() 17 | func deinitialize() 18 | } 19 | 20 | public struct PlatformContext { 21 | private static let contextDelegate = CocoaContext() 22 | 23 | public static var osName: String { get { 24 | return Self.contextDelegate.osName 25 | }} 26 | 27 | public static var platformName: String { get { 28 | return Self.contextDelegate.platformName 29 | }} 30 | 31 | public static var currentTime: Double { get { 32 | return Self.contextDelegate.currentTime 33 | }} 34 | 35 | public static var isAppRunning: Bool { get { 36 | return Self.contextDelegate.isAppRunning 37 | }} 38 | 39 | public static var isAppActive: Bool { get { 40 | return Self.contextDelegate.isAppActive 41 | }} 42 | 43 | public static func initialize() { 44 | return Self.contextDelegate.initialize() 45 | } 46 | 47 | public static func activate() { 48 | return Self.contextDelegate.activate() 49 | } 50 | 51 | public static func deinitialize() { 52 | return Self.contextDelegate.deinitialize() 53 | } 54 | 55 | private init() { } 56 | } 57 | -------------------------------------------------------------------------------- /Package.resolved: -------------------------------------------------------------------------------- 1 | { 2 | "object": { 3 | "pins": [ 4 | { 5 | "package": "MathLib", 6 | "repositoryURL": "https://github.com/forkercat/MathLib.git", 7 | "state": { 8 | "branch": "main", 9 | "revision": "439fa720cd73b285f9109dc12c4d781847e5f502", 10 | "version": null 11 | } 12 | }, 13 | { 14 | "package": "MothECS", 15 | "repositoryURL": "https://github.com/forkercat/MothECS.git", 16 | "state": { 17 | "branch": "main", 18 | "revision": "f954455d8aece6b4d0ec9ca9b3960d9360b3eb98", 19 | "version": null 20 | } 21 | }, 22 | { 23 | "package": "OhMyLog", 24 | "repositoryURL": "https://github.com/forkercat/OhMyLog.git", 25 | "state": { 26 | "branch": "main", 27 | "revision": "73a0a39360caca125af38eeb2a7045592e2268e4", 28 | "version": null 29 | } 30 | }, 31 | { 32 | "package": "ImGui", 33 | "repositoryURL": "https://github.com/forkercat/SwiftImGui.git", 34 | "state": { 35 | "branch": "update-1.86-docking", 36 | "revision": "336ee0c6649149a2199dc44b7c5f8e5ca1fc0abd", 37 | "version": null 38 | } 39 | }, 40 | { 41 | "package": "ImGuizmo", 42 | "repositoryURL": "https://github.com/forkercat/SwiftImGuizmo.git", 43 | "state": { 44 | "branch": "master", 45 | "revision": "b94f9e4c84f5f0769ba721dac6842e37074f81f9", 46 | "version": null 47 | } 48 | } 49 | ] 50 | }, 51 | "version": 1 52 | } 53 | -------------------------------------------------------------------------------- /Sources/Palico/ImGui/Backends/imgui_shaders.swift: -------------------------------------------------------------------------------- 1 | // 2 | // imgui_shaders.swift 3 | // 4 | // 5 | // Created by Christian Treffs on 01.09.19. 6 | // 7 | 8 | enum ImGuiShaders { 9 | static var `default`: String = """ 10 | #include 11 | using namespace metal; 12 | 13 | struct Uniforms { 14 | float4x4 projectionMatrix; 15 | }; 16 | 17 | struct VertexIn { 18 | float2 position [[attribute(0)]]; 19 | float2 texCoords [[attribute(1)]]; 20 | uchar4 color [[attribute(2)]]; 21 | }; 22 | 23 | struct VertexOut { 24 | float4 position [[position]]; 25 | float2 texCoords; 26 | float4 color; 27 | }; 28 | 29 | vertex VertexOut vertex_main(VertexIn in [[stage_in]], 30 | constant Uniforms &uniforms [[buffer(1)]]) { 31 | VertexOut out; 32 | out.position = uniforms.projectionMatrix * float4(in.position, 0, 1); 33 | out.texCoords = in.texCoords; 34 | out.color = float4(in.color) / float4(255.0); 35 | return out; 36 | } 37 | 38 | fragment half4 fragment_main(VertexOut in [[stage_in]], 39 | texture2d texture [[texture(0)]]) { 40 | constexpr sampler linearSampler(coord::normalized, min_filter::linear, mag_filter::linear, mip_filter::linear); 41 | half4 texColor = texture.sample(linearSampler, in.texCoords); 42 | return half4(in.color) * texColor; 43 | } 44 | """ 45 | } 46 | -------------------------------------------------------------------------------- /Sources/Palico/Scene/Component+MeshRenderer.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Component+MeshRenderer.swift 3 | // Palico 4 | // 5 | // Created by Junhao Wang on 1/9/22. 6 | // 7 | 8 | public class MeshRendererComponent: Component { 9 | public var title: String { "Mesh Renderer" } 10 | public var enabled: Bool = true 11 | public static var icon: String { FAIcon.vectorSquare } 12 | 13 | // Mesh 14 | public private(set) var mesh: Mesh? = nil 15 | public private(set) var meshType: PrimitiveType? = nil // TODO: Should be dedicated MeshType instead of PrimitiveTypes 16 | 17 | // Material 18 | public var tintColor: Color4 = .white 19 | 20 | public required init() { } 21 | 22 | public func setMesh(_ primitiveType: PrimitiveType?) { 23 | meshType = primitiveType 24 | 25 | guard let primitiveType = primitiveType else { 26 | mesh = nil 27 | return 28 | } 29 | 30 | switch primitiveType { 31 | case .cube: 32 | mesh = MeshFactory.makePrimitiveMesh(type: .cube) 33 | case .sphere: 34 | mesh = MeshFactory.makePrimitiveMesh(type: .sphere) 35 | case .hemisphere: 36 | mesh = MeshFactory.makePrimitiveMesh(type: .hemisphere) 37 | case .plane: 38 | mesh = MeshFactory.makePrimitiveMesh(type: .plane) 39 | case .capsule: 40 | mesh = MeshFactory.makePrimitiveMesh(type: .capsule) 41 | case .cylinder: 42 | mesh = MeshFactory.makePrimitiveMesh(type: .cylinder) 43 | case .cone: 44 | mesh = MeshFactory.makePrimitiveMesh(type: .cone) 45 | } 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /Sources/Palico/Script/NativeScript.swift: -------------------------------------------------------------------------------- 1 | // 2 | // NativeScript.swift 3 | // Palico 4 | // 5 | // Created by Junhao Wang on 1/15/22. 6 | // 7 | 8 | import MothECS 9 | 10 | open class NativeScript { 11 | open var name: String 12 | 13 | weak var gameObject: GameObject! = nil 14 | 15 | public var entityID: MothEntityID { 16 | assert(gameObject != nil, "Native script has nil game object!") 17 | return gameObject!.entityID 18 | } 19 | 20 | public var gameObjectName: String { 21 | assert(gameObject != nil, "Native script has nil game object!") 22 | return gameObject!.name 23 | } 24 | 25 | public init(name: String = "Unnamed Script") { 26 | self.name = name 27 | } 28 | 29 | public func getGameObject() -> GameObject { 30 | assert(gameObject != nil, "Native script has nil game object!") 31 | return gameObject! 32 | } 33 | 34 | public func hasComponent(_ type: T.Type) -> Bool { 35 | assert(gameObject != nil, "Native script has nil game object!") 36 | return gameObject.hasComponent(type) 37 | } 38 | 39 | public func getComponent(_ type: T.Type) -> T { 40 | assert(gameObject != nil, "Native script has nil game object!") 41 | return gameObject.getComponent(type) 42 | } 43 | 44 | // Overridden by users 45 | open func onCreate() { 46 | 47 | } 48 | 49 | open func onDestroy() { 50 | 51 | } 52 | 53 | open func onUpdate(deltaTime ts: Timestep) { 54 | 55 | } 56 | 57 | open func onUpdateEditor(deltaTime ts: Timestep) { 58 | 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /Sources/Palico/Assets/Fonts/FontAwesome5/LICENSE.txt: -------------------------------------------------------------------------------- 1 | Font Awesome Free License 2 | ------------------------- 3 | 4 | Font Awesome Free is free, open source, and GPL friendly. You can use it for 5 | commercial projects, open source projects, or really almost whatever you want. 6 | Full Font Awesome Free license: https://fontawesome.com/license/free. 7 | 8 | # Icons: CC BY 4.0 License (https://creativecommons.org/licenses/by/4.0/) 9 | In the Font Awesome Free download, the CC BY 4.0 license applies to all icons 10 | packaged as SVG and JS file types. 11 | 12 | # Fonts: SIL OFL 1.1 License (https://scripts.sil.org/OFL) 13 | In the Font Awesome Free download, the SIL OFL license applies to all icons 14 | packaged as web and desktop font files. 15 | 16 | # Code: MIT License (https://opensource.org/licenses/MIT) 17 | In the Font Awesome Free download, the MIT license applies to all non-font and 18 | non-icon files. 19 | 20 | # Attribution 21 | Attribution is required by MIT, SIL OFL, and CC BY licenses. Downloaded Font 22 | Awesome Free files already contain embedded comments with sufficient 23 | attribution, so you shouldn't need to do anything additional when using these 24 | files normally. 25 | 26 | We've kept attribution comments terse, so we ask that you do not actively work 27 | to remove them from files, especially code. They're a great way for folks to 28 | learn about Font Awesome. 29 | 30 | # Brand Icons 31 | All brand icons are trademarks of their respective owners. The use of these 32 | trademarks does not indicate endorsement of the trademark holder by Font 33 | Awesome, nor vice versa. **Please do not use brand logos for any purpose except 34 | to represent the company, product, or service to which they refer.** 35 | -------------------------------------------------------------------------------- /Sources/Palico/Scene/Component+Transform.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Component+Transform.swift 3 | // Palico 4 | // 5 | // Created by Junhao Wang on 1/9/22. 6 | // 7 | 8 | import MathLib 9 | 10 | public class TransformComponent: Component { 11 | public var title: String { "Transform" } 12 | public var enabled: Bool = true { 13 | didSet { 14 | enabled = true 15 | Log.error("You cannot set transform component status! Skipping") 16 | } 17 | } 18 | public static var icon: String { FAIcon.locationArrow } 19 | 20 | public var position: Float3 = [0, 0, 0] 21 | public var rotation: Float3 = [0, 0, 0] { 22 | didSet { 23 | let rotationMatrix = Float4x4(rotationXYZ: rotation) 24 | // let rotationMatrix = Float4x4(rotationZXY: rotation) // Follow Unity (Extransic Order) 25 | quaternion = Quaternion(rotationMatrix) 26 | } 27 | } 28 | public var scale: Float3 = [1, 1, 1] 29 | 30 | public var modelMatrix: Float4x4 { get { 31 | let T = Float4x4(translation: position) 32 | let R = Float4x4(quaternion) 33 | let S = Float4x4(scale: scale) 34 | return mul(T, mul(R, S)) 35 | }} 36 | 37 | public var rightDirection: Float3 { get { // X 38 | return quaternion.act(Float3.right) 39 | }} 40 | public var upDirection: Float3 { get { // Y 41 | return quaternion.act(Float3.up) 42 | }} 43 | public var forwardDirection: Float3 { get { // Z 44 | return quaternion.act(Float3.forward) 45 | }} 46 | 47 | // Used quaternion internally 48 | private var quaternion = Quaternion() 49 | 50 | public required init() { } 51 | } 52 | -------------------------------------------------------------------------------- /Sources/Palico/Assets/Shaders/Common.metal: -------------------------------------------------------------------------------- 1 | // 2 | // Common.metal 3 | // Palico 4 | // 5 | // Created by Junhao Wang on 1/6/22. 6 | // 7 | 8 | #include 9 | using namespace metal; 10 | 11 | namespace Palico { 12 | 13 | // Index 14 | enum Attribute { 15 | Position = 0, 16 | Normal = 1, 17 | UV = 2 18 | }; 19 | 20 | enum class Buffer { 21 | vertices = 0, 22 | vertexUniform = 11, 23 | fragmentUniform = 12, 24 | lightData = 13 25 | }; 26 | 27 | enum Texture { 28 | BaseColorTexture = 0, 29 | NormalTexture = 1, 30 | RoughnessTexture = 2, 31 | MetallicTexture = 3, 32 | AOTexture = 4 33 | }; 34 | 35 | // Uniforms 36 | struct VertexUniformData { 37 | float4x4 modelMatrix; 38 | float4x4 viewMatrix; 39 | float4x4 projectionMatrix; 40 | float3x3 normalMatrix; 41 | }; 42 | 43 | struct FragmentUniformData { 44 | float4 tintColor; 45 | float3 cameraPosition; 46 | uint lightCount; 47 | uint noLight; 48 | }; 49 | 50 | // Light 51 | enum LightType { 52 | DirLight = 0, 53 | PointLight = 1, 54 | SpotLight = 2, 55 | AmbientLight = 3 56 | }; 57 | 58 | struct LightData { 59 | LightType type; 60 | float3 position; 61 | float3 color; 62 | float intensity; 63 | float3 direction; 64 | float3 attenuation; 65 | float coneAngle; 66 | float3 coneDirection; 67 | float3 coneAttenuation; 68 | }; 69 | 70 | } // Palico 71 | -------------------------------------------------------------------------------- /Sources/Palico/Core/Layer.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Layer.swift 3 | // 4 | // 5 | // Created by Junhao Wang on 12/15/21. 6 | // 7 | 8 | // Layer 9 | open class Layer { 10 | var debugName: String = "Untitled Layer" 11 | 12 | public init() { } 13 | 14 | public init(name: String) { 15 | self.debugName = name 16 | } 17 | 18 | open func onAttach() { } 19 | open func onDetach() { } 20 | open func onUpdate(deltaTime ts: Timestep) { } 21 | open func onImGuiRender() { } 22 | open func onEvent(event: Event) { } 23 | } 24 | 25 | extension Layer: Equatable { 26 | public static func ==(lhs: Layer, rhs: Layer) -> Bool { 27 | return lhs === rhs 28 | } 29 | } 30 | 31 | // LayerStack 32 | class LayerStack { 33 | private(set) var layers: [Layer] = [] 34 | private var layerInsertIndex = 0 35 | 36 | init() { } 37 | 38 | deinit { 39 | layers.forEach { layer in 40 | layer.onDetach() 41 | } 42 | } 43 | 44 | func pushLayer(_ layer: Layer) { 45 | layers.insert(layer, at: layerInsertIndex) 46 | layerInsertIndex += 1 47 | } 48 | 49 | func popLayer(_ layer: Layer) { 50 | guard let index = layers.firstIndex(of: layer) else { 51 | Log.warn("LayerStack::popLayer::Layer not found in the stack!") 52 | return 53 | } 54 | layers.remove(at: index) 55 | layerInsertIndex -= 1 56 | } 57 | 58 | func pushOverlay(_ overlay: Layer) { 59 | layers.append(overlay) 60 | } 61 | 62 | func popOverlay(_ overlay: Layer) { 63 | guard let index = layers.firstIndex(of: overlay) else { 64 | Log.warn("LayerStack::popOverlay::Overlay not found in the stack!") 65 | return 66 | } 67 | layers.remove(at: index) 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /Sources/Palico/ImGui/ImGuiBackend.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ImGuiContext.swift 3 | // Palico 4 | // 5 | // Created by Junhao Wang on 12/26/21. 6 | // 7 | 8 | import ImGui 9 | 10 | protocol ImGuiBackendPlatformDelegate: AnyObject { 11 | func implPlatformInit() 12 | func implPlatformNewFrame() 13 | func implPlatformShutdown() 14 | } 15 | 16 | protocol ImGuiBackendGraphicsDelegate: AnyObject { 17 | func implGraphicsInit() 18 | func implGraphicsNewFrame() 19 | func implGraphicsShutdown() 20 | func implGraphicsRenderDrawData(_ drawData: ImDrawData) 21 | } 22 | 23 | public struct ImGuiBackend { 24 | private static var platformDelegate: ImGuiBackendCocoaPlatform! = nil 25 | private static var graphicsDelegate: ImGuiBackendMetalGraphics! = nil 26 | 27 | private init() { } 28 | 29 | public static func initialize() { 30 | platformDelegate = ImGuiBackendCocoaPlatform() 31 | graphicsDelegate = ImGuiBackendMetalGraphics() 32 | } 33 | 34 | // Platform 35 | public static func implPlatformInit() { 36 | return platformDelegate.implPlatformInit() 37 | } 38 | 39 | public static func implPlatformNewFrame() { 40 | return platformDelegate.implPlatformNewFrame() 41 | } 42 | 43 | public static func implPlatformShutdown() { 44 | return platformDelegate.implPlatformShutdown() 45 | } 46 | 47 | // Graphics 48 | public static func implGraphicsInit() { 49 | return graphicsDelegate.implGraphicsInit() 50 | } 51 | 52 | public static func implGraphicsNewFrame() { 53 | return graphicsDelegate.implGraphicsNewFrame() 54 | } 55 | 56 | public static func implGraphicsShutdown() { 57 | return graphicsDelegate.implGraphicsShutdown() 58 | } 59 | 60 | public static func implGraphicsRenderDrawData(_ drawData: ImDrawData) { 61 | return graphicsDelegate.implGraphicsRenderDrawData(drawData) 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /Sources/Palico/Scene/Scene.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Scene.swift 3 | // Palico 4 | // 5 | // Created by Junhao Wang on 12/26/21. 6 | // 7 | 8 | import MathLib 9 | import MothECS 10 | 11 | public class Scene { 12 | public var bgColor: Color4 = .black 13 | 14 | let moth: Moth = Moth() 15 | var gameObjectMap: [MothEntityID: GameObject] = [:] 16 | var viewportSize: Int2 = [0, 0] 17 | 18 | public var gameObjectList: [GameObject] { get { 19 | var list: [GameObject] = [] 20 | for entityID in moth.entityIDs { 21 | list.append(gameObjectMap[entityID]!) 22 | } 23 | return list 24 | }} 25 | 26 | public init() { 27 | bgColor = Color4(r: 0.13, g: 0.13, b: 0.13, a: 1.0) 28 | } 29 | } 30 | 31 | // Update 32 | extension Scene { 33 | // Editor 34 | public func onUpdateEditor(deltaTime ts: Timestep) { 35 | for gameObject in gameObjectMap.values { 36 | gameObject.onUpdateEditor(deltaTime: ts) 37 | } 38 | } 39 | 40 | public func onRenderEditor(deltaTime ts: Timestep, editorCamera: EditorCamera) { 41 | Renderer.beginRenderPass(type: .colorPass, 42 | begin: .clear, 43 | clearColor: bgColor) 44 | // Setup 45 | Renderer.preRenderSetup(scene: self, camera: editorCamera) 46 | 47 | Renderer.render(scene: self) 48 | 49 | Renderer.endRenderPass() 50 | } 51 | 52 | // Runtime 53 | public func onUpdateRuntime(deltaTime ts: Timestep) { 54 | for gameObject in gameObjectMap.values { 55 | gameObject.onUpdateRuntime(deltaTime: ts) 56 | } 57 | } 58 | 59 | public func onRenderRuntime(deltaTime ts: Timestep) { 60 | // TODO: Play Mode 61 | } 62 | 63 | public func onViewportResize(size: Int2) { 64 | viewportSize = size 65 | // TODO: Resize scene camera. Needed? 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /Sources/Palico/Event/Event.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Event.swift 3 | // Palico 4 | // 5 | // Created by Junhao Wang on 12/19/21. 6 | // 7 | 8 | import Darwin 9 | 10 | public enum EventUtils { 11 | // Check if an event's category (could be multiple values) has a specific category. 12 | public static func isInCategory(event: Event, category: EventCategory) -> Bool { 13 | return event.categoryFlags.contains(category) 14 | } 15 | } 16 | 17 | public enum EventType { 18 | case none 19 | case windowViewResize, windowClose 20 | case keyPressed, keyReleased, charTyped 21 | case mouseButtonPressed, mouseButtonReleased, mouseMoved, mouseScrolled 22 | } 23 | 24 | public struct EventCategory: OptionSet { 25 | public let rawValue: UInt8 26 | public init(rawValue: UInt8) { self.rawValue = rawValue } 27 | 28 | public static let none = Self(rawValue: 1 << 0) 29 | public static let application = Self(rawValue: 1 << 1) 30 | public static let input = Self(rawValue: 1 << 2) 31 | public static let keyboard = Self(rawValue: 1 << 3) 32 | public static let mouse = Self(rawValue: 1 << 4) 33 | public static let mouseButton = Self(rawValue: 1 << 5) 34 | } 35 | 36 | // Event 37 | public protocol Event: AnyObject, CustomStringConvertible { 38 | static var staticEventType: EventType { get } 39 | var eventType: EventType { get } 40 | var categoryFlags: EventCategory { get } 41 | var handled: Bool { get set } 42 | } 43 | 44 | public typealias EventCallback = (T) -> Bool where T: Event 45 | 46 | // EventDispatcher 47 | public class EventDispatcher { 48 | public var event: Event 49 | 50 | public init(event: Event) { 51 | self.event = event 52 | } 53 | 54 | @discardableResult 55 | public func dispatch(callback: EventCallback) -> Bool where T: Event { 56 | if event.eventType == T.staticEventType { 57 | event.handled = event.handled || callback(event as! T) 58 | return true 59 | } 60 | return false 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /Sources/Palico/Renderer/ShaderDataBridge.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ShaderDataBridge.swift 3 | // Palico 4 | // 5 | // Created by Junhao Wang on 1/6/22. 6 | // 7 | 8 | import MathLib 9 | 10 | // At this time, we need to manually keep this file consistent 11 | // with shader files (Common.metal). 12 | 13 | // Index 14 | public enum Attribute: Int { 15 | case position = 0 16 | case normal = 1 17 | case uv = 2 18 | } 19 | 20 | public enum BufferIndex: Int { 21 | case vertices = 0 22 | case vertexUniform = 11 23 | case fragmentUniform = 12 24 | case lightData = 13 25 | // skybox... 26 | } 27 | 28 | public enum TextureIndex: Int { 29 | case baseColor = 0 30 | case normal = 1 31 | case roughness = 2 32 | case metallic = 3 33 | case ao = 4 34 | } 35 | 36 | // Uniforms 37 | public struct VertexUniformData { 38 | var modelMatrix: Float4x4 = .identity 39 | var viewMatrix: Float4x4 = .identity 40 | var projectionMatrx: Float4x4 = .identity 41 | var normalMatrix: Float3x3 = .identity 42 | } 43 | 44 | public struct FragmentUniformData { 45 | var tintColor: Color4 = .white 46 | var cameraPosition: Float3 = [0, 0, 0] 47 | var lightCount: Int32 = 0 48 | var noLight: Int32 = 0 49 | } 50 | 51 | // Light 52 | public enum LightType: Int32 { 53 | case dirLight = 0 54 | case pointLight = 1 55 | case spotLight = 2 56 | case ambientLight = 3 57 | 58 | public static let typeStrings: [String] = [ 59 | "Directional", "Point", "Spot", "Ambient" 60 | ] 61 | } 62 | 63 | public struct LightData { 64 | var type: Int32 = LightType.dirLight.rawValue 65 | var position: Float3 = [0, 0, 0] 66 | var color: Color3 = .white 67 | var intensity: Float = 1.0 68 | var direction: Float3 = normalize([1, 1, 1]) 69 | var attenuation: Float3 = [1, 0, 0] 70 | var coneAngle: Float = 0 71 | var coneDirection: Float3 = [0, 0, 0] 72 | var coneAttenuation: Float3 = [1, 0, 0] 73 | } 74 | -------------------------------------------------------------------------------- /Sources/Palico/Event/KeyEvent.swift: -------------------------------------------------------------------------------- 1 | // 2 | // KeyEvent.swift 3 | // Palico 4 | // 5 | // Created by Junhao Wang on 12/19/21. 6 | // 7 | 8 | internal protocol KeyEvent: Event { 9 | var key: Key { get } 10 | } 11 | 12 | // KeyPressed 13 | public class KeyPressedEvent: KeyEvent { 14 | public static var staticEventType: EventType { .keyPressed } 15 | 16 | public var eventType: EventType { Self.staticEventType } 17 | public var categoryFlags: EventCategory { [.keyboard, .input] } 18 | public var handled: Bool = false 19 | 20 | public let key: Key 21 | public let repeatCount: UInt 22 | 23 | public init(keyCode: KeyCode, repeat repeatCount: UInt) { 24 | key = Key(rawValue: keyCode) ?? .unknown 25 | self.repeatCount = repeatCount 26 | } 27 | 28 | public var description: String { 29 | "[Event] type=KeyPressed, key=\(key), repeat=\(repeatCount), handled=\(handled)" 30 | } 31 | } 32 | 33 | // KeyReleased 34 | public class KeyReleasedEvent: KeyEvent { 35 | public static var staticEventType: EventType { .keyReleased } 36 | 37 | public var categoryFlags: EventCategory { [.keyboard, .input] } 38 | public var eventType: EventType { Self.staticEventType } 39 | public var handled: Bool = false 40 | 41 | public let key: Key 42 | 43 | public init(keyCode: KeyCode) { 44 | key = Key(rawValue: keyCode) ?? .unknown 45 | } 46 | 47 | public var description: String { 48 | "[Event] type=KeyReleased, key=\(key), handled=\(handled)" 49 | } 50 | } 51 | 52 | // CharTyped 53 | public class CharTypedEvent: Event { 54 | public static var staticEventType: EventType { .charTyped } 55 | 56 | public var categoryFlags: EventCategory { [.keyboard, .input] } 57 | public var eventType: EventType { Self.staticEventType } 58 | public var handled: Bool = false 59 | 60 | public let char: String 61 | 62 | public init(char: String) { 63 | self.char = char 64 | } 65 | 66 | public var description: String { 67 | "[Event] type=KeyTyped, char=\(char)), handled=\(handled)" 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /Sources/Palico/Platform/Cocoa/CocoaWindow+Event.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CocoaWindow+Event.swift 3 | // Palico 4 | // 5 | // Created by Junhao Wang on 1/2/22. 6 | // 7 | 8 | import Cocoa 9 | 10 | extension CocoaWindow { 11 | func publishEvent(_ event: Event) { 12 | windowDelegate?.onEvent(event: event) 13 | } 14 | 15 | func onMouseEvent(event: NSEvent) { 16 | ImGui_ImplOSX_HandleEvent(event, vc.view) 17 | 18 | switch event.type { 19 | // mouse button 20 | case .leftMouseDown, .leftMouseUp, .rightMouseDown, .rightMouseUp, .otherMouseDown, .otherMouseUp: 21 | mouseButtonEventCallback(nsEvent: event) 22 | 23 | // mouse moved 24 | case .mouseMoved, .leftMouseDragged, .rightMouseDragged, .otherMouseDragged: 25 | mouseMovedEventCallback(nsEvent: event) 26 | 27 | // scroll wheel 28 | case .scrollWheel: // ScrollWheel 29 | mouseScrollWheelEventCallback(nsEvent: event) 30 | 31 | default: 32 | Log.warn("Unknown mouse event! - \(event)") 33 | } 34 | } 35 | 36 | func onKeyEvent(event: NSEvent) { 37 | ImGui_ImplOSX_HandleEvent(event, vc.view) 38 | 39 | /* 40 | // Just handle the events. This will then dispatch to ImGuiLayer.onEvent, 41 | // in which events will be decided to dispatch or not to our application. 42 | // Original code: 43 | let wantsCapture: Bool = ImGui_ImplOSX_HandleEvent(nsEvent, nativeView) 44 | if nsEvent.type == .keyDown && wantsCapture { 45 | return nil // do not dispatch keydown event when ImGUi wants to capture 46 | } 47 | */ 48 | 49 | // Handled by us 50 | switch event.type { 51 | case .keyDown: 52 | keyEventCallback(nsEvent: event) 53 | charTypedEventCallback(nsEvent: event) 54 | case .keyUp: 55 | keyEventCallback(nsEvent: event) 56 | case .flagsChanged: 57 | modifierChangedEventCallback(nsEvent: event) 58 | default: 59 | Log.warn("Unknown mouse event! - \(event)") 60 | } 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /Sources/Palico/ImGui/ImGuiBackend+Metal.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ImGuiBackend+Metal.swift 3 | // Palico 4 | // 5 | // Created by Junhao Wang on 12/26/21. 6 | // 7 | 8 | import Metal 9 | import ImGui 10 | 11 | class ImGuiBackendMetalGraphics: ImGuiBackendGraphicsDelegate { 12 | private(set) var commandBuffer: MTLCommandBuffer! 13 | private(set) var renderCommandEncoder: MTLRenderCommandEncoder! 14 | 15 | init() { } 16 | 17 | func implGraphicsInit() { 18 | ImGui_ImplMetal_Init(MetalContext.device) 19 | } 20 | 21 | func implGraphicsNewFrame() { 22 | guard let mtkView = MetalContext.view, 23 | let commandBuffer = Renderer.currentCommandBuffer, 24 | let renderPassDescriptor = mtkView.currentRenderPassDescriptor 25 | else { 26 | Log.warn("ImplGraphicsNewFrame: Required resources are not available!") 27 | return 28 | } 29 | 30 | self.commandBuffer = commandBuffer 31 | 32 | // Config 33 | let io = ImGuiGetIO()! 34 | let dpi: Float = MetalContext.dpi 35 | io.pointee.DisplayFramebufferScale = ImVec2(dpi, dpi) 36 | io.pointee.DisplaySize = ImVec2(Float(mtkView.bounds.width), Float(mtkView.bounds.height)) 37 | io.pointee.DeltaTime = Time.deltaTime 38 | 39 | // Keeping results from other render passes that run first 40 | /* 41 | renderPassDescriptor.colorAttachments[0].loadAction = .load 42 | renderPassDescriptor.depthAttachment.loadAction = .load 43 | */ 44 | 45 | let commandEncoder = commandBuffer.makeRenderCommandEncoder(descriptor: renderPassDescriptor)! 46 | commandEncoder.pushDebugGroup("ImGui Layer") 47 | 48 | renderCommandEncoder = commandEncoder 49 | 50 | ImGui_ImplMetal_NewFrame(renderPassDescriptor) 51 | } 52 | 53 | func implGraphicsShutdown() { 54 | ImGui_ImplMetal_Shutdown() 55 | } 56 | 57 | func implGraphicsRenderDrawData(_ drawData: ImDrawData) { 58 | ImGui_ImplMetal_RenderDrawData(drawData, commandBuffer, renderCommandEncoder) 59 | 60 | renderCommandEncoder.popDebugGroup() 61 | renderCommandEncoder.endEncoding() 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /Sources/Palico/Scene/SceneLight.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SceneLight.swift 3 | // Palico 4 | // 5 | // Created by Junhao Wang on 1/8/22. 6 | // 7 | 8 | import MathLib 9 | 10 | public class SceneLight: GameObject { 11 | public init(_ scene: Scene, 12 | name: String = "Scene Light", 13 | type: LightType = .dirLight, 14 | position: Float3 = [3, 3, 2], 15 | rotation: Float3 = [0, 0, 0]) { 16 | 17 | var defaultRotation: Float3 = rotation 18 | var defaultScale: Float3 = [0.2, 0.2, 0.2] 19 | 20 | if type == .dirLight || type == .spotLight { 21 | defaultRotation = [Float(-45).toRadians, Float(-90).toRadians, 0] 22 | defaultScale = [0.2, 0.3, 0.2] 23 | } 24 | 25 | super.init(scene, name: name, 26 | position: position, 27 | rotation: defaultRotation, 28 | scale: defaultScale) 29 | 30 | let meshRenderer = MeshRendererComponent() 31 | meshRenderer.setMesh(makeLightMeshType(type: type)) 32 | addComponent(meshRenderer) 33 | 34 | addComponent(LightComponent(type: type)) 35 | } 36 | 37 | public override func onUpdateEditor(deltaTime ts: Timestep) { 38 | super.onUpdateEditor(deltaTime: ts) 39 | updateLightComponentData() 40 | } 41 | 42 | public override func onUpdateRuntime(deltaTime ts: Timestep) { 43 | super.onUpdateRuntime(deltaTime: ts) 44 | updateLightComponentData() 45 | } 46 | 47 | private func updateLightComponentData() { 48 | let transform = getComponent(TransformComponent.self) // guranteed 49 | if hasComponent(LightComponent.self) { 50 | let lightComponent = getComponent(LightComponent.self) 51 | lightComponent.light.position = transform.position 52 | lightComponent.light.direction = -transform.upDirection // points to -Y 53 | } 54 | } 55 | 56 | private func makeLightMeshType(type: LightType) -> PrimitiveType { 57 | switch type { 58 | case .dirLight: 59 | return .hemisphere 60 | case .pointLight: 61 | return .sphere 62 | case .spotLight: 63 | return .cone 64 | case .ambientLight: 65 | return .cube 66 | } 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /Sources/Palico/Assets/Fonts/Ruda/README.txt: -------------------------------------------------------------------------------- 1 | Ruda Variable Font 2 | ================== 3 | 4 | This download contains Ruda as both a variable font and static fonts. 5 | 6 | Ruda is a variable font with this axis: 7 | wght 8 | 9 | This means all the styles are contained in a single file: 10 | Ruda-VariableFont_wght.ttf 11 | 12 | If your app fully supports variable fonts, you can now pick intermediate styles 13 | that aren’t available as static fonts. Not all apps support variable fonts, and 14 | in those cases you can use the static font files for Ruda: 15 | static/Ruda-Regular.ttf 16 | static/Ruda-Medium.ttf 17 | static/Ruda-SemiBold.ttf 18 | static/Ruda-Bold.ttf 19 | static/Ruda-ExtraBold.ttf 20 | static/Ruda-Black.ttf 21 | 22 | Get started 23 | ----------- 24 | 25 | 1. Install the font files you want to use 26 | 27 | 2. Use your app's font picker to view the font family and all the 28 | available styles 29 | 30 | Learn more about variable fonts 31 | ------------------------------- 32 | 33 | https://developers.google.com/web/fundamentals/design-and-ux/typography/variable-fonts 34 | https://variablefonts.typenetwork.com 35 | https://medium.com/variable-fonts 36 | 37 | In desktop apps 38 | 39 | https://theblog.adobe.com/can-variable-fonts-illustrator-cc 40 | https://helpx.adobe.com/nz/photoshop/using/fonts.html#variable_fonts 41 | 42 | Online 43 | 44 | https://developers.google.com/fonts/docs/getting_started 45 | https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_Fonts/Variable_Fonts_Guide 46 | https://developer.microsoft.com/en-us/microsoft-edge/testdrive/demos/variable-fonts 47 | 48 | Installing fonts 49 | 50 | MacOS: https://support.apple.com/en-us/HT201749 51 | Linux: https://www.google.com/search?q=how+to+install+a+font+on+gnu%2Blinux 52 | Windows: https://support.microsoft.com/en-us/help/314960/how-to-install-or-remove-a-font-in-windows 53 | 54 | Android Apps 55 | 56 | https://developers.google.com/fonts/docs/android 57 | https://developer.android.com/guide/topics/ui/look-and-feel/downloadable-fonts 58 | 59 | License 60 | ------- 61 | Please read the full license text (OFL.txt) to understand the permissions, 62 | restrictions and requirements for usage, redistribution, and modification. 63 | 64 | You can use them freely in your products & projects - print or digital, 65 | commercial or otherwise. 66 | 67 | This isn't legal advice, please consider consulting a lawyer and see the full 68 | license for all details. 69 | -------------------------------------------------------------------------------- /Package.swift: -------------------------------------------------------------------------------- 1 | // swift-tools-version:5.3 2 | import PackageDescription 3 | 4 | // Dependencies 5 | let OhMyLog = Target.Dependency.product(name: "OhMyLog", package: "OhMyLog") 6 | let MathLib = Target.Dependency.product(name: "MathLib", package: "MathLib") 7 | let MothECS = Target.Dependency.product(name: "MothECS", package: "MothECS") 8 | let ImGui = Target.Dependency.product(name: "ImGui", package: "SwiftImGui") 9 | let ImGuizmo = Target.Dependency.product(name: "ImGuizmo", package: "SwiftImGuizmo") 10 | // let CGLFW3 = Target.Dependency.product(name: "CGLFW3", package: "CGLFW3") 11 | 12 | // Engine Dependencies 13 | let engineDependencies: [Target.Dependency] = [ 14 | OhMyLog, MathLib, MothECS, ImGui, ImGuizmo 15 | ] 16 | 17 | // Application Dependencies 18 | let appDependencies: [Target.Dependency] = [ 19 | OhMyLog, MathLib, MothECS, ImGui, ImGuizmo 20 | ] 21 | 22 | let package = Package( 23 | name: "PalicoEngine", 24 | 25 | platforms: [ 26 | .macOS(.v11) 27 | ], 28 | 29 | products: [ 30 | .library(name: "Palico", targets: ["Palico"]), 31 | .executable(name: "Editor", targets: ["Editor"]), 32 | .executable(name: "Example", targets: ["Example"]), 33 | ], 34 | 35 | dependencies: [ 36 | // .package(url: "https://github.com/forkercat/CGLFW3.git", .branch("main")), 37 | .package(url: "https://github.com/forkercat/OhMyLog.git", .branch("main")), 38 | .package(url: "https://github.com/forkercat/MathLib.git", .branch("main")), 39 | .package(url: "https://github.com/forkercat/MothECS.git", .branch("main")), 40 | .package(url: "https://github.com/forkercat/SwiftImGui.git", .branch("update-1.86-docking")), 41 | .package(url: "https://github.com/forkercat/SwiftImGuizmo.git", .branch("master")), 42 | ], 43 | 44 | targets: [ 45 | .target(name: "Palico", 46 | dependencies: engineDependencies + [ 47 | 48 | ], 49 | exclude: ["Platform/GLFW/README.md"], 50 | resources: [ 51 | .copy("Assets/") 52 | ]), 53 | 54 | .target(name: "Editor", 55 | dependencies: appDependencies + [ 56 | "Palico" 57 | ], 58 | resources: [ 59 | .copy("Assets/") 60 | ]), 61 | 62 | .target(name: "Example", 63 | dependencies: appDependencies + [ 64 | "Palico", 65 | ]), 66 | ] 67 | ) 68 | -------------------------------------------------------------------------------- /Sources/Editor/Panels/AssetPanel.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AssetPanel.swift 3 | // Editor 4 | // 5 | // Created by Junhao Wang on 1/4/22. 6 | // 7 | 8 | import Palico 9 | import ImGui 10 | 11 | class AssetPanel: Panel { 12 | var panelName: String { "Assets" } 13 | 14 | func onImGuiRender() { 15 | ImGuiBegin("\(FAIcon.folderOpen) \(panelName)", nil, 0) 16 | 17 | ImGuiPushStyleVar(Im(ImGuiStyleVar_FramePadding), ImVec2(2, 2)) 18 | ImGuiPushStyleVar(Im(ImGuiStyleVar_ItemSpacing), ImVec2(3, 6)) 19 | 20 | ImGuiTextV("\(FAIcon.star) Favorite") 21 | if ImGuiTreeNode("\(FAIcon.folder) IT IS FAKE!") { ImGuiTreePop() } 22 | 23 | ImGuiSeparator() 24 | 25 | ImGuiTextV("\(FAIcon.cloud) Cloud") 26 | if ImGuiTreeNode("\(FAIcon.folder) IT IS FAKE!") { ImGuiTreePop() } 27 | 28 | ImGuiSeparator() 29 | 30 | ImGuiTextV("\(FAIcon.archive) Assets") 31 | 32 | if ImGuiTreeNode("\(FAIcon.folder) IT IS FAKE!") { ImGuiTreePop() } 33 | if ImGuiTreeNode("\(FAIcon.folder) Fonts") { ImGuiTreePop() } 34 | if ImGuiTreeNode("\(FAIcon.folder) Materials") { 35 | if ImGuiTreeNode("\(FAIcon.folder) Shaders") { 36 | ImGuiIndent(16) 37 | ImGuiTextV("\(FAIcon.fileCode) Main.metal") 38 | ImGuiTextV("\(FAIcon.fileCode) Shadow.metal") 39 | ImGuiUnindent(16) 40 | ImGuiTreePop() 41 | } 42 | if ImGuiTreeNode("\(FAIcon.folder) Textures") { ImGuiTreePop() } 43 | ImGuiTreePop() 44 | } 45 | if ImGuiTreeNode("\(FAIcon.folder) Models") { 46 | ImGuiTreePop() 47 | } 48 | if ImGuiTreeNode("\(FAIcon.folder) Scenes") { 49 | ImGuiTreePop() 50 | } 51 | if ImGuiTreeNode("\(FAIcon.folder) Sounds") { 52 | if ImGuiTreeNode("\(FAIcon.folder) BGM") { ImGuiTreePop() } 53 | if ImGuiTreeNode("\(FAIcon.folder) SFX") { ImGuiTreePop() } 54 | ImGuiTreePop() 55 | } 56 | if ImGuiTreeNode("\(FAIcon.folder) Scripts") { 57 | if ImGuiTreeNode("\(FAIcon.folder) Controllers") { ImGuiTreePop() } 58 | if ImGuiTreeNode("\(FAIcon.folder) Managers") { ImGuiTreePop() } 59 | if ImGuiTreeNode("\(FAIcon.folder) Player") { ImGuiTreePop() } 60 | ImGuiTreePop() 61 | } 62 | 63 | ImGuiPopStyleVar(2) 64 | 65 | ImGuiEnd() 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /Sources/Palico/Platform/Cocoa/ViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ViewController.swift 3 | // Palico 4 | // 5 | // Created by Junhao Wang on 1/2/22. 6 | // 7 | 8 | import Cocoa 9 | import MetalKit 10 | import MathLib 11 | 12 | typealias MouseEventCallback = (NSEvent) -> Void 13 | typealias ViewDrawCallback = () -> Void 14 | typealias ViewResizeCallback = (Int2) -> Void 15 | 16 | class ViewController: NSViewController { 17 | let mtkView = MTKView() 18 | 19 | var mouseEventCallback: MouseEventCallback? = nil 20 | var viewDrawCallback: ViewDrawCallback? = nil 21 | var viewResizeCallback: ViewResizeCallback? = nil 22 | 23 | override func loadView() { 24 | mtkView.device = MetalContext.device 25 | mtkView.wantsLayer = true 26 | mtkView.layer?.backgroundColor = .black 27 | mtkView.clearColor = .init(red: 0, green: 0, blue: 0, alpha: 1) 28 | mtkView.delegate = self 29 | MetalContext.updateMTKView(mtkView) 30 | 31 | self.view = mtkView 32 | } 33 | 34 | // Mouse 35 | override func mouseMoved(with event: NSEvent) { 36 | mouseEventCallback?(event) 37 | } 38 | 39 | override func mouseDown(with event: NSEvent) { 40 | mouseEventCallback?(event) 41 | } 42 | 43 | override func mouseUp(with event: NSEvent) { 44 | mouseEventCallback?(event) 45 | } 46 | 47 | override func mouseDragged(with event: NSEvent) { 48 | mouseEventCallback?(event) 49 | } 50 | 51 | override func rightMouseDown(with event: NSEvent) { 52 | mouseEventCallback?(event) 53 | } 54 | 55 | override func rightMouseUp(with event: NSEvent) { 56 | mouseEventCallback?(event) 57 | } 58 | 59 | override func rightMouseDragged(with event: NSEvent) { 60 | mouseEventCallback?(event) 61 | } 62 | 63 | override func otherMouseDown(with event: NSEvent) { 64 | mouseEventCallback?(event) 65 | } 66 | 67 | override func otherMouseUp(with event: NSEvent) { 68 | mouseEventCallback?(event) 69 | } 70 | 71 | override func otherMouseDragged(with event: NSEvent) { 72 | mouseEventCallback?(event) 73 | } 74 | 75 | override func scrollWheel(with event: NSEvent) { 76 | mouseEventCallback?(event) 77 | } 78 | } 79 | 80 | extension ViewController: MTKViewDelegate { 81 | func draw(in view: MTKView) { 82 | Time.update() // only used here to update deltaTime 83 | viewDrawCallback?() 84 | } 85 | 86 | func mtkView(_ view: MTKView, drawableSizeWillChange size: CGSize) { 87 | viewResizeCallback?(Int2(Int(size.width), Int(size.height))) 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /Sources/Palico/Renderer/Color.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Color.swift 3 | // Palico 4 | // 5 | // Created by Junhao Wang on 1/6/22. 6 | // 7 | 8 | import MathLib 9 | 10 | public typealias Color3 = Float3 11 | public typealias Color4 = Float4 12 | 13 | extension Color3 { 14 | public static let white = Color3(1, 1, 1) 15 | public static let black = Color3(1, 1, 1) 16 | public static let red = Color3(1, 0, 0) 17 | public static let green = Color3(0, 1, 0) 18 | public static let blue = Color3(0, 0, 1) 19 | public static let yellow = Color3(1, 1, 0) 20 | public static let cyan = Color3(0, 1, 1) 21 | public static let magneta = Color3(1, 0, 1) 22 | 23 | public static let grey = Color3(0.5, 0.5, 0.5) 24 | public static let lightGrey = Color3(0.7, 0.7, 0.7) 25 | 26 | public static let lightYellow = Color3(ri: 238, gi: 205, bi: 151) 27 | public static let lightBlue = Color3(ri: 59, gi: 148, bi: 240) 28 | 29 | public var r: Float { x } 30 | public var g: Float { y } 31 | public var b: Float { z } 32 | 33 | public init(r: Float, g: Float, b: Float) { 34 | self.init(r, g, b) 35 | } 36 | 37 | public init(ri: Int, gi: Int, bi: Int) { 38 | self.init(Float(ri) / Float(255), Float(gi) / Float(255), Float(bi) / Float(255)) 39 | } 40 | } 41 | 42 | extension Color4 { 43 | public static let white = Color4(Color3.white, 1) 44 | public static let black = Color4(Color3.black, 1) 45 | public static let red = Color4(Color3.red, 1) 46 | public static let green = Color4(Color3.green, 1) 47 | public static let blue = Color4(Color3.blue, 1) 48 | public static let yellow = Color4(Color3.yellow, 1) 49 | public static let cyan = Color4(Color3.cyan, 1) 50 | public static let magneta = Color4(Color3.magneta, 1) 51 | 52 | public static let lightYellow = Color4(Color3.lightYellow, 1) 53 | public static let lightBlue = Color4(Color3.lightBlue, 1) 54 | 55 | public static let grey = Color4(Color3.grey, 1) 56 | public static let lightGrey = Color4(Color3.lightGrey, 1) 57 | 58 | public var r: Float { x } 59 | public var g: Float { y } 60 | public var b: Float { z } 61 | public var a: Float { w } 62 | 63 | public var rgb: Color3 { 64 | return xyz 65 | } 66 | 67 | public init(r: Float, g: Float, b: Float, a: Float = 1.0) { 68 | self.init(r, g, b, a) 69 | } 70 | 71 | public init(ri: Int, gi: Int, bi: Int, ai: Int = 255) { 72 | self.init(Float(ri) / Float(255), Float(gi) / Float(255), Float(bi) / Float(255), Float(ai) / Float(255)) 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /Sources/Palico/Renderer/PipelineStatePool.swift: -------------------------------------------------------------------------------- 1 | // 2 | // PipelineStatePool.swift 3 | // Palico 4 | // 5 | // Created by Junhao Wang on 12/27/21. 6 | // 7 | 8 | import MetalKit 9 | 10 | class PipelineStatePool { 11 | private(set) static var shared: PipelineStatePool = PipelineStatePool() 12 | 13 | var colorPipelineState: MTLRenderPipelineState! 14 | var shadowPipelineState: MTLRenderPipelineState! 15 | var geometryPipelineState: MTLRenderPipelineState! 16 | 17 | private init() { 18 | buildColorPipelineState() 19 | buildShadowPipelineState() 20 | geometryPipelineState = nil 21 | } 22 | 23 | func fetchPipelineState(type: RenderPassType) -> MTLRenderPipelineState { 24 | switch type { 25 | case .colorPass: 26 | return colorPipelineState 27 | case .shadowPass: 28 | return shadowPipelineState 29 | default: 30 | fatalError("Unsupported pipeline state!") 31 | } 32 | } 33 | 34 | private func buildColorPipelineState() { 35 | let descriptor = MTLRenderPipelineDescriptor() 36 | descriptor.vertexFunction = MetalContext.library.makeFunction(name: "Palico::vertex_main") 37 | descriptor.fragmentFunction = MetalContext.library.makeFunction(name: "Palico::fragment_main") 38 | descriptor.colorAttachments[0].pixelFormat = RenderConfig.PixelFormat.color 39 | descriptor.depthAttachmentPixelFormat = RenderConfig.PixelFormat.depth 40 | descriptor.vertexDescriptor = MTKMetalVertexDescriptorFromModelIO(Mesh.defaultVertexDescriptor) 41 | 42 | do { 43 | colorPipelineState = try MetalContext.device.makeRenderPipelineState(descriptor: descriptor) 44 | } catch let error { 45 | assertionFailure(error.localizedDescription) 46 | return 47 | } 48 | } 49 | 50 | // Shadow 51 | private func buildShadowPipelineState() { 52 | let descriptor = MTLRenderPipelineDescriptor() 53 | descriptor.vertexFunction = MetalContext.library.makeFunction(name: "Palico::vertex_main") 54 | descriptor.fragmentFunction = MetalContext.library.makeFunction(name: "Palico::fragment_main") 55 | descriptor.colorAttachments[0].pixelFormat = .invalid 56 | descriptor.depthAttachmentPixelFormat = RenderConfig.PixelFormat.depth 57 | descriptor.vertexDescriptor = MTKMetalVertexDescriptorFromModelIO(Mesh.defaultVertexDescriptor) 58 | 59 | do { 60 | shadowPipelineState = try MetalContext.device.makeRenderPipelineState(descriptor: descriptor) 61 | } catch let error { 62 | assertionFailure(error.localizedDescription) 63 | return 64 | } 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /Sources/Palico/Platform/Cocoa/CocoaWindow.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CocoaWindow.swift 3 | // Palico 4 | // 5 | // Created by Junhao Wang on 12/25/21. 6 | // 7 | 8 | import Cocoa 9 | 10 | class CocoaWindow: Window { 11 | var title: String { nsWindow.title } 12 | var width: Int { Int(nsWindow.contentView?.bounds.width ?? 0) } 13 | var height: Int { Int(nsWindow.contentView?.bounds.height ?? 0) } 14 | var isMinimized: Bool { nsWindow.isMiniaturized } 15 | 16 | weak var windowDelegate: WindowDelegate? = nil 17 | 18 | let nsWindow: NSWindow 19 | let vc: ViewController 20 | 21 | required init(descriptor: WindowDescriptor) { 22 | Log.info("Initializing Cocoa window: \(descriptor.title) \(descriptor.width) x \(descriptor.height)") 23 | 24 | // View 25 | vc = ViewController() 26 | defer { 27 | vc.mouseEventCallback = onMouseEvent 28 | vc.viewDrawCallback = onViewDraw 29 | vc.viewResizeCallback = onViewResize 30 | } 31 | 32 | // NSWindow 33 | nsWindow = NSWindow(contentRect: NSRect(x: 0, y: 0, width: Int(descriptor.width), height: Int(descriptor.height)), 34 | styleMask: [.miniaturizable, .closable, .resizable, .titled], 35 | backing: .buffered, 36 | defer: false) 37 | nsWindow.title = descriptor.title 38 | nsWindow.center() 39 | nsWindow.contentView = vc.view 40 | nsWindow.acceptsMouseMovedEvents = true 41 | nsWindow.makeKeyAndOrderFront(nil) 42 | nsWindow.makeMain() 43 | 44 | // Add a tracking area to capture move events within the window; 45 | // otherwise, location would become screen coord when mouse is outside. 46 | let trackingArea = NSTrackingArea(rect: .zero, options: [.mouseMoved, .inVisibleRect, .activeAlways], 47 | owner: vc.view, userInfo: nil) 48 | vc.view.addTrackingArea(trackingArea) 49 | 50 | // If we want to receive key events, we either need to be in the responder chain of the key view, 51 | // or else we can install a local monitor. The consequence of this heavy-handed approach is that 52 | // we receive events for all controls, not just Dear ImGui widgets. If we had native controls in our 53 | // window, we'd want to be much more careful than just ingesting the complete event stream, though we 54 | // do make an effort to be good citizens by passing along events when Dear ImGui doesn't want to capture. 55 | let eventMask: NSEvent.EventTypeMask = [.keyDown, .keyUp, .flagsChanged] 56 | NSEvent.addLocalMonitorForEvents(matching: eventMask) { [unowned self](event) -> NSEvent? in 57 | onKeyEvent(event: event) 58 | return event 59 | } 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /Sources/Palico/Renderer/Light.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Light.swift 3 | // Palico 4 | // 5 | // Created by Junhao Wang on 1/8/22. 6 | // 7 | 8 | import MathLib 9 | 10 | public protocol Light: Any { 11 | var type: LightType { get } 12 | var position: Float3 { get set } 13 | var color: Color3 { get set } 14 | var intensity: Float { get set } 15 | var direction: Float3 { get set } 16 | 17 | var lightData: LightData { get } 18 | } 19 | 20 | public struct DirectionalLight: Light { 21 | public var type: LightType { .dirLight } 22 | public var position: Float3 = [0, 0, 0] 23 | public var color: Color3 = .white 24 | public var intensity: Float = 1.0 25 | public var direction: Float3 = normalize([-1, -1, 0]) 26 | 27 | public var lightData: LightData { 28 | var data = LightData() 29 | data.type = type.rawValue 30 | data.position = position 31 | data.color = color 32 | data.intensity = intensity 33 | data.direction = direction 34 | 35 | return data 36 | } 37 | } 38 | 39 | public struct PointLight: Light { 40 | public var type: LightType { .pointLight } 41 | public var position: Float3 = [0, 0, 0] 42 | public var color: Color3 = .white 43 | public var intensity: Float = 1.0 44 | public var direction: Float3 = normalize([-1, -1, 0]) 45 | 46 | public var lightData: LightData { 47 | var data = LightData() 48 | data.type = type.rawValue 49 | data.position = position 50 | data.color = color 51 | data.intensity = intensity 52 | data.direction = direction 53 | 54 | return data 55 | } 56 | } 57 | 58 | public struct SpotLight: Light { 59 | public var type: LightType { .spotLight } 60 | public var position: Float3 = [0, 0, 0] 61 | public var color: Color3 = .white 62 | public var intensity: Float = 1.0 63 | public var direction: Float3 = normalize([-1, -1, 0]) 64 | 65 | public var lightData: LightData { 66 | var data = LightData() 67 | data.type = type.rawValue 68 | data.position = position 69 | data.color = color 70 | data.intensity = intensity 71 | data.direction = direction 72 | 73 | return data 74 | } 75 | } 76 | 77 | public struct AmbientLight: Light { 78 | public var type: LightType { .ambientLight } 79 | public var position: Float3 = [0, 0, 0] 80 | public var color: Color3 = .white 81 | public var intensity: Float = 1.0 82 | public var direction: Float3 = normalize([-1, -1, 0]) 83 | 84 | public var lightData: LightData { 85 | var data = LightData() 86 | data.type = type.rawValue 87 | data.position = position 88 | data.color = color 89 | data.intensity = intensity 90 | data.direction = direction 91 | 92 | return data 93 | } 94 | } 95 | -------------------------------------------------------------------------------- /Sources/Palico/Event/MouseEvent.swift: -------------------------------------------------------------------------------- 1 | // 2 | // MouseEvent.swift 3 | // Palico 4 | // 5 | // Created by Junhao Wang on 12/19/21. 6 | // 7 | 8 | internal protocol MouseEvent: Event { } 9 | 10 | // MouseMoved 11 | public class MouseMovedEvent: MouseEvent { 12 | public static var staticEventType: EventType { .mouseMoved } 13 | 14 | public var categoryFlags: EventCategory { [.mouse, .input] } 15 | public var eventType: EventType { Self.staticEventType } 16 | public var handled: Bool = false 17 | 18 | public let xpos: Float 19 | public let ypos: Float 20 | 21 | public init(x xpos: Float, y ypos: Float) { 22 | self.xpos = xpos 23 | self.ypos = ypos 24 | } 25 | 26 | public var description: String { 27 | "[Event] type=MouseMoved, position=(\(xpos), \(ypos)), handled=\(handled)" 28 | } 29 | } 30 | 31 | // MouseScrolled 32 | public class MouseScrolledEvent: MouseEvent { 33 | public static var staticEventType: EventType { .mouseScrolled } 34 | 35 | public var categoryFlags: EventCategory { [.mouse, .input] } 36 | public var eventType: EventType { Self.staticEventType } 37 | public var handled: Bool = false 38 | 39 | public let xoffset: Float 40 | public let yoffset: Float 41 | 42 | public init(x xoffset: Float, y yoffset: Float) { 43 | self.xoffset = xoffset 44 | self.yoffset = yoffset 45 | } 46 | 47 | public var description: String { 48 | "[Event] type=MouseScrolled, offset=(\(xoffset), \(yoffset)), handled=\(handled)" 49 | } 50 | } 51 | 52 | internal protocol MouseButtonEvent: Event { 53 | var button: Mouse { get } 54 | } 55 | 56 | // MouseButtonPressed 57 | public class MouseButtonPressedEvent: MouseButtonEvent { 58 | public static var staticEventType: EventType { .mouseButtonPressed } 59 | 60 | public var categoryFlags: EventCategory { [.mouseButton, .input] } 61 | public var eventType: EventType { Self.staticEventType } 62 | public var handled: Bool = false 63 | 64 | public let button: Mouse 65 | 66 | public init(mouseCode: MouseCode) { 67 | self.button = Mouse(rawValue: mouseCode) ?? .unknown 68 | } 69 | 70 | public var description: String { 71 | "[Event] type=MouseButtonPressed, button=\(button), handled=\(handled)" 72 | } 73 | } 74 | 75 | // MouseButtonReleased 76 | public class MouseButtonReleasedEvent: MouseButtonEvent { 77 | public static var staticEventType: EventType { .mouseButtonReleased } 78 | 79 | public var categoryFlags: EventCategory { [.mouseButton, .input] } 80 | public var eventType: EventType { Self.staticEventType } 81 | public var handled: Bool = false 82 | 83 | public let button: Mouse 84 | 85 | public init(mouseCode: MouseCode) { 86 | self.button = Mouse(rawValue: mouseCode) ?? .unknown 87 | } 88 | 89 | public var description: String { 90 | "[Event] type=MouseButtonReleased, button=\(button), handled=\(handled)" 91 | } 92 | } 93 | -------------------------------------------------------------------------------- /Sources/Palico/Platform/Cocoa/CocoaWindow+Callback.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CocoaWindow+Callback.swift 3 | // Palico 4 | // 5 | // Created by Junhao Wang on 1/2/22. 6 | // 7 | 8 | import Cocoa 9 | 10 | extension CocoaWindow { 11 | func keyEventCallback(nsEvent: NSEvent) { 12 | switch nsEvent.type { 13 | case .keyDown: 14 | Input.updateKeyMap(with: true, on: nsEvent.keyCode) 15 | let event = KeyPressedEvent(keyCode: nsEvent.keyCode, repeat: nsEvent.isARepeat ? 1 : 0) 16 | publishEvent(event) 17 | case .keyUp: 18 | Input.updateKeyMap(with: false, on: nsEvent.keyCode) 19 | let event = KeyReleasedEvent(keyCode: nsEvent.keyCode) 20 | publishEvent(event) 21 | default: 22 | return 23 | } 24 | } 25 | 26 | func modifierChangedEventCallback(nsEvent: NSEvent) { 27 | let flags = nsEvent.modifierFlags 28 | 29 | // For debugging 30 | // print("Command: \(flags.contains(.command)), Shift: \(flags.contains(.shift)), Option: \(flags.contains(.option)), Fn: \(flags.contains(.function))") 31 | 32 | // Does not support distinguishing from right modifier keys yet 33 | Input.updateKeyMap(with: flags.contains(.command), on: Key.command.rawValue) 34 | Input.updateKeyMap(with: flags.contains(.shift), on: Key.shift.rawValue) 35 | Input.updateKeyMap(with: flags.contains(.option), on: Key.option.rawValue) 36 | Input.updateKeyMap(with: flags.contains(.control), on: Key.control.rawValue) 37 | Input.updateKeyMap(with: flags.contains(.function), on: Key.function.rawValue) 38 | } 39 | 40 | func charTypedEventCallback(nsEvent: NSEvent) { 41 | switch nsEvent.type { 42 | case .keyDown: 43 | guard let string = nsEvent.characters else { 44 | return 45 | } 46 | let event = CharTypedEvent(char: string) 47 | publishEvent(event) 48 | default: 49 | return 50 | } 51 | } 52 | 53 | func mouseButtonEventCallback(nsEvent: NSEvent) { 54 | switch nsEvent.type { 55 | case .leftMouseDown, .rightMouseDown, .otherMouseDown: 56 | Input.updateMouseMap(with: true, on: MouseCode(nsEvent.buttonNumber)) 57 | let event = MouseButtonPressedEvent(mouseCode: MouseCode(nsEvent.buttonNumber)) 58 | publishEvent(event) 59 | case .leftMouseUp, .rightMouseUp, .otherMouseUp: 60 | Input.updateMouseMap(with: false, on: MouseCode(nsEvent.buttonNumber)) 61 | let event = MouseButtonReleasedEvent(mouseCode: MouseCode(nsEvent.buttonNumber)) 62 | publishEvent(event) 63 | default: 64 | return 65 | } 66 | } 67 | 68 | func mouseMovedEventCallback(nsEvent: NSEvent) { 69 | let cursorLocation = nsEvent.locationInWindow 70 | let event = MouseMovedEvent(x: Float(cursorLocation.x), y: Float(cursorLocation.y)) 71 | publishEvent(event) 72 | } 73 | 74 | func mouseScrollWheelEventCallback(nsEvent: NSEvent) { 75 | let event = MouseScrolledEvent(x: Float(nsEvent.scrollingDeltaX), y: Float(nsEvent.scrollingDeltaY)) 76 | publishEvent(event) 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /Sources/Palico/Renderer/ShaderLibrary.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ShaderLibrary.swift 3 | // Palico 4 | // 5 | // Created by Junhao Wang on 12/28/21. 6 | // 7 | 8 | import Foundation 9 | 10 | public struct ShaderLibrary { 11 | private static var shaderCache: [String: Shader] = [:] 12 | public static var shaderCount: Int { get { shaderCache.count } } 13 | 14 | public static func makeShader(name: String, url: URL) -> Shader? { 15 | Log.debug("Loading shader -> \(name) (\(url.lastPathComponent))") 16 | return Shader(name: name, url: url) 17 | } 18 | 19 | public static func makeShader(name: String, source: String) -> Shader? { 20 | Log.debug("Loading shader -> \(name) (source code)") 21 | return Shader(name: name, source: source) 22 | } 23 | 24 | public static func add(shader: Shader?) { 25 | guard let shader = shader else { 26 | assertionFailure("You are adding a nil shader to library. Skipping it!") 27 | return 28 | } 29 | shaderCache[shader.name] = shader 30 | } 31 | 32 | public static func add(name: String, url: URL?) { 33 | guard let url = url, 34 | let shader = makeShader(name: name, url: url) 35 | else { 36 | Log.warn("You are adding a nil shader to library. Skipping it!") 37 | return 38 | } 39 | add(shader: shader) 40 | } 41 | 42 | public static func add(name: String, source: String) { 43 | shaderCache[name] = makeShader(name: name, source: source) 44 | } 45 | 46 | public static func get(name: String) -> Shader? { 47 | return shaderCache[name] 48 | } 49 | 50 | public static func compileAll() { 51 | compileShaders(names: Array(shaderCache.keys)) 52 | } 53 | 54 | public static func compileShaders(names: [String]) { 55 | let nameSet = Set(names) 56 | 57 | var shaderSource: String = "" 58 | 59 | // Common (compile this first!) 60 | if let commonHeader = get(name: "Common") { 61 | shaderSource.append(contentsOf: "\(commonHeader.source ?? "")\n") 62 | } else { 63 | Log.warn("Cannot compile common header. Skipping compilation!") 64 | } 65 | 66 | // Compile other files 67 | for name in nameSet { 68 | guard name != "Common" else { 69 | continue // skip Common.metal 70 | } 71 | 72 | guard let shader = get(name: name) else { 73 | let message = "Unknown shader name: \(name)" 74 | assertionFailure(message) 75 | Log.warn(message) 76 | continue 77 | } 78 | shaderSource.append(contentsOf: "\(shader.source ?? "")\n") 79 | } 80 | 81 | // compile 82 | Shader.compile(source: shaderSource) 83 | 84 | for shader in shaderCache.values { 85 | shader.isCompiled = true 86 | } 87 | 88 | Log.info(""" 89 | Compiled \(nameSet.count) shader file(s) \ 90 | that contain \(MetalContext.library.functionNames.count) function(s): \(MetalContext.library.functionNames) 91 | """) 92 | Console.info("Compiled Shader Functions: \(MetalContext.library.functionNames)") 93 | } 94 | 95 | public static func empty() { 96 | Log.debug("Remove all cached shaders!") 97 | shaderCache.removeAll(keepingCapacity: true) 98 | } 99 | 100 | private init() { } 101 | } 102 | -------------------------------------------------------------------------------- /Sources/Editor/Panels/ConsolePanel.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ConsolePanel.swift 3 | // Editor 4 | // 5 | // Created by Junhao Wang on 1/4/22. 6 | // 7 | 8 | import Palico 9 | import ImGui 10 | 11 | class ConsolePanel: Panel { 12 | var panelName: String { "Console" } 13 | 14 | var autoScrolled: Bool = true 15 | var currentMessageLevel: Int32 = 0 16 | 17 | func onImGuiRender() { 18 | let io = ImGuiGetIO()! 19 | 20 | ImGuiBegin("\(FAIcon.terminal) \(panelName)", nil, 0) 21 | 22 | // Header 23 | ImGuiTextV(String(format: "FPS: %.1f (%.3f ms/frame)", io.pointee.Framerate, 1000.0 / io.pointee.Framerate)) 24 | 25 | // For Debug 26 | /* 27 | ImGuiSameLine(0, -1) 28 | if ImGuiButton("debug", ImVec2(0, 0)) { 29 | Console.debug("Hi debug - \(Time.currentTime)") 30 | } 31 | 32 | ImGuiSameLine(0, -1) 33 | if ImGuiButton("info", ImVec2(0, 0)) { 34 | Console.info("Hi info - \(Time.currentTime)") 35 | } 36 | 37 | ImGuiSameLine(0, -1) 38 | if ImGuiButton("warn", ImVec2(0, 0)) { 39 | Console.warn("Hi Warn - \(Time.currentTime)") 40 | } 41 | 42 | ImGuiSameLine(0, -1) 43 | if ImGuiButton("error", ImVec2(0, 0)) { 44 | Console.error("Hi error - \(Time.currentTime)") 45 | } 46 | */ 47 | 48 | ImGuiSameLine(ImGuiGetWindowWidth() - 150 - 95 - 40, -1) // TODO: Use calculated width 49 | ImGuiCheckbox("Auto-Scroll", &autoScrolled) 50 | ImGuiSameLine(0, -1) 51 | 52 | ImGuiPushItemWidth(95) 53 | ImGuiCombo("##OutputLevelCombo", ¤tMessageLevel, 54 | Console.Message.Level.levelStringsWithIcons, 55 | Int32(Console.Message.Level.numLevels), -1) 56 | ImGuiPopItemWidth() 57 | 58 | ImGuiSameLine(0, -1) 59 | if ImGuiButton("\(FAIcon.trashAlt) Clear", ImVec2(0, 0)) { 60 | Console.clear() 61 | } 62 | 63 | ImGuiSeparator() 64 | ImGuiSpacing() 65 | 66 | // Message Region 67 | ImGuiBeginChild("##ConsoleScrollRegion", ImVec2(0, 0), false, Im(ImGuiWindowFlags_HorizontalScrollbar)) 68 | 69 | if ImGuiBeginPopupContextWindow("##ConsoleClearPopup", 1) { 70 | if ImGuiSelectable("\(FAIcon.trashAlt) Clear", false, ImGuiFlag_None, ImVec2(0, 0)) { 71 | Console.clear() 72 | } 73 | ImGuiEndPopup() 74 | } 75 | 76 | while let message = Console.nextMessage() { 77 | if message.level.rawValue < currentMessageLevel { 78 | continue 79 | } 80 | 81 | var color: ImVec4 = ImGuiTheme.text 82 | 83 | switch message.level { 84 | case .debug: 85 | color = ImGuiTheme.consoleDebug 86 | case .info: 87 | color = ImGuiTheme.consoleInfo 88 | case .warn: 89 | color = ImGuiTheme.consoleWarn 90 | case .error: 91 | color = ImGuiTheme.consoleError 92 | } 93 | 94 | ImGuiPushStyleColor(Im(ImGuiCol_Text), color) 95 | ImGuiTextV(message.str) 96 | ImGuiPopStyleColor(1) 97 | } 98 | 99 | // Auto scroll to button 100 | if autoScrolled && ImGuiGetScrollY() >= ImGuiGetScrollMaxY() { 101 | ImGuiSetScrollHereY(1.0) 102 | } 103 | 104 | ImGuiEndChild() 105 | 106 | ImGuiEnd() 107 | } 108 | } 109 | -------------------------------------------------------------------------------- /Sources/Palico/Scene/Scene+Object.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Scene+Object.swift 3 | // Palico 4 | // 5 | // Created by Junhao Wang on 1/13/22. 6 | // 7 | 8 | import MothECS 9 | 10 | extension Scene { 11 | // Create 12 | @discardableResult 13 | public func createEmptyGameObject() -> GameObject { 14 | let gameObject = GameObject(self) 15 | gameObjectMap[gameObject.entityID] = gameObject 16 | return gameObject 17 | } 18 | 19 | @discardableResult 20 | public func createPrimitive(type: PrimitiveType) -> Primitive { 21 | switch type { 22 | case .cube: 23 | let gameObject = Cube(self) 24 | gameObjectMap[gameObject.entityID] = gameObject 25 | return gameObject 26 | case .sphere: 27 | let gameObject = Sphere(self) 28 | gameObjectMap[gameObject.entityID] = gameObject 29 | return gameObject 30 | case .hemisphere: 31 | let gameObject = Hemisphere(self) 32 | gameObjectMap[gameObject.entityID] = gameObject 33 | return gameObject 34 | case .plane: 35 | let gameObject = Plane(self) 36 | gameObjectMap[gameObject.entityID] = gameObject 37 | return gameObject 38 | case .capsule: 39 | let gameObject = Capsule(self) 40 | gameObjectMap[gameObject.entityID] = gameObject 41 | return gameObject 42 | case .cylinder: 43 | let gameObject = Cylinder(self) 44 | gameObjectMap[gameObject.entityID] = gameObject 45 | return gameObject 46 | case .cone: 47 | let gameObject = Cone(self) 48 | gameObjectMap[gameObject.entityID] = gameObject 49 | return gameObject 50 | } 51 | } 52 | 53 | @discardableResult 54 | public func createSceneLight(type: LightType) -> SceneLight { 55 | switch type { 56 | case .dirLight: 57 | let gameObject = SceneLight(self, name: "Directional Light", type: .dirLight) 58 | gameObjectMap[gameObject.entityID] = gameObject 59 | return gameObject 60 | case .pointLight: 61 | let gameObject = SceneLight(self, name: "Point Light", type: .pointLight) 62 | gameObjectMap[gameObject.entityID] = gameObject 63 | return gameObject 64 | case .spotLight: 65 | let gameObject = SceneLight(self, name: "Spot Light", type: .spotLight) 66 | gameObjectMap[gameObject.entityID] = gameObject 67 | return gameObject 68 | case .ambientLight: 69 | let gameObject = SceneLight(self, name: "Ambient Light", type: .ambientLight) 70 | gameObjectMap[gameObject.entityID] = gameObject 71 | return gameObject 72 | } 73 | } 74 | 75 | // Add 76 | public func addGameObject(_ gameObject: GameObject) { 77 | gameObjectMap[gameObject.entityID] = gameObject 78 | } 79 | 80 | public func addGameObjects(_ gameObjects: [GameObject]) { 81 | for gameObject in gameObjects { 82 | addGameObject(gameObject) 83 | } 84 | } 85 | 86 | // Get 87 | public func getGameObjectBy(entityID: MothEntityID) -> GameObject { 88 | return gameObjectMap[entityID]! 89 | } 90 | 91 | // Destroy 92 | @discardableResult 93 | public func destroyGameObject(_ gameObject: GameObject) -> Bool { 94 | let result: Bool = moth.removeEntity(entityID: gameObject.entityID) 95 | if result { 96 | gameObjectMap[gameObject.entityID] = nil 97 | } 98 | return result 99 | } 100 | } 101 | -------------------------------------------------------------------------------- /Sources/Palico/Input/KeyCode.swift: -------------------------------------------------------------------------------- 1 | // 2 | // KeyCode.swift 3 | // Palico 4 | // 5 | // Created by Junhao Wang on 12/19/21. 6 | // 7 | 8 | public typealias KeyCode = UInt16 9 | 10 | public enum Key: KeyCode { 11 | // Currently only supports Cocoa framework 12 | // https://stackoverflow.com/questions/36900825/where-are-all-the-cocoa-keycodes 13 | 14 | // Digits 15 | case d0 = 29 16 | case d1 = 18 17 | case d2 = 19 18 | case d3 = 20 19 | case d4 = 21 20 | case d5 = 23 21 | case d6 = 22 22 | case d7 = 26 23 | case d8 = 28 24 | case d9 = 25 25 | 26 | // Alphabets 27 | case A = 0 28 | case B = 11 29 | case C = 8 30 | case D = 2 31 | case E = 14 32 | case F = 3 33 | case G = 5 34 | case H = 4 35 | case I = 34 36 | case J = 38 37 | case K = 40 38 | case L = 37 39 | case M = 46 40 | case N = 45 41 | case O = 31 42 | case P = 35 43 | case Q = 12 44 | case R = 15 45 | case S = 1 46 | case T = 17 47 | case U = 32 48 | case V = 9 49 | case W = 13 50 | case X = 7 51 | case Y = 16 52 | case Z = 6 53 | 54 | case sectionSign = 10 55 | case grave = 50 56 | case minus = 27 57 | case equal = 24 58 | case leftBracket = 33 59 | case RightBracket = 30 60 | case Semicolon = 41 61 | case quote = 39 62 | case comma = 43 63 | case period = 47 64 | case slash = 44 65 | case backslash = 42 66 | 67 | case keypad0 = 82 68 | case keypad1 = 83 69 | case keypad2 = 84 70 | case keypad3 = 85 71 | case keypad4 = 86 72 | case keypad5 = 87 73 | case keypad6 = 88 74 | case keypad7 = 89 75 | case keypad8 = 91 76 | case keypad9 = 92 77 | case keypadDecimal = 65 78 | case keypadMultiply = 67 79 | case keypadPlus = 69 80 | case keypadDivide = 75 81 | case keypadMinus = 78 82 | case keypadEquals = 81 83 | case keypadClear = 71 84 | case keypadEnter = 76 85 | 86 | case space = 49 87 | case enter = 36 /* return */ 88 | case tab = 48 89 | case delete = 51 90 | case forwardDelete = 117 91 | case linefeed = 52 92 | case escape = 53 93 | 94 | // Modifiers 95 | case command = 55 96 | case shift = 56 97 | case capsLock = 57 98 | case option = 58 99 | case control = 59 100 | case rightShift = 60 101 | case rightOption = 61 102 | case rightControl = 62 103 | case function = 63 104 | 105 | case f1 = 122 106 | case f2 = 120 107 | case f3 = 99 108 | case f4 = 118 109 | case f5 = 96 110 | case f6 = 97 111 | case f7 = 98 112 | case f8 = 100 113 | case f9 = 101 114 | case f10 = 109 115 | case f11 = 103 116 | case f12 = 111 117 | case f13 = 105 118 | case f14 = 107 119 | case f15 = 113 120 | case f16 = 106 121 | case f17 = 64 122 | case f18 = 79 123 | case f19 = 80 124 | case f20 = 90 125 | 126 | case volumeUp = 72 127 | case volumeDown = 73 128 | case mute = 74 129 | case insert = 114 /* help */ 130 | case home = 115 131 | case end = 119 132 | case pageUp = 116 133 | case pageDown = 121 134 | case leftArrow = 123 135 | case rightArrow = 124 136 | case downArrow = 125 137 | case upArrow = 126 138 | 139 | case unknown = 199 140 | } 141 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Palico Engine: Metal-Based Game Engine in Swift 🐑 2 | 3 | [![macOS](https://github.com/forkercat/PalicoEngine/actions/workflows/ci-macos.yml/badge.svg)](https://github.com/forkercat/PalicoEngine/actions/workflows/ci-macos.yml) 4 | [![license](https://img.shields.io/badge/license-MIT-brightgreen.svg)](LICENSE) 5 | 6 | [![platform-compatibility](https://img.shields.io/endpoint?url=https%3A%2F%2Fswiftpackageindex.com%2Fapi%2Fpackages%2Fforkercat%2FPalicoEngine%2Fbadge%3Ftype%3Dplatforms)](https://swiftpackageindex.com/forkercat/PalicoEngine) 7 | [![swift-version-compatibility](https://img.shields.io/endpoint?url=https%3A%2F%2Fswiftpackageindex.com%2Fapi%2Fpackages%2Fforkercat%2FPalicoEngine%2Fbadge%3Ftype%3Dswift-versions)](https://swiftpackageindex.com/forkercat/PalicoEngine) 8 | 9 | Implement a game engine on macOS using Metal API. Still in development. Currently I am working on a more capable entity-component-system [MothECS](https://github.com/forkercat/MothECS) :) 10 | 11 |

12 | Palico Engine Screenshot 13 |

14 | 15 | [Palico](https://monsterhunterworld.wiki.fextralife.com/Palicoes) - It is a cat-like combat companion in Monster Hunter! 16 | 17 |

18 | This is Palico! Credits: Francesco 19 |

20 | 21 | Palico Engine's [Sprint Board](https://forkercat.atlassian.net/jira/software/projects/PALICO/boards/1) on Jira (need permission request). Currently there are **65 issues** in total! 22 | 23 | 24 | ## 🔧 Install & Run 25 | 26 | ```sh 27 | # Clone 28 | git clone https://github.com/forkercat/PalicoEngine.git 29 | cd PalicoEngine 30 | 31 | # Compile 32 | swift build 33 | 34 | # Run 35 | swift run Editor 36 | ``` 37 | 38 | 39 | ## 📝 User Guide 40 | 41 | Mouse Controls: 42 | - `Command + Left`: Rotate camera 43 | - `Right`: Look around 44 | - `Middle`: Pan 45 | - `Scroll`: Zoom in/out 46 | 47 | Keyboard Controls: 48 | - `Tab`: Select next in-scene object 49 | - `F`: Focus on object 50 | - `Q`: No action 51 | - `W`: Translate 52 | - `E`: Rotate 53 | - `R`: Scale 54 | 55 | Create GameObject: 56 |

57 | Palico Engine Screenshot 58 |

59 | 60 | 61 | ## 🍻 Dependencies 62 | 63 | ```swift 64 | dependencies: [ 65 | .package(url: "https://github.com/forkercat/OhMyLog.git", .branch("main")), 66 | .package(url: "https://github.com/forkercat/MathLib.git", .branch("main")), 67 | .package(url: "https://github.com/forkercat/MothECS.git", .branch("main")), 68 | .package(url: "https://github.com/forkercat/SwiftImGui.git", .branch("update-1.86-docking")), // forked from @ctreffs 69 | .package(url: "https://github.com/forkercat/SwiftImGuizmo.git", .branch("master")), // forked from @ctreffs 70 | ], 71 | ``` 72 | 73 | Thanks to SwiftImGui by [@ctreffs](https://github.com/ctreffs) I am able to use ImGui in this application. 74 | 75 | I forked the repository and wrapped ImGui v1.86 and added new OSX backend file. Related PRs: 76 | - [Update to 1.86 and update OSX backend (fix keyboard issue) #7](https://github.com/ctreffs/SwiftImGui/pull/7) 77 | - [Update ImGui 1.86-docking #8](https://github.com/ctreffs/SwiftImGui/pull/8) 78 | 79 | 80 | ## 🥺 Future Development 81 | 82 | Rendering: 83 | - [ ] Add skybox 84 | - [ ] Add shadow 85 | - [ ] Support PBR 86 | - [ ] Support deferred rendering (render pass has already been setup) 87 | 88 | Model: 89 | - [ ] Load 3D models 90 | - [ ] Load textures in models 91 | - [ ] Load animation 92 | 93 | Other: 94 | - [ ] Improve MothECS 95 | - [ ] Integrate ImGuizmo 96 | - [ ] Property/Inspector panel 97 | - [ ] Game object selection 98 | - [ ] Scene loading (yaml) 99 | - [ ] Content browser (Asset Panel) 100 | 101 | 102 | ## 🙏 Reference 103 | 104 | Started by following [game engine turotial](https://www.youtube.com/playlist?list=PLlrATfBNZ98dC-V-N3m0Go4deliWHPFwT) series by [TheCherno](https://www.youtube.com/c/TheChernoProject) and wrote implementation in C++. Also check out [Hazel Engine](https://github.com/thecherno/hazel) repository. It is a great learning resource! 105 | 106 | - [Metal by Tutorial](https://www.raywenderlich.com/books/metal-by-tutorials/v2.0) by Caroline Begbie & Marius Horga 107 | - ImGui on GitHub: [ocornut/imgui](https://github.com/ocornut/imgui), [cimgui/cimgui](https://github.com/cimgui/cimgui), [ctreffs/SwiftImGui](https://github.com/ctreffs/SwiftImGui) 108 | - More to be added! 109 | -------------------------------------------------------------------------------- /Sources/Palico/Assets/Shaders/Main.metal: -------------------------------------------------------------------------------- 1 | // 2 | // Main.metal 3 | // Palico 4 | // 5 | // Created by Junhao Wang on 12/28/21. 6 | // 7 | 8 | using namespace metal; 9 | 10 | namespace Palico { 11 | 12 | // In 13 | struct VertexIn { 14 | float4 position [[ attribute(Position) ]]; 15 | float3 normal [[ attribute(Normal) ]]; 16 | float2 uv [[ attribute(UV) ]]; 17 | }; 18 | 19 | // Out 20 | struct VertexOut { 21 | float4 position [[ position ]]; 22 | float2 uv; 23 | float3 worldNormal; 24 | float3 worldPosition; 25 | }; 26 | 27 | // Vertex 28 | vertex VertexOut vertex_main( 29 | const VertexIn in [[ stage_in ]], 30 | constant VertexUniformData& vertexUniform [[ buffer(Buffer::vertexUniform) ]]) { 31 | 32 | float4 positionOS = in.position; 33 | float4 positionWS = vertexUniform.modelMatrix * positionOS; 34 | 35 | float4 output = vertexUniform.projectionMatrix * vertexUniform.viewMatrix * positionWS; 36 | 37 | VertexOut out { 38 | .position = output, 39 | .worldNormal = vertexUniform.normalMatrix * in.normal, 40 | .worldPosition = positionWS.xyz, 41 | .uv = in.uv 42 | }; 43 | return out; 44 | } 45 | 46 | // Fragment 47 | fragment float4 fragment_main( 48 | const VertexOut in [[ stage_in ]], 49 | constant FragmentUniformData& fragmentUniform [[ buffer(Buffer::fragmentUniform) ]], 50 | constant LightData* lightData [[ buffer(Buffer::lightData) ]]) { 51 | 52 | // Temp for light objects 53 | if (fragmentUniform.noLight) { 54 | return fragmentUniform.tintColor; 55 | } 56 | 57 | float3 normalWS = normalize(in.worldNormal); 58 | float3 tintColor = fragmentUniform.tintColor.xyz; 59 | float materialShininess = 32; 60 | float3 materialSpecularColor = float3(1, 1, 1); 61 | 62 | // Output Color 63 | float3 diffuseColor = 0; 64 | float3 specularColor = 0; 65 | float3 ambientColor = 0; 66 | 67 | for (uint i = 0; i < fragmentUniform.lightCount; i++) { 68 | LightData light = lightData[i]; 69 | if (light.type == DirLight) { 70 | float3 lightDir = normalize(-light.direction); 71 | float diffuseIntensity = saturate(dot(normalWS, lightDir)); // NdotL 72 | float3 intensity = diffuseIntensity * light.intensity; 73 | diffuseColor += light.color * tintColor * intensity; 74 | 75 | if (diffuseIntensity > 0) { 76 | float3 reflectDir = normalize(reflect(-lightDir, normalWS)); 77 | float3 viewDir = normalize(fragmentUniform.cameraPosition - in.worldPosition); 78 | float specularIntensity = pow(saturate(dot(reflectDir, viewDir)), materialShininess); 79 | float3 sIntensity = specularIntensity * light.intensity; 80 | specularColor += light.color * materialSpecularColor * sIntensity; 81 | } 82 | } else if (light.type == PointLight) { 83 | float3 lightDir = normalize(light.position - in.worldPosition); 84 | float diffuseIntensity = saturate(dot(normalWS, lightDir)); // NdotL 85 | float3 intensity = diffuseIntensity * light.intensity; 86 | diffuseColor += light.color * tintColor * intensity; 87 | 88 | if (diffuseIntensity > 0) { 89 | float3 reflectDir = normalize(reflect(-lightDir, normalWS)); 90 | float3 viewDir = normalize(fragmentUniform.cameraPosition - in.worldPosition); 91 | float specularIntensity = pow(saturate(dot(reflectDir, viewDir)), materialShininess); 92 | float3 sIntensity = specularIntensity * light.intensity; 93 | specularColor += light.color * materialSpecularColor * sIntensity; 94 | } 95 | } else if (light.type == SpotLight) { 96 | continue; // TODO: support spot light 97 | } else if (light.type == AmbientLight) { 98 | float3 intensity = light.intensity; 99 | ambientColor += light.color * intensity; 100 | } 101 | } 102 | 103 | float3 color = diffuseColor + specularColor + ambientColor; 104 | return float4(color, 1); 105 | } 106 | 107 | } // Palico 108 | -------------------------------------------------------------------------------- /Sources/Palico/Assets/Fonts/Ruda/OFL.txt: -------------------------------------------------------------------------------- 1 | Copyright 2019 The Ruda Project Authors (https://github.com/marmonsalve/Ruda-new) 2 | 3 | This Font Software is licensed under the SIL Open Font License, Version 1.1. 4 | This license is copied below, and is also available with a FAQ at: 5 | http://scripts.sil.org/OFL 6 | 7 | 8 | ----------------------------------------------------------- 9 | SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007 10 | ----------------------------------------------------------- 11 | 12 | PREAMBLE 13 | The goals of the Open Font License (OFL) are to stimulate worldwide 14 | development of collaborative font projects, to support the font creation 15 | efforts of academic and linguistic communities, and to provide a free and 16 | open framework in which fonts may be shared and improved in partnership 17 | with others. 18 | 19 | The OFL allows the licensed fonts to be used, studied, modified and 20 | redistributed freely as long as they are not sold by themselves. The 21 | fonts, including any derivative works, can be bundled, embedded, 22 | redistributed and/or sold with any software provided that any reserved 23 | names are not used by derivative works. The fonts and derivatives, 24 | however, cannot be released under any other type of license. The 25 | requirement for fonts to remain under this license does not apply 26 | to any document created using the fonts or their derivatives. 27 | 28 | DEFINITIONS 29 | "Font Software" refers to the set of files released by the Copyright 30 | Holder(s) under this license and clearly marked as such. This may 31 | include source files, build scripts and documentation. 32 | 33 | "Reserved Font Name" refers to any names specified as such after the 34 | copyright statement(s). 35 | 36 | "Original Version" refers to the collection of Font Software components as 37 | distributed by the Copyright Holder(s). 38 | 39 | "Modified Version" refers to any derivative made by adding to, deleting, 40 | or substituting -- in part or in whole -- any of the components of the 41 | Original Version, by changing formats or by porting the Font Software to a 42 | new environment. 43 | 44 | "Author" refers to any designer, engineer, programmer, technical 45 | writer or other person who contributed to the Font Software. 46 | 47 | PERMISSION & CONDITIONS 48 | Permission is hereby granted, free of charge, to any person obtaining 49 | a copy of the Font Software, to use, study, copy, merge, embed, modify, 50 | redistribute, and sell modified and unmodified copies of the Font 51 | Software, subject to the following conditions: 52 | 53 | 1) Neither the Font Software nor any of its individual components, 54 | in Original or Modified Versions, may be sold by itself. 55 | 56 | 2) Original or Modified Versions of the Font Software may be bundled, 57 | redistributed and/or sold with any software, provided that each copy 58 | contains the above copyright notice and this license. These can be 59 | included either as stand-alone text files, human-readable headers or 60 | in the appropriate machine-readable metadata fields within text or 61 | binary files as long as those fields can be easily viewed by the user. 62 | 63 | 3) No Modified Version of the Font Software may use the Reserved Font 64 | Name(s) unless explicit written permission is granted by the corresponding 65 | Copyright Holder. This restriction only applies to the primary font name as 66 | presented to the users. 67 | 68 | 4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font 69 | Software shall not be used to promote, endorse or advertise any 70 | Modified Version, except to acknowledge the contribution(s) of the 71 | Copyright Holder(s) and the Author(s) or with their explicit written 72 | permission. 73 | 74 | 5) The Font Software, modified or unmodified, in part or in whole, 75 | must be distributed entirely under this license, and must not be 76 | distributed under any other license. The requirement for fonts to 77 | remain under this license does not apply to any document created 78 | using the Font Software. 79 | 80 | TERMINATION 81 | This license becomes null and void if any of the above conditions are 82 | not met. 83 | 84 | DISCLAIMER 85 | THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 86 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF 87 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT 88 | OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE 89 | COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 90 | INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL 91 | DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 92 | FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM 93 | OTHER DEALINGS IN THE FONT SOFTWARE. 94 | -------------------------------------------------------------------------------- /Sources/Palico/Scene/GameObject.swift: -------------------------------------------------------------------------------- 1 | // 2 | // GameObject.swift 3 | // Palico 4 | // 5 | // Created by Junhao Wang on 12/26/21. 6 | // 7 | 8 | import MathLib 9 | import MothECS 10 | 11 | public class GameObject { 12 | public var name: String = "GameObject" 13 | public var enabled: Bool = true 14 | 15 | public let entityID: MothEntityID 16 | 17 | internal weak var scene: Scene! = nil 18 | 19 | init(_ scene: Scene, 20 | name: String = "GameObject", 21 | position: Float3 = [0, 0, 0], 22 | rotation: Float3 = [0, 0, 0], 23 | scale: Float3 = [1, 1, 1]) { 24 | 25 | self.scene = scene 26 | self.name = name 27 | entityID = scene.moth.createEntity() 28 | 29 | let transform = TransformComponent() 30 | transform.position = position 31 | transform.rotation = rotation 32 | transform.scale = scale 33 | let tag = TagComponent() 34 | 35 | addComponent(tag) 36 | addComponent(transform) 37 | } 38 | 39 | // Editor Update 40 | public func onUpdateEditor(deltaTime ts: Timestep) { 41 | if hasComponent(ScriptComponent.self) { 42 | let scriptComponent = getComponent(ScriptComponent.self) 43 | guard scriptComponent.enabled else { 44 | return 45 | } 46 | scriptComponent.nativeScript?.onUpdateEditor(deltaTime: ts) 47 | } 48 | } 49 | 50 | // Runtime Update 51 | public func onUpdateRuntime(deltaTime ts: Timestep) { 52 | if hasComponent(ScriptComponent.self) { 53 | let scriptComponent = getComponent(ScriptComponent.self) 54 | guard scriptComponent.enabled else { 55 | return 56 | } 57 | scriptComponent.nativeScript?.onUpdate(deltaTime: ts) 58 | } 59 | } 60 | } 61 | 62 | extension GameObject: Equatable { 63 | public static func == (lhs: GameObject, rhs: GameObject) -> Bool { 64 | return lhs.entityID == rhs.entityID 65 | } 66 | } 67 | 68 | extension GameObject: Hashable { 69 | public func hash(into hasher: inout Hasher) { 70 | hasher.combine(entityID) 71 | } 72 | } 73 | 74 | // Component Methods 75 | extension GameObject { 76 | public func addComponent(_ component: T) { 77 | scene.moth.assignComponent(component, to: entityID) 78 | 79 | if let scriptComponent = component as? ScriptComponent { 80 | scriptComponent.nativeScript?.gameObject = self 81 | guard scriptComponent.enabled else { 82 | return 83 | } 84 | scriptComponent.nativeScript?.onCreate() 85 | } 86 | } 87 | 88 | public func addComponent(_ type: T.Type) { 89 | let component = scene.moth.createComponent(type, to: entityID) 90 | 91 | if let scriptComponent = component as? ScriptComponent { 92 | scriptComponent.nativeScript?.gameObject = self 93 | guard scriptComponent.enabled else { 94 | return 95 | } 96 | scriptComponent.nativeScript?.onCreate() 97 | } 98 | } 99 | 100 | public func hasComponent(_ type: T.Type) -> Bool { 101 | return scene.moth.hasComponent(type, in: entityID) 102 | } 103 | 104 | public func getComponent(_ type: T.Type) -> T { 105 | return scene.moth.getComponent(type, from: entityID) 106 | } 107 | 108 | @discardableResult 109 | public func removeComponent(_ component: T) -> T? { 110 | guard !(component is TagComponent) || !(component is TransformComponent) else { 111 | Console.warn("\(T.self) cannot be removed from a game object!") 112 | return nil 113 | } 114 | 115 | if let scriptComponent = component as? ScriptComponent { 116 | scriptComponent.nativeScript?.onDestroy() 117 | } 118 | 119 | return scene.moth.removeComponent(T.self, from: entityID) 120 | } 121 | 122 | @discardableResult 123 | public func removeComponent(_ type: T.Type) -> T? { 124 | guard type != TagComponent.self && type != TransformComponent.self else { 125 | Console.warn("\(T.self) cannot be removed from a game object!") 126 | return nil 127 | } 128 | 129 | let component = scene.moth.removeComponent(T.self, from: entityID) 130 | 131 | if let scriptComponent = component as? ScriptComponent { 132 | scriptComponent.nativeScript?.onDestroy() 133 | } 134 | 135 | return component 136 | } 137 | } 138 | -------------------------------------------------------------------------------- /Sources/Palico/Core/Console.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Console.swift 3 | // Palico 4 | // 5 | // Created by Junhao Wang on 1/8/22. 6 | // 7 | 8 | import Foundation 9 | 10 | public class Console { 11 | public struct Message { 12 | public enum Level: Int { 13 | case debug = 0 14 | case info = 1 15 | case warn = 2 16 | case error = 3 17 | 18 | public var str: String { 19 | switch self { 20 | case .debug: return Self.levelStrings[0] 21 | case .info: return Self.levelStrings[1] 22 | case .warn: return Self.levelStrings[2] 23 | case .error: return Self.levelStrings[3] 24 | } 25 | } 26 | 27 | public static let levelStrings: [String] = ["DEBUG", "INFO", "WARN", "ERROR"] 28 | public static let levelStringsWithIcons: [String] = ["\(FAIcon.search) DEBUG", 29 | "\(FAIcon.commentDots) INFO", 30 | "\(FAIcon.exclamationTriangle) WARN", 31 | "\(FAIcon.timesCircle) ERROR"] 32 | public static var numLevels: Int = levelStrings.count 33 | } 34 | 35 | public var timestamp: String = "00:00:00.000" 36 | public var level: Level = .info 37 | public var content: String = "" 38 | 39 | public var str: String { get { 40 | "[\(timestamp)] \(level.str) - \(content)" 41 | }} 42 | } 43 | 44 | private static let maxMessageCount: Int = 1000 45 | private static var messages: [Message?] = [Message?](repeating: nil, count: maxMessageCount) 46 | private static var start: Int = 0 47 | private static var end: Int = 0 48 | public private(set) static var numMessages: Int = 0 49 | 50 | private static let dateFormatter: DateFormatter = DateFormatter() 51 | private static let timePattern = "hh:mm:ss.SSS" 52 | 53 | private init() { 54 | 55 | } 56 | 57 | public static func initialize() { 58 | dateFormatter.dateFormat = timePattern 59 | clear() 60 | } 61 | 62 | public static func getMessageList() -> [Message?] { 63 | return messages 64 | } 65 | 66 | private static var outputCursor: Int = start 67 | 68 | public static func nextMessage() -> Message? { 69 | guard outputCursor != end else { // including the case where numMessages == 0 70 | outputCursor = start // reset 71 | return nil 72 | } 73 | 74 | let msg = messages[outputCursor] 75 | outputCursor = (outputCursor + 1) % maxMessageCount 76 | return msg 77 | } 78 | 79 | public static func clear() { 80 | numMessages = 0 81 | start = 0 82 | end = 0 83 | outputCursor = start 84 | for index in messages.indices { 85 | messages[index] = nil 86 | } 87 | } 88 | 89 | // [S(E), _, _, _, _] // start 90 | // [S, E, _, _, _] 91 | // [S, _, _, _, E] // numMessage = 5 92 | // [E, S, _, _, _] // numMessage = 5 (kick out the earlist message) 93 | // [_, _, _, E, S] 94 | // [S, _, _, _, E] 95 | // [E, S, _, _, _] 96 | private static func addMessage(_ message: Message) { 97 | messages[end] = message 98 | numMessages = min(numMessages + 1, maxMessageCount) 99 | 100 | end = (end + 1) % maxMessageCount 101 | 102 | // Update start when end overlaps 103 | if start == end { 104 | start = (start + 1) % maxMessageCount 105 | } 106 | } 107 | 108 | @inline(__always) 109 | public static func debug(_ obj: Any) { // debug 110 | debug("\(obj)") 111 | } 112 | 113 | @inline(__always) 114 | public static func debug(_ msg: String) { 115 | handleMessage(msg, level: .debug) 116 | } 117 | 118 | @inline(__always) 119 | public static func info(_ obj: Any) { // info 120 | info("\(obj)") 121 | } 122 | 123 | @inline(__always) 124 | public static func info(_ msg: String) { 125 | handleMessage(msg, level: .info) 126 | } 127 | 128 | @inline(__always) 129 | public static func warn(_ obj: Any) { // warn 130 | warn("\(obj)") 131 | } 132 | 133 | @inline(__always) 134 | public static func warn(_ msg: String) { 135 | handleMessage(msg, level: .warn) 136 | } 137 | 138 | @inline(__always) 139 | public static func error(_ obj: Any) { // error 140 | error("\(obj)") 141 | } 142 | 143 | @inline(__always) 144 | public static func error(_ msg: String) { 145 | handleMessage(msg, level: .error) 146 | } 147 | 148 | private static func handleMessage(_ msg: String, level: Message.Level) { 149 | var message = Message() 150 | 151 | message.timestamp = dateFormatter.string(from: Date()) 152 | message.level = level 153 | message.content = msg 154 | addMessage(message) 155 | } 156 | } 157 | -------------------------------------------------------------------------------- /Sources/Editor/Panels/ScenePanel+Hierarchy.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ScenePanel+Hierarchy.swift 3 | // Editor 4 | // 5 | // Created by Junhao Wang on 1/13/22. 6 | // 7 | 8 | import Palico 9 | import ImGui 10 | 11 | extension ScenePanel { 12 | func drawHierarchyPanel() { 13 | ImGuiBegin("\(FAIcon.list) \(hierarchyPanelName)", nil, 0) 14 | 15 | let gameObjectList = scene.gameObjectList 16 | for gameObject in gameObjectList { 17 | drawGameObjectNode(gameObject) 18 | } 19 | 20 | // Left-click on blank space: Delete game object 21 | if ImGuiIsMouseClicked(Im(ImGuiMouseButton_Left), false) && ImGuiIsWindowHovered(0) { 22 | selectedEntityID = .invalid 23 | } 24 | 25 | // Right-click on blank space: Pop-up for creation 26 | if ImGuiBeginPopupContextWindow(nil, Im(ImGuiPopupFlags_MouseButtonRight) | Im(ImGuiPopupFlags_NoOpenOverItems)) { 27 | drawGameObjectCreationMenuItems() 28 | ImGuiEndPopup() 29 | } 30 | 31 | ImGuiHelpMarker("README", """ 32 | Currently object selection in viewport is not supported. 33 | Use \(FAIcon.keyboard) [ Tab ] to navigate in-scene objects. 34 | Use \(FAIcon.keyboard) [ F ] to focus on object. 35 | Use \(FAIcon.mouse) [ RightClick ] to delete/create objects. 36 | Do not press \(FAIcon.keyboard) [ Ctrl+Tab ]. I don't know why it crashes yet. \(FAIcon.sadCry) 37 | """) 38 | 39 | ImGuiEnd() 40 | } 41 | 42 | private func drawGameObjectNode(_ gameObject: GameObject) { 43 | ImGuiPushStyleVar(Im(ImGuiStyleVar_ItemSpacing), ImVec2(8, 6)) 44 | ImGuiPushStyleVar(Im(ImGuiStyleVar_FramePadding), ImVec2(1, 3)) 45 | 46 | var flags: ImGuiTreeNodeFlags = Im(ImGuiTreeNodeFlags_SpanAvailWidth) | Im(ImGuiTreeNodeFlags_OpenOnArrow) | Im(ImGuiTreeNodeFlags_OpenOnDoubleClick) 47 | flags |= (gameObject.entityID == selectedEntityID) ? Im(ImGuiTreeNodeFlags_Selected) : 0 48 | 49 | var icon: String = FAIcon.cube 50 | if gameObject is Primitive { 51 | icon = FAIcon.shapes 52 | } else if gameObject is SceneLight { 53 | icon = FAIcon.lightbulb 54 | } 55 | 56 | let opened: Bool = ImGuiTreeNodeEx("\(icon) \(gameObject.name)", flags) 57 | // let opened: Bool = ImGuiTreeNodeEx("\(icon) [ID: \(gameObject.entityID)] \(gameObject.name)", flags) 58 | 59 | if ImGuiIsItemClicked(Im(ImGuiMouseButton_Left)) { 60 | selectedEntityID = gameObject.entityID 61 | } 62 | 63 | // Delete Game Object 64 | var isGameObjectDeleted: Bool = false 65 | if ImGuiBeginPopupContextItem(nil, 1) { 66 | if ImGuiMenuItem("\(FAIcon.trashAlt) Delete", nil, false, true) { 67 | isGameObjectDeleted = true 68 | } 69 | ImGuiEndPopup() 70 | } 71 | 72 | if opened { 73 | ImGuiTextV("EntityID: \(gameObject.entityID)") 74 | ImGuiTreePop() 75 | } 76 | 77 | ImGuiPopStyleVar(2) // ItemSpacing & FramePadding 78 | 79 | // Hanlde Deletion 80 | if isGameObjectDeleted { 81 | scene.destroyGameObject(gameObject) 82 | selectedEntityID = .invalid 83 | } 84 | } 85 | 86 | func drawGameObjectCreationMenuItems() { 87 | if ImGuiMenuItem("\(FAIcon.cube) Create Empty", nil, false, true) { 88 | scene.createEmptyGameObject() 89 | } 90 | 91 | if ImGuiBeginMenu("\(FAIcon.shapes) Create Primitives", true) { 92 | if ImGuiMenuItem("Cube", nil, false, true) { 93 | scene.createPrimitive(type: .cube) 94 | } 95 | if ImGuiMenuItem("Sphere", nil, false, true) { 96 | scene.createPrimitive(type: .sphere) 97 | } 98 | if ImGuiMenuItem("Hemisphere", nil, false, true) { 99 | scene.createPrimitive(type: .hemisphere) 100 | } 101 | if ImGuiMenuItem("Plane", nil, false, true) { 102 | scene.createPrimitive(type: .plane) 103 | } 104 | if ImGuiMenuItem("Capsule", nil, false, true) { 105 | scene.createPrimitive(type: .capsule) 106 | } 107 | if ImGuiMenuItem("Cylinder", nil, false, true) { 108 | scene.createPrimitive(type: .cylinder) 109 | } 110 | if ImGuiMenuItem("Cone", nil, false, true) { 111 | scene.createPrimitive(type: .cone) 112 | } 113 | ImGuiEndMenu() 114 | } 115 | 116 | if ImGuiBeginMenu("\(FAIcon.lightbulb) Create Lights", true) { 117 | if ImGuiMenuItem("Directional Light", nil, false, true) { 118 | scene.createSceneLight(type: .dirLight) 119 | } 120 | if ImGuiMenuItem("Point Light", nil, false, true) { 121 | scene.createSceneLight(type: .pointLight) 122 | } 123 | if ImGuiMenuItem("Spot Light", nil, false, true) { 124 | scene.createSceneLight(type: .spotLight) 125 | } 126 | if ImGuiMenuItem("Ambient Light", nil, false, true) { 127 | scene.createSceneLight(type: .ambientLight) 128 | } 129 | ImGuiEndMenu() 130 | } 131 | } 132 | } 133 | -------------------------------------------------------------------------------- /Sources/Palico/ImGui/ImGuiTheme.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ImGuiTheme.swift 3 | // Palico 4 | // 5 | // Created by Junhao Wang on 1/15/22. 6 | // 7 | 8 | import ImGui 9 | 10 | public struct ImGuiTheme { 11 | // Theme 12 | public static var mainTheme = ImVec4(0.84, 0.65, 0.19, 1.0) 13 | 14 | public static var componentX = ImVec4(0.80, 0.35, 0.35, 1.0) 15 | public static var componentHoveredX = ImVec4(0.90, 0.35, 0.35, 1.0) 16 | 17 | public static var componentY = ImVec4(0.35, 0.80, 0.35, 1.0) 18 | public static var componentHoveredY = ImVec4(0.40, 0.85, 0.40, 1.0) 19 | 20 | public static var componentZ = ImVec4(0.35, 0.56, 0.76, 1.0) 21 | public static var componentHoveredZ = ImVec4(0.40, 0.65, 0.90, 1.0) 22 | 23 | // Console 24 | public static var consoleDebug = ImVec4(0.40, 0.90, 0.40, 1.0) 25 | public static var consoleInfo = text 26 | public static var consoleWarn = ImVec4(0.90, 0.90, 0.40, 1.0) 27 | public static var consoleError = ImVec4(0.90, 0.40, 0.40, 1.0) 28 | 29 | // General 30 | public static var windowBg = ImVec4(0.15, 0.15, 0.15, 1.0) 31 | public static var popupBg = ImVec4(0.15, 0.15, 0.15, 0.95) 32 | public static var text = ImVec4(0.90, 0.90, 0.90, 1.0) 33 | 34 | public static var normal = ImVec4(0.20, 0.21, 0.21, 1.0) 35 | public static var hovered = ImVec4(0.30, 0.30, 0.30, 1.0) 36 | public static var active = ImVec4(0.15, 0.15, 0.15, 1.0) 37 | 38 | public static var enabled = ImVec4(0.30, 0.30, 0.30, 1.0) 39 | public static var disabled = ImVec4(0.20, 0.21, 0.21, 1.0) 40 | 41 | public static var tabNormal = ImVec4(0.15, 0.15, 0.15, 1.0) 42 | public static var tabHovered = ImVec4(0.38, 0.38, 0.38, 1.0) 43 | public static var tabActive = ImVec4(0.28, 0.28, 0.28, 1.0) 44 | public static var tabUnfocused = ImVec4(0.15, 0.15, 0.15, 1.0) 45 | public static var tabUnfocusedActive = ImVec4(0.20, 0.21, 0.21, 1.0) 46 | 47 | public static var titleBg = ImVec4(0.15, 0.15, 0.15, 1.0) 48 | public static var titleBgActive = ImVec4(0.15, 0.15, 0.15, 1.0) 49 | public static var titleBgCollapsed = ImVec4(0.15, 0.15, 0.15, 1.0) 50 | 51 | public static var separator = ImVec4(0.23, 0.23, 0.23, 0.5) 52 | public static var separatorHovered = ImVec4(0.23, 0.23, 0.23, 0.8) 53 | public static var separatorActive = ImVec4(0.23, 0.23, 0.23, 0.8) 54 | 55 | public static var scrollBarBg = windowBg 56 | 57 | public func loadTheme() { 58 | ImGui.CArray.write(&ImGuiGetStyle().pointee.Colors) { colors in 59 | colors[Int(Im(ImGuiCol_WindowBg))] = Self.windowBg 60 | colors[Int(Im(ImGuiCol_PopupBg))] = Self.popupBg 61 | colors[Int(Im(ImGuiCol_Text))] = Self.text 62 | 63 | // Follow Main Theme Color 64 | colors[Int(Im(ImGuiCol_DockingPreview))] = Self.mainTheme 65 | colors[Int(Im(ImGuiCol_CheckMark))] = Self.mainTheme 66 | colors[Int(Im(ImGuiCol_SliderGrab))] = Self.mainTheme 67 | colors[Int(Im(ImGuiCol_SliderGrabActive))] = Self.mainTheme 68 | colors[Int(Im(ImGuiCol_ResizeGrip))] = Self.mainTheme 69 | 70 | // Headers 71 | colors[Int(Im(ImGuiCol_Header))] = Self.normal 72 | colors[Int(Im(ImGuiCol_HeaderHovered))] = Self.hovered 73 | colors[Int(Im(ImGuiCol_HeaderActive))] = Self.active 74 | 75 | // Buttons 76 | colors[Int(Im(ImGuiCol_Button))] = Self.normal 77 | colors[Int(Im(ImGuiCol_ButtonHovered))] = Self.hovered 78 | colors[Int(Im(ImGuiCol_ButtonActive))] = Self.active 79 | 80 | // Frame BG 81 | colors[Int(Im(ImGuiCol_FrameBg))] = Self.normal 82 | colors[Int(Im(ImGuiCol_FrameBgHovered))] = Self.hovered 83 | colors[Int(Im(ImGuiCol_FrameBgActive))] = Self.active 84 | 85 | // Tabs 86 | colors[Int(Im(ImGuiCol_Tab))] = Self.tabNormal 87 | colors[Int(Im(ImGuiCol_TabHovered))] = Self.tabHovered 88 | colors[Int(Im(ImGuiCol_TabActive))] = Self.tabActive 89 | colors[Int(Im(ImGuiCol_TabUnfocused))] = Self.tabUnfocused 90 | colors[Int(Im(ImGuiCol_TabUnfocusedActive))] = Self.tabUnfocusedActive 91 | 92 | // Title 93 | colors[Int(Im(ImGuiCol_TitleBg))] = Self.titleBg 94 | colors[Int(Im(ImGuiCol_TitleBgActive))] = Self.titleBgActive 95 | colors[Int(Im(ImGuiCol_TitleBgCollapsed))] = Self.titleBgCollapsed 96 | 97 | // Separator 98 | colors[Int(Im(ImGuiCol_Separator))] = Self.separator 99 | colors[Int(Im(ImGuiCol_SeparatorHovered))] = Self.separatorHovered 100 | colors[Int(Im(ImGuiCol_SeparatorActive))] = Self.separatorActive 101 | 102 | // ScrollBar 103 | colors[Int(Im(ImGuiCol_ScrollbarBg))] = Self.scrollBarBg 104 | } 105 | } 106 | } 107 | -------------------------------------------------------------------------------- /Sources/Palico/Core/Application.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Application.swift 3 | // Palico 4 | // 5 | // Created by Junhao Wang on 12/15/21. 6 | // 7 | 8 | import MathLib 9 | 10 | open class Application { 11 | public private(set) static var shared: Application? 12 | 13 | private let layerStack: LayerStack = LayerStack() 14 | private let imGuiLayer: ImGuiLayer = ImGuiLayer() 15 | 16 | private(set) var window: Window 17 | 18 | public init(name: String = "Palico Engine", arguments: [String] = [], size: Int2) { 19 | Log.registerLogger(name: "Palico", level: .trace) 20 | Log.info("Arguments[1:]: \(arguments.dropFirst())") 21 | 22 | // Applicaiton 23 | assert(Application.shared == nil, "Only one application is allowed!") 24 | defer { Application.shared = self } 25 | 26 | // Context 27 | PlatformContext.initialize() // application 28 | MetalContext.initialize() // graphics 29 | 30 | // Console 31 | Console.initialize() 32 | 33 | // Window 34 | let info = "\(PlatformContext.osName) & \(PlatformContext.platformName) [\(MetalContext.apiName) on \(MetalContext.deviceName)]" 35 | let windowDescriptor = WindowDescriptor(title: "\(name) - \(info)", width: size.width, height: size.height) 36 | window = CocoaWindow(descriptor: windowDescriptor) 37 | defer { 38 | window.windowDelegate = self 39 | } 40 | 41 | Console.info("Welcome to Palico Engine!!! \(FAIcon.laughBeam)") 42 | Console.info("Platform: \(info)") 43 | Console.info("Window: [\(size.width) x \(size.height)]") 44 | 45 | // Renderer 46 | Renderer.initialize() 47 | Renderer.setPreferredFPS(60) 48 | 49 | // ImGui 50 | pushOverlay(imGuiLayer) 51 | } 52 | 53 | public func run() { 54 | PlatformContext.activate() 55 | } 56 | 57 | public func close() { 58 | popOverlay(imGuiLayer) 59 | PlatformContext.deinitialize() 60 | } 61 | 62 | public func pushLayer(_ layer: Layer) { 63 | layerStack.pushLayer(layer) 64 | layer.onAttach() 65 | Console.debug("LayerStack: add layer [\(layer.debugName)]") 66 | } 67 | 68 | public func pushOverlay(_ overlay: Layer) { 69 | layerStack.pushOverlay(overlay) 70 | overlay.onAttach() 71 | Console.debug("LayerStack: add overlay [\(overlay.debugName)]") 72 | } 73 | 74 | public func popLayer(_ layer: Layer) { 75 | layerStack.popLayer(layer) 76 | layer.onAttach() 77 | } 78 | 79 | public func popOverlay(_ overlay: Layer) { 80 | layerStack.popOverlay(overlay) 81 | overlay.onDetach() 82 | } 83 | } 84 | 85 | // Graphics Context Callbacks 86 | extension Application { 87 | func onUpdate() { 88 | guard PlatformContext.isAppRunning && !window.isMinimized else { 89 | return 90 | } 91 | 92 | // Log.debug("FPS: \(1.0 / Time.deltaTime)") 93 | // Log.debug("DeltaTime: \(Time.deltaTime)") 94 | 95 | Renderer.begin() // begin 96 | 97 | // - 1. Layer Update 98 | for layer in layerStack.layers { 99 | layer.onUpdate(deltaTime: Time.deltaTime) 100 | } 101 | 102 | // - 2. Layer ImGuiRender 103 | imGuiLayer.begin() 104 | for layer in layerStack.layers { 105 | layer.onImGuiRender() 106 | } 107 | imGuiLayer.end() 108 | 109 | Renderer.end() // end 110 | } 111 | 112 | func onResize(size: Int2) { 113 | let event = WindowViewResizeEvent(size: size) 114 | onEvent(event: event) 115 | } 116 | } 117 | 118 | // Window Delegate 119 | extension Application: WindowDelegate { 120 | func onEvent(event: Event) { 121 | let dispatcher = EventDispatcher(event: event) 122 | 123 | // 1. Application Level 124 | dispatcher.dispatch(callback: onWindowClose) 125 | dispatcher.dispatch(callback: onWindowViewResize) 126 | dispatcher.dispatch(callback: onEscPressedForDebugging) 127 | 128 | // 2. Layer Level 129 | // Process events in layers (reversed order) Ex: [back, ..., front] 130 | for layer in layerStack.layers.reversed() { 131 | if event.handled { 132 | break 133 | } 134 | layer.onEvent(event: event) 135 | } 136 | } 137 | 138 | private func onWindowClose(event: WindowCloseEvent) -> Bool { 139 | close() 140 | return true 141 | } 142 | 143 | private func onEscPressedForDebugging(event: KeyPressedEvent) -> Bool { 144 | if event.key == .escape { 145 | close() 146 | return true 147 | } else { 148 | return false 149 | } 150 | } 151 | 152 | private func onWindowViewResize(event: WindowViewResizeEvent) -> Bool { 153 | if event.size.width == 0 || event.size.height == 0 { 154 | Log.warn("Window view size is 0. Do not updating.") 155 | return false 156 | } 157 | 158 | // Renderer Resize 159 | // Ex: Renderer::onWindowViewResize 160 | 161 | return false 162 | } 163 | } 164 | 165 | extension Application { 166 | public func SetShouldImGuiTryToBlockEvents(_ blockEvents: Bool) { 167 | imGuiLayer.tryToBlockEvents = blockEvents 168 | } 169 | } 170 | -------------------------------------------------------------------------------- /Sources/Palico/Renderer/RenderPass.swift: -------------------------------------------------------------------------------- 1 | // 2 | // RenderPass.swift 3 | // Palico 4 | // 5 | // Created by Junhao Wang on 12/27/21. 6 | // 7 | 8 | import MetalKit 9 | import MathLib 10 | 11 | public class RenderPass { 12 | public struct Target: OptionSet { 13 | public let rawValue: UInt8 14 | public init(rawValue: UInt8) { self.rawValue = rawValue } 15 | 16 | public static let color = Self(rawValue: 1 << 0) 17 | public static let depth = Self(rawValue: 1 << 1) 18 | public static let normal = Self(rawValue: 1 << 2) 19 | public static let position = Self(rawValue: 1 << 3) 20 | // ... 21 | } 22 | 23 | var descriptor: MTLRenderPassDescriptor? = nil 24 | 25 | public var colorTexture: MTLTexture? = nil 26 | public var depthTexture: MTLTexture? = nil 27 | public var normalTexture: MTLTexture? = nil 28 | public var positionTexture: MTLTexture? = nil 29 | 30 | public private(set) var size: Int2 = [1, 1] 31 | 32 | public let name: String 33 | 34 | init(name: String, size: Int2, targets: Target = [.color, .depth]) { 35 | guard size.width > 0 && size.height > 0 else { 36 | fatalError("Do not initialize render pass with zero width or height!") 37 | } 38 | 39 | self.name = name 40 | self.size = size 41 | 42 | if targets.contains(.color) { 43 | colorTexture = buildColorTexture(size: size) 44 | } 45 | 46 | if targets.contains(.depth) { 47 | depthTexture = buildDepthTexture(size: size) 48 | } 49 | 50 | if targets.contains(.normal) { 51 | normalTexture = buildNormalTexture(size: size) 52 | } 53 | 54 | if targets.contains(.position) { 55 | positionTexture = buildPositionTexture(size: size) 56 | } 57 | 58 | descriptor = setupRenderPassDescriptor() 59 | } 60 | 61 | func resizeTextures(size: Int2, targets: Target = [.color, .depth, .normal, .position]) { 62 | guard size.width > 0 && size.height > 0 else { 63 | Log.warn("You are resizing textures in render pass with zero width or height. Skipping resize!") 64 | return 65 | } 66 | 67 | self.size = size 68 | 69 | if targets.contains(.color) && colorTexture != nil { 70 | colorTexture = buildColorTexture(size: size) 71 | } 72 | 73 | if targets.contains(.depth) && depthTexture != nil { 74 | depthTexture = buildDepthTexture(size: size) 75 | } 76 | 77 | if targets.contains(.normal) && normalTexture != nil { 78 | normalTexture = buildNormalTexture(size: size) 79 | } 80 | 81 | if targets.contains(.position) && positionTexture != nil { 82 | positionTexture = buildPositionTexture(size: size) 83 | } 84 | 85 | descriptor = setupRenderPassDescriptor() 86 | } 87 | 88 | private func setupRenderPassDescriptor() -> MTLRenderPassDescriptor { 89 | let descriptor = MTLRenderPassDescriptor() 90 | 91 | if let texture = colorTexture { 92 | descriptor.setUpColorAttachment(position: 0, texture: texture) 93 | } 94 | 95 | if let texture = depthTexture { 96 | descriptor.setUpDepthAttachment(texture: texture) 97 | } 98 | 99 | // TODO: set up normal, position textures 100 | 101 | return descriptor 102 | } 103 | 104 | private func buildColorTexture(size: Int2) -> MTLTexture { 105 | return TextureUtils.buildTexture(size: size, label: name + "_color", 106 | pixelFormat: RenderConfig.PixelFormat.color, 107 | usage: [.renderTarget, .shaderRead], 108 | storage: .private) 109 | } 110 | 111 | private func buildDepthTexture(size: Int2) -> MTLTexture { 112 | return TextureUtils.buildTexture(size: size, label: name + "_depth", 113 | pixelFormat: RenderConfig.PixelFormat.depth, 114 | usage: [.renderTarget, .shaderRead], 115 | storage: .private) 116 | } 117 | 118 | private func buildNormalTexture(size: Int2) -> MTLTexture { 119 | return TextureUtils.buildTexture(size: size, label: name + "_normal", 120 | pixelFormat: RenderConfig.PixelFormat.normal, 121 | usage: [.renderTarget, .shaderRead], 122 | storage: .private) 123 | } 124 | 125 | private func buildPositionTexture(size: Int2) -> MTLTexture { 126 | return TextureUtils.buildTexture(size: size, label: name + "_position", 127 | pixelFormat: RenderConfig.PixelFormat.position, 128 | usage: [.renderTarget, .shaderRead], 129 | storage: .private) 130 | } 131 | } 132 | 133 | private extension MTLRenderPassDescriptor { 134 | func setUpDepthAttachment(texture: MTLTexture) { 135 | depthAttachment.texture = texture 136 | depthAttachment.loadAction = .clear 137 | depthAttachment.storeAction = .store 138 | depthAttachment.clearDepth = 1 139 | } 140 | 141 | func setUpColorAttachment(position: Int, texture: MTLTexture) { 142 | let attachment: MTLRenderPassColorAttachmentDescriptor = colorAttachments[position] 143 | attachment.texture = texture 144 | attachment.loadAction = .clear 145 | attachment.storeAction = .store 146 | attachment.clearColor = MTLClearColorMake(0, 0, 0, 1) 147 | } 148 | } 149 | -------------------------------------------------------------------------------- /Sources/Palico/Scene/Primitive.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Primitive.swift 3 | // Palico 4 | // 5 | // Created by Junhao Wang on 12/26/21. 6 | // 7 | 8 | import MathLib 9 | 10 | public protocol Primitive: AnyObject { 11 | 12 | } 13 | 14 | public class Cube: GameObject, Primitive { 15 | public override init(_ scene: Scene, 16 | name: String = "Cube", 17 | position: Float3 = [0, 0, 0], 18 | rotation: Float3 = [0, 0, 0], 19 | scale: Float3 = [1, 1, 1]) { 20 | super.init(scene, name: name, position: position, rotation: rotation, scale: scale) 21 | 22 | let meshRenderer = MeshRendererComponent() 23 | meshRenderer.setMesh(.cube) 24 | addComponent(meshRenderer) 25 | } 26 | 27 | public override func onUpdateEditor(deltaTime ts: Timestep) { 28 | super.onUpdateEditor(deltaTime: ts) 29 | } 30 | 31 | public override func onUpdateRuntime(deltaTime ts: Timestep) { 32 | super.onUpdateRuntime(deltaTime: ts) 33 | } 34 | } 35 | 36 | public class Sphere: GameObject, Primitive { 37 | public override init(_ scene: Scene, 38 | name: String = "Sphere", 39 | position: Float3 = [0, 0, 0], 40 | rotation: Float3 = [0, 0, 0], 41 | scale: Float3 = [1, 1, 1]) { 42 | super.init(scene, name: name, position: position, rotation: rotation, scale: scale) 43 | 44 | let meshRenderer = MeshRendererComponent() 45 | meshRenderer.setMesh(.sphere) 46 | addComponent(meshRenderer) 47 | } 48 | 49 | public override func onUpdateEditor(deltaTime ts: Timestep) { 50 | super.onUpdateEditor(deltaTime: ts) 51 | } 52 | 53 | public override func onUpdateRuntime(deltaTime ts: Timestep) { 54 | super.onUpdateRuntime(deltaTime: ts) 55 | } 56 | } 57 | 58 | public class Hemisphere: GameObject, Primitive { 59 | public override init(_ scene: Scene, 60 | name: String = "HemiSphere", 61 | position: Float3 = [0, 0, 0], 62 | rotation: Float3 = [0, 0, 0], 63 | scale: Float3 = [1, 1, 1]) { 64 | super.init(scene, name: name, position: position, rotation: rotation, scale: scale) 65 | 66 | let meshRenderer = MeshRendererComponent() 67 | meshRenderer.setMesh(.hemisphere) 68 | addComponent(meshRenderer) 69 | } 70 | 71 | public override func onUpdateEditor(deltaTime ts: Timestep) { 72 | super.onUpdateEditor(deltaTime: ts) 73 | } 74 | 75 | public override func onUpdateRuntime(deltaTime ts: Timestep) { 76 | super.onUpdateRuntime(deltaTime: ts) 77 | } 78 | } 79 | 80 | public class Plane: GameObject, Primitive { 81 | public override init(_ scene: Scene, 82 | name: String = "Plane", 83 | position: Float3 = [0, 0, 0], 84 | rotation: Float3 = [0, Float(90.0).toRadians, 0], 85 | scale: Float3 = [1, 1, 1]) { 86 | super.init(scene, name: name, position: position, rotation: rotation, scale: scale) 87 | 88 | let meshRenderer = MeshRendererComponent() 89 | meshRenderer.setMesh(.plane) 90 | addComponent(meshRenderer) 91 | } 92 | 93 | public override func onUpdateEditor(deltaTime ts: Timestep) { 94 | super.onUpdateEditor(deltaTime: ts) 95 | } 96 | 97 | public override func onUpdateRuntime(deltaTime ts: Timestep) { 98 | super.onUpdateRuntime(deltaTime: ts) 99 | } 100 | } 101 | 102 | public class Capsule: GameObject, Primitive { 103 | public override init(_ scene: Scene, 104 | name: String = "Plane", 105 | position: Float3 = [0, 0, 0], 106 | rotation: Float3 = [0, 0, 0], 107 | scale: Float3 = [1, 1, 1]) { 108 | super.init(scene, name: name, position: position, rotation: rotation, scale: scale) 109 | 110 | let meshRenderer = MeshRendererComponent() 111 | meshRenderer.setMesh(.capsule) 112 | addComponent(meshRenderer) 113 | } 114 | 115 | public override func onUpdateEditor(deltaTime ts: Timestep) { 116 | super.onUpdateEditor(deltaTime: ts) 117 | } 118 | 119 | public override func onUpdateRuntime(deltaTime ts: Timestep) { 120 | super.onUpdateRuntime(deltaTime: ts) 121 | } 122 | } 123 | 124 | public class Cylinder: GameObject, Primitive { 125 | public override init(_ scene: Scene, 126 | name: String = "Cylinder", 127 | position: Float3 = [0, 0, 0], 128 | rotation: Float3 = [0, 0, 0], 129 | scale: Float3 = [1, 1, 1]) { 130 | super.init(scene, name: name, position: position, rotation: rotation, scale: scale) 131 | 132 | let meshRenderer = MeshRendererComponent() 133 | meshRenderer.setMesh(.cylinder) 134 | addComponent(meshRenderer) 135 | } 136 | 137 | public override func onUpdateEditor(deltaTime ts: Timestep) { 138 | super.onUpdateEditor(deltaTime: ts) 139 | } 140 | 141 | public override func onUpdateRuntime(deltaTime ts: Timestep) { 142 | super.onUpdateRuntime(deltaTime: ts) 143 | } 144 | } 145 | 146 | public class Cone: GameObject, Primitive { 147 | public override init(_ scene: Scene, 148 | name: String = "Plane", 149 | position: Float3 = [0, 0, 0], 150 | rotation: Float3 = [0, 0, 0], 151 | scale: Float3 = [1, 1, 1]) { 152 | super.init(scene, name: name, position: position, rotation: rotation, scale: scale) 153 | 154 | let meshRenderer = MeshRendererComponent() 155 | meshRenderer.setMesh(.cone) 156 | addComponent(meshRenderer) 157 | } 158 | 159 | public override func onUpdateEditor(deltaTime ts: Timestep) { 160 | super.onUpdateEditor(deltaTime: ts) 161 | } 162 | 163 | public override func onUpdateRuntime(deltaTime ts: Timestep) { 164 | super.onUpdateRuntime(deltaTime: ts) 165 | } 166 | } 167 | -------------------------------------------------------------------------------- /Sources/Palico/Renderer/Mesh.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Mesh.swift 3 | // Palico 4 | // 5 | // Created by Junhao Wang on 1/2/22. 6 | // 7 | 8 | import MetalKit 9 | 10 | public enum PrimitiveType: Int { 11 | case cube 12 | case sphere 13 | case hemisphere 14 | case plane 15 | case capsule 16 | case cylinder 17 | case cone 18 | 19 | public static let typeStrings: [String] = [ 20 | "Cube", "Sphere", "Hemisphere", "Plane", "Capsule", "Cylinder", "Cone" 21 | ] 22 | } 23 | 24 | class MeshFactory { 25 | static var meshCache: [PrimitiveType: Mesh] = [:] 26 | 27 | static func makePrimitiveMesh(type: PrimitiveType) -> Mesh { 28 | if let mesh = meshCache[type] { 29 | return mesh 30 | } 31 | 32 | let newMesh = Mesh(type: type) 33 | meshCache[type] = newMesh 34 | return newMesh 35 | } 36 | } 37 | 38 | public class Mesh { 39 | public let nativeMesh: MTKMesh 40 | let submeshes: [Submesh] 41 | 42 | init(type: PrimitiveType) { 43 | let allocator = MTKMeshBufferAllocator(device: MetalContext.device) 44 | 45 | var mdlMesh: MDLMesh 46 | 47 | switch type { 48 | case .cube: 49 | mdlMesh = Self.makeCubeMesh(allocator) 50 | case .sphere: 51 | mdlMesh = Self.makeSphereMesh(allocator) 52 | case .hemisphere: 53 | mdlMesh = Self.makeHemiSphereMesh(allocator) 54 | case .plane: 55 | mdlMesh = Self.makePlaneMesh(allocator) 56 | case .capsule: 57 | mdlMesh = Self.makeCapsule(allocator) 58 | case .cylinder: 59 | mdlMesh = Self.makeCylinderMesh(allocator) 60 | case .cone: 61 | mdlMesh = Self.makeConeMesh(allocator) 62 | } 63 | 64 | do { 65 | let mtkMesh = try MTKMesh(mesh: mdlMesh, device: MetalContext.device) 66 | nativeMesh = mtkMesh 67 | 68 | submeshes = mdlMesh.submeshes?.enumerated().compactMap { index, submesh in 69 | (submesh as? MDLSubmesh).map { 70 | Submesh(submesh: mtkMesh.submeshes[index], mdlSubmesh: $0) 71 | } 72 | } ?? [] 73 | } catch let error { 74 | fatalError(error.localizedDescription) 75 | } 76 | } 77 | } 78 | 79 | extension Mesh { 80 | private static func makeCubeMesh(_ allocator: MTKMeshBufferAllocator) -> MDLMesh { 81 | return MDLMesh(boxWithExtent: [1, 1, 1], 82 | segments: [1, 1, 1], 83 | inwardNormals: false, 84 | geometryType: .triangles, 85 | allocator: allocator) 86 | } 87 | 88 | private static func makeSphereMesh(_ allocator: MTKMeshBufferAllocator) -> MDLMesh { 89 | return MDLMesh(sphereWithExtent: [0.5, 0.5, 0.5], // radius 90 | segments: [50, 50], 91 | inwardNormals: false, 92 | geometryType: .triangles, 93 | allocator: allocator) 94 | } 95 | 96 | private static func makeHemiSphereMesh(_ allocator: MTKMeshBufferAllocator) -> MDLMesh { 97 | return MDLMesh(hemisphereWithExtent: [0.5, 0.5, 0.5], 98 | segments: [50, 50], 99 | inwardNormals: false, 100 | cap: true, 101 | geometryType: .triangles, 102 | allocator: allocator) 103 | } 104 | 105 | private static func makePlaneMesh(_ allocator: MTKMeshBufferAllocator) -> MDLMesh { 106 | return MDLMesh(planeWithExtent: [1, 1, 1], 107 | segments: [1, 1], 108 | geometryType: .triangles, 109 | allocator: allocator) 110 | } 111 | 112 | private static func makeCapsule(_ allocator: MTKMeshBufferAllocator) -> MDLMesh { 113 | return MDLMesh(capsuleWithExtent: [0.5, 2, 0.5], 114 | cylinderSegments: [50, 50], 115 | hemisphereSegments: 50, 116 | inwardNormals: false, 117 | geometryType: .triangles, 118 | allocator: allocator) 119 | } 120 | 121 | private static func makeCylinderMesh(_ allocator: MTKMeshBufferAllocator) -> MDLMesh { 122 | return MDLMesh(cylinderWithExtent: [0.5, 1, 0.5], 123 | segments: [50, 50], 124 | inwardNormals: false, 125 | topCap: true, 126 | bottomCap: true, 127 | geometryType: .triangles, 128 | allocator: allocator) 129 | } 130 | 131 | private static func makeConeMesh(_ allocator: MTKMeshBufferAllocator) -> MDLMesh { 132 | return MDLMesh(coneWithExtent: [1, 1, 1], 133 | segments: [50, 50], 134 | inwardNormals: false, 135 | cap: true, 136 | geometryType: .triangles, 137 | allocator: allocator) 138 | } 139 | } 140 | 141 | extension Mesh { 142 | static var defaultVertexDescriptor: MDLVertexDescriptor = { 143 | let vertexDescriptor = MDLVertexDescriptor() 144 | vertexDescriptor.attributes[0] = MDLVertexAttribute(name: MDLVertexAttributePosition, 145 | format: .float3, 146 | offset: 0, 147 | bufferIndex: 0) 148 | vertexDescriptor.attributes[1] = MDLVertexAttribute(name: MDLVertexAttributeNormal, 149 | format: .float3, 150 | offset: 12, 151 | bufferIndex: 0) 152 | vertexDescriptor.attributes[2] = MDLVertexAttribute(name: MDLVertexAttributeTextureCoordinate, 153 | format: .float2, 154 | offset: 24, 155 | bufferIndex: 0) 156 | vertexDescriptor.layouts[0] = MDLVertexBufferLayout(stride: 32) 157 | return vertexDescriptor 158 | }() 159 | } 160 | 161 | class Submesh { 162 | let nativeSubmesh: MTKSubmesh 163 | 164 | init(submesh: MTKSubmesh, mdlSubmesh: MDLSubmesh) { 165 | self.nativeSubmesh = submesh 166 | } 167 | } 168 | -------------------------------------------------------------------------------- /Sources/Palico/Renderer/TextureUtils.swift: -------------------------------------------------------------------------------- 1 | // 2 | /** 3 | * Copyright (c) 2019 Razeware LLC 4 | * 5 | * Permission is hereby granted, free of charge, to any person obtaining a copy 6 | * of this software and associated documentation files (the "Software"), to deal 7 | * in the Software without restriction, including without limitation the rights 8 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | * copies of the Software, and to permit persons to whom the Software is 10 | * furnished to do so, subject to the following conditions: 11 | * 12 | * The above copyright notice and this permission notice shall be included in 13 | * all copies or substantial portions of the Software. 14 | * 15 | * Notwithstanding the foregoing, you may not use, copy, modify, merge, publish, 16 | * distribute, sublicense, create a derivative work, and/or sell copies of the 17 | * Software in any work that is designed, intended, or marketed for pedagogical or 18 | * instructional purposes related to programming, coding, application development, 19 | * or information technology. Permission for such use, copying, modification, 20 | * merger, publication, distribution, sublicensing, creation of derivative works, 21 | * or sale is expressly withheld. 22 | * 23 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 24 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 25 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 26 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 27 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 28 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 29 | * THE SOFTWARE. 30 | */ 31 | 32 | import MetalKit 33 | import MathLib 34 | 35 | public enum TextureUtils { 36 | public static func loadTexture(url: URL) throws -> MTLTexture? { 37 | let textureLoader = MTKTextureLoader(device: MetalContext.device) 38 | 39 | let textureLoaderOptions: [MTKTextureLoader.Option: Any] = [.origin: MTKTextureLoader.Origin.bottomLeft, 40 | .SRGB: false, 41 | .generateMipmaps: NSNumber(booleanLiteral: true)] 42 | let texture = try textureLoader.newTexture(URL: url, 43 | options: textureLoaderOptions) 44 | print("loaded texture: \(url.lastPathComponent)") 45 | 46 | return texture 47 | } 48 | 49 | public static func loadTexture(texture: MDLTexture) throws -> MTLTexture? { 50 | let textureLoader = MTKTextureLoader(device: MetalContext.device) 51 | let textureLoaderOptions: [MTKTextureLoader.Option: Any] = 52 | [.origin: MTKTextureLoader.Origin.bottomLeft, 53 | .SRGB: false, 54 | .generateMipmaps: NSNumber(booleanLiteral: true)] 55 | 56 | let texture = try? textureLoader.newTexture(texture: texture, 57 | options: textureLoaderOptions) 58 | return texture 59 | } 60 | 61 | public static func loadCubeTexture(imageName: String) throws -> MTLTexture { 62 | let textureLoader = MTKTextureLoader(device: MetalContext.device) 63 | if let texture = MDLTexture(cubeWithImagesNamed: [imageName]) { 64 | let options: [MTKTextureLoader.Option: Any] = 65 | [.origin: MTKTextureLoader.Origin.topLeft, 66 | .SRGB: false, 67 | .generateMipmaps: NSNumber(booleanLiteral: false)] 68 | return try textureLoader.newTexture(texture: texture, options: options) 69 | } 70 | let texture = try textureLoader.newTexture(name: imageName, scaleFactor: 1.0, 71 | bundle: .main) 72 | return texture 73 | } 74 | 75 | public static func loadTextureArray(urls: [URL]) -> MTLTexture? { 76 | var textures: [MTLTexture] = [] 77 | for url in urls { 78 | do { 79 | if let texture = try self.loadTexture(url: url) { 80 | textures.append(texture) 81 | } 82 | } 83 | catch { 84 | fatalError(error.localizedDescription) 85 | } 86 | } 87 | guard textures.count > 0 else { return nil } 88 | let descriptor = MTLTextureDescriptor() 89 | descriptor.textureType = .type2DArray 90 | descriptor.pixelFormat = textures[0].pixelFormat 91 | descriptor.width = textures[0].width 92 | descriptor.height = textures[0].height 93 | descriptor.arrayLength = textures.count 94 | let arrayTexture = MetalContext.device.makeTexture(descriptor: descriptor)! 95 | let commandBuffer = MetalContext.commandQueue.makeCommandBuffer()! 96 | let blitEncoder = commandBuffer.makeBlitCommandEncoder()! 97 | let origin = MTLOrigin(x: 0, y: 0, z: 0) 98 | let size = MTLSize(width: arrayTexture.width, 99 | height: arrayTexture.height, depth: 1) 100 | for (index, texture) in textures.enumerated() { 101 | blitEncoder.copy(from: texture, sourceSlice: 0, sourceLevel: 0, 102 | sourceOrigin: origin, sourceSize: size, 103 | to: arrayTexture, destinationSlice: index, 104 | destinationLevel: 0, destinationOrigin: origin) 105 | } 106 | blitEncoder.endEncoding() 107 | commandBuffer.commit() 108 | return arrayTexture 109 | } 110 | 111 | public static func buildTexture(size: Int2, 112 | label: String, 113 | pixelFormat: MTLPixelFormat = .rgba8Unorm, 114 | usage: MTLTextureUsage = [.shaderRead], 115 | storage: MTLStorageMode = .managed) -> MTLTexture { 116 | let descriptor = MTLTextureDescriptor.texture2DDescriptor(pixelFormat: pixelFormat, 117 | width: size.width, 118 | height: size.height, 119 | mipmapped: false) 120 | descriptor.sampleCount = 1 121 | descriptor.storageMode = storage 122 | descriptor.textureType = .type2D 123 | descriptor.usage = usage 124 | guard let texture = MetalContext.device.makeTexture(descriptor: descriptor) else { 125 | fatalError("Texture not created") 126 | } 127 | texture.label = label 128 | return texture 129 | } 130 | } 131 | -------------------------------------------------------------------------------- /Sources/Palico/ImGui/ImGuiLayer.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ImGuiLayer.swift 3 | // Palico 4 | // 5 | // Created by Junhao Wang on 12/22/21. 6 | // 7 | 8 | import ImGui 9 | import ImGuizmo 10 | 11 | class ImGuiLayer: Layer { 12 | // Determine if the events are dispatched to subsequent layers. 13 | var tryToBlockEvents: Bool = true 14 | 15 | let theme = ImGuiTheme() 16 | 17 | override init() { 18 | super.init(name: "ImGui Layer") 19 | } 20 | 21 | override func onAttach() { 22 | ImGuiBackend.initialize() 23 | 24 | Console.debug("ImGui Version: \(ImGuiGetVersion() ?? "unknown")") 25 | 26 | // ImGui Context 27 | IMGUI_CHECKVERSION() 28 | _ = ImGuiCreateContext(nil) 29 | 30 | let io = ImGuiGetIO()! 31 | /* 32 | io.pointee.ConfigFlags |= Im(ImGuiConfigFlags_NavEnableKeyboard) 33 | io.pointee.ConfigFlags |= Im(ImGuiConfigFlags_NavEnableGamepad) 34 | */ 35 | io.pointee.ConfigFlags |= Im(ImGuiConfigFlags_DockingEnable) 36 | io.pointee.ConfigFlags |= Im(ImGuiConfigFlags_ViewportsEnable) 37 | 38 | // ImGui Font 39 | setFonts() 40 | 41 | // ImGui Style 42 | setStyles() 43 | setThemeColors() 44 | 45 | // Setup Platform/Renderer Bindings 46 | ImGuiBackend.implPlatformInit() 47 | ImGuiBackend.implGraphicsInit() 48 | } 49 | 50 | override func onDetach() { 51 | ImGuiBackend.implGraphicsShutdown() 52 | ImGuiBackend.implPlatformShutdown() 53 | ImGuiDestroyContext(ImGuiGetCurrentContext()) 54 | } 55 | 56 | override func onEvent(event: Event) { 57 | if tryToBlockEvents { 58 | let io = ImGuiGetIO()! 59 | 60 | let mouse = EventUtils.isInCategory(event: event, category: [.mouse]) && io.pointee.WantCaptureMouse 61 | event.handled = event.handled || mouse 62 | 63 | let keyboard = EventUtils.isInCategory(event: event, category: [.keyboard]) && io.pointee.WantCaptureKeyboard 64 | event.handled = event.handled || keyboard 65 | } 66 | // Otherwise: evnets are not handled by ImGui 67 | } 68 | 69 | func begin() { 70 | ImGuiBackend.implGraphicsNewFrame() 71 | ImGuiBackend.implPlatformNewFrame() 72 | ImGuiNewFrame() 73 | 74 | // ImGuizmo 75 | ImGuizmoBeginFrame() 76 | } 77 | 78 | func end() { 79 | ImGuiRender() 80 | let drawData = ImGuiGetDrawData()! 81 | ImGuiBackend.implGraphicsRenderDrawData(drawData.pointee) 82 | 83 | if (ImGuiGetIO()!.pointee.ConfigFlags & Im(ImGuiConfigFlags_ViewportsEnable)) != 0 { 84 | ImGuiUpdatePlatformWindows() 85 | ImGuiRenderPlatformWindowsDefault(nil, nil) 86 | } 87 | } 88 | 89 | private static var iconRanges: [ImWchar] = [0xe00f, 0xf8ff, 0] 90 | private static var config: ImFontConfig = ImFontConfig_ImFontConfig().pointee // don't use ImFontConfig() 91 | 92 | private func setFonts() { 93 | let io = ImGuiGetIO()! 94 | 95 | let dpi: Float = MetalContext.dpi 96 | let fontSize = Float(17.0) 97 | let scaledFontSize = Float(dpi * fontSize) 98 | let iconFontSize = Float(12.0) 99 | let iconScaledFontSize = Float(dpi * iconFontSize) 100 | io.pointee.FontGlobalScale = 1 / dpi 101 | 102 | guard let fontBold = FileUtils.getURL(path: "Assets/Fonts/Ruda/Ruda-Bold.ttf")?.path, 103 | // let fontRegular = FileUtils.getURL(path: "Assets/Fonts/Ruda/Ruda-SemiBold.ttf")?.path, 104 | let fontSolidIcon = FileUtils.getURL(path: "Assets/Fonts/FontAwesome5/fa-solid-900.ttf")?.path, 105 | let fontRegularIcon = FileUtils.getURL(path: "Assets/Fonts/FontAwesome5/fa-regular-400.ttf")?.path, 106 | let fontBrandsIcon = FileUtils.getURL(path: "Assets/Fonts/FontAwesome5/fa-brands-400.ttf")?.path 107 | else { 108 | Log.warn("Not able to load custom fonts.") 109 | return 110 | } 111 | 112 | io.pointee.FontDefault = ImFontAtlas_AddFontFromFileTTF(io.pointee.Fonts, fontBold, scaledFontSize, nil, nil) 113 | ImGuiFontLibrary.defaultFont = io.pointee.FontDefault 114 | 115 | // FontAwesome5 116 | Self.config.MergeMode = true 117 | Self.config.GlyphMinAdvanceX = scaledFontSize // Use if you want to make the icon monospaced 118 | ImGuiFontLibrary.regularIcon = ImFontAtlas_AddFontFromFileTTF(io.pointee.Fonts, fontSolidIcon, iconScaledFontSize, &Self.config, &Self.iconRanges) 119 | ImFontAtlas_AddFontFromFileTTF(io.pointee.Fonts, fontRegularIcon, iconScaledFontSize, &Self.config, &Self.iconRanges) 120 | ImFontAtlas_AddFontFromFileTTF(io.pointee.Fonts, fontBrandsIcon, iconScaledFontSize, &Self.config, &Self.iconRanges) 121 | 122 | // Large Icons 123 | let largeScale: Float = 1.8 124 | Self.config.MergeMode = false 125 | ImGuiFontLibrary.largeIcon = ImFontAtlas_AddFontFromFileTTF(io.pointee.Fonts, fontSolidIcon, iconScaledFontSize * largeScale, &Self.config, &Self.iconRanges) 126 | Self.config.MergeMode = true 127 | ImFontAtlas_AddFontFromFileTTF(io.pointee.Fonts, fontRegularIcon, iconScaledFontSize * largeScale, &Self.config, &Self.iconRanges) 128 | ImFontAtlas_AddFontFromFileTTF(io.pointee.Fonts, fontBrandsIcon, iconScaledFontSize * largeScale, &Self.config, &Self.iconRanges) 129 | 130 | // ImFontAtlas_AddFontFromFileTTF(io.pointee.Fonts, fontRegular, scaledFontSize, nil, nil) 131 | } 132 | 133 | private func setStyles() { 134 | let style = ImGuiGetStyle()! 135 | 136 | ImGuiStyleColorsDark(nil) 137 | 138 | // Rounding 139 | style.pointee.WindowRounding = 4.0 140 | style.pointee.ChildRounding = 4.0 141 | style.pointee.FrameRounding = 4.0 142 | style.pointee.TabRounding = 4.0 143 | style.pointee.PopupRounding = 4.0 144 | style.pointee.GrabRounding = 3.0 145 | 146 | // Padding 147 | style.pointee.FramePadding = ImVec2(6, 3) 148 | 149 | // Size 150 | style.pointee.GrabMinSize = 11.0 151 | 152 | // Show/Hide 153 | style.pointee.WindowMenuButtonPosition = Im(ImGuiDir_Right) 154 | 155 | /* 156 | let style = ImGuiGetStyle()! 157 | if (io.pointee.ConfigFlags & Im(ImGuiConfigFlags_ViewportsEnable)) != 0 { 158 | style.pointee.WindowRounding = 0.0 159 | CArray.write(&style.pointee.Colors) { colors in 160 | colors[Int(Im(ImGuiCol_WindowBg))].w = 1.0 161 | } 162 | } 163 | */ 164 | } 165 | 166 | private func setThemeColors() { 167 | theme.loadTheme() 168 | } 169 | } 170 | -------------------------------------------------------------------------------- /Sources/Palico/Renderer/EditorCamera.swift: -------------------------------------------------------------------------------- 1 | // 2 | // EditorCamera.swift 3 | // Palico 4 | // 5 | // Created by Junhao Wang on 1/6/22. 6 | // 7 | 8 | import MathLib 9 | 10 | public class EditorCamera: Camera { 11 | // Parameters 12 | public private(set) var fov: Float = 45.0 13 | public private(set) var aspectRatio: Float = 1.778 14 | public private(set) var nearClip: Float = 0.1 15 | public private(set) var farClip: Float = 1000.0 16 | public var position: Float3 { get { 17 | return focusPoint - forwardDirection * distance 18 | }} 19 | 20 | // Orientation 21 | public var orientation: Quaternion { get { 22 | // Order matters! We want: 23 | // 1. Rotation around X-axis behaves like as in local space 24 | // 2. Rotation around Y-axis behaves like as in world space 25 | // Thus, rotation order: X -> Y (in quaternion it is inverse) 26 | return Quaternion(rotaionXYZ: [-pitchAtFocus, yawAtFocus, 0]) 27 | 28 | // Equivalent: 29 | // let qx = Quaternion(angle: -pitchAtFocus, axis: Float3.right) 30 | // let qy = Quaternion(angle: yawAtFocus, axis: Float3.up) 31 | // return mul(qy, qx) 32 | }} 33 | public var rightDirection: Float3 { get { // X 34 | return orientation.act(Float3.right) 35 | 36 | }} 37 | public var upDirection: Float3 { get { // Y 38 | return orientation.act(Float3.up) 39 | }} 40 | public var forwardDirection: Float3 { get { // Z 41 | return orientation.act(Float3.forward) 42 | }} 43 | 44 | // Config 45 | private var rotationSpeed: Float { get { 46 | 0.2 47 | }} 48 | private var lookAroundSpeed: Float { get { 49 | 0.1 50 | }} 51 | private var zoomSpeed: Float { get { 52 | let dist = max(distance * 0.15, 0) 53 | let speed = min(dist * dist, 15) // max speed is 15 54 | return speed 55 | }} 56 | private var panSpeed: Float2 { get { 57 | // Source: EditorCamera.cpp in Hazel Engine (by The Cherno) 58 | // Calculate based on the viewport size 59 | let x: Float = min(Float(viewportSize.width) / 1000.0, 2.4) // min = 2.4 60 | let xFactor: Float = 0.0366 * x * x - 0.1778 * x + 0.3021 61 | 62 | let y: Float = min(Float(viewportSize.height) / 1000.0, 2.4) // min = 2.4 63 | let yFactor = 0.0366 * y * y - 0.1778 + 0.3021 64 | 65 | let scale: Float = 0.4 // change speed 66 | return Float2(xFactor, yFactor) * scale 67 | }} 68 | 69 | // Private 70 | private var focusPoint: Float3 = [0, 1.0, 0] 71 | private var distance: Float = 10.0 72 | private var pitchAtFocus: Float = Float(-20).toRadians 73 | private var yawAtFocus: Float = Float(-135).toRadians // within [+X, +Y, +Z] 10 away from origin 74 | private var viewportSize: Int2 = [1280, 720] 75 | 76 | // Output 77 | public private(set) var viewMatrix: Float4x4 = .identity 78 | public private(set) var projectionMatrix: Float4x4 = .identity 79 | 80 | private var initialMousePosition: Float2 = [-1, -1] 81 | 82 | public init() { 83 | updateProjection() 84 | updateView() 85 | } 86 | 87 | public init(fov: Float, aspect: Float, nearClip: Float = 0.1, farClip: Float = 1000.0) { 88 | self.fov = fov 89 | self.aspectRatio = aspect 90 | self.nearClip = nearClip 91 | self.farClip = farClip 92 | 93 | updateProjection() 94 | updateView() 95 | } 96 | } 97 | 98 | // View and Projection 99 | extension EditorCamera { 100 | private func updateView() { 101 | /* Lock camera's orientation 102 | yaw = 0, pitch = 0 103 | */ 104 | let R = Float4x4(orientation) 105 | let T = Float4x4(translation: position) 106 | viewMatrix = mul(T, R).inverse 107 | } 108 | 109 | private func updateProjection() { 110 | aspectRatio = Float(viewportSize.width) / Float(viewportSize.height) 111 | projectionMatrix = Float4x4(projectionFov: fov.toRadians, 112 | aspectRatio: aspectRatio, 113 | near: nearClip, far: farClip) 114 | } 115 | } 116 | 117 | // Setter 118 | extension EditorCamera { 119 | public func setDistance(_ distance: Float) { 120 | self.distance = distance 121 | updateView() 122 | } 123 | 124 | public func setFocusPoint(_ focusPoint: Float3, _ distance: Float = 10) { 125 | self.distance = distance 126 | self.focusPoint = focusPoint 127 | updateView() 128 | } 129 | 130 | public func setFov(_ fov: Float) { 131 | self.fov = fov 132 | updateProjection() 133 | } 134 | 135 | public func setViewportSize(_ size: Int2) { 136 | viewportSize = size 137 | updateProjection() 138 | } 139 | } 140 | 141 | // Update 142 | extension EditorCamera { 143 | public func onUpdate(deltaTime ts: Timestep) { 144 | let mouse: Float2 = Input.mousePos 145 | 146 | guard initialMousePosition.x != -1 else { 147 | initialMousePosition = mouse 148 | return 149 | } 150 | 151 | let delta: Float2 = (mouse - initialMousePosition) * ts 152 | initialMousePosition = mouse 153 | 154 | if Input.isPressed(key: .option) || Input.isPressed(key: .command) { 155 | if Input.isPressed(mouse: .left) { 156 | rotateAroundFocusPoint(delta: delta) 157 | } 158 | } 159 | 160 | if Input.isPressed(mouse: .middle) { 161 | pan(delta: delta) 162 | } 163 | 164 | if Input.isPressed(mouse: .right) { 165 | lookAround(delta: delta) 166 | } 167 | 168 | updateView() 169 | } 170 | 171 | public func onEvent(event: Event) { 172 | let dispatcher = EventDispatcher(event: event) 173 | dispatcher.dispatch(callback: onMouseScroll) 174 | } 175 | 176 | private func onMouseScroll(event: MouseScrolledEvent) -> Bool { 177 | let delta: Float = event.yoffset * 0.1 178 | zoom(delta: delta) 179 | updateView() 180 | return false 181 | } 182 | 183 | private func pan(delta: Float2) { 184 | let speed = panSpeed 185 | // Should be opposite as you pan to other direction 186 | focusPoint -= rightDirection * delta.x * speed.x * distance 187 | focusPoint -= upDirection * delta.y * speed.y * distance 188 | } 189 | 190 | private func rotateAroundFocusPoint(delta: Float2) { 191 | let yawSign: Float = upDirection.y < 0 ? -1.0 : 1.0 192 | yawAtFocus += yawSign * delta.x * rotationSpeed 193 | pitchAtFocus += delta.y * rotationSpeed 194 | } 195 | 196 | private func zoom(delta: Float) { 197 | distance -= delta * zoomSpeed 198 | if distance < 1.0 { 199 | focusPoint += forwardDirection 200 | distance = 1.0 201 | } 202 | } 203 | 204 | private func lookAround(delta: Float2) { 205 | let oldPosition = position 206 | pitchAtFocus += delta.y * lookAroundSpeed 207 | yawAtFocus += delta.x * lookAroundSpeed 208 | focusPoint = oldPosition + forwardDirection * distance 209 | } 210 | } 211 | -------------------------------------------------------------------------------- /Sources/Editor/Panels/DrawControls.swift: -------------------------------------------------------------------------------- 1 | // 2 | // DrawControls.swift 3 | // Editor 4 | // 5 | // Created by Junhao Wang on 1/15/22. 6 | // 7 | 8 | import Palico 9 | import ImGui 10 | import MathLib 11 | 12 | func drawControlFloat3(_ label: String, _ values: inout Float3, _ format: String = "%.1f", _ resetValue: Float = 0.0, _ columnWidth: Float = 100.0) { 13 | ImGuiPushID(label) 14 | ImGuiColumns(2, nil, false) 15 | 16 | ImGuiSetColumnWidth(0, columnWidth) 17 | ImGuiTextV(label) 18 | ImGuiNextColumn() 19 | 20 | ImGuiPushMultiItemsWidths(3, ImGuiCalcItemWidth()) 21 | 22 | let itemInnerSpacing: Float = 4 23 | let itemOutterSpacing: Float = 4 24 | 25 | // X 26 | ImGuiPushStyleColor(Im(ImGuiCol_Button), ImGuiTheme.componentX) 27 | ImGuiPushStyleColor(Im(ImGuiCol_ButtonHovered), ImGuiTheme.componentHoveredX) 28 | ImGuiPushStyleColor(Im(ImGuiCol_ButtonActive), ImGuiTheme.componentX) 29 | if ImGuiButton("X", ImVec2(0, 0)) { 30 | values.x = resetValue 31 | } 32 | ImGuiPopStyleColor(3) 33 | ImGuiSameLine(0, itemInnerSpacing) 34 | ImGuiDragFloat("##X", &values.x, 0.1, 0.0, 0.0, format, 0) 35 | ImGuiPopItemWidth() 36 | ImGuiSameLine(0, itemOutterSpacing) 37 | 38 | // Y 39 | ImGuiPushStyleColor(Im(ImGuiCol_Button), ImGuiTheme.componentY) 40 | ImGuiPushStyleColor(Im(ImGuiCol_ButtonHovered), ImGuiTheme.componentHoveredY) 41 | ImGuiPushStyleColor(Im(ImGuiCol_ButtonActive), ImGuiTheme.componentY) 42 | if ImGuiButton("Y", ImVec2(0, 0)) { 43 | values.y = resetValue 44 | } 45 | ImGuiPopStyleColor(3) 46 | ImGuiSameLine(0, itemInnerSpacing) 47 | ImGuiDragFloat("##Y", &values.y, 0.1, 0.0, 0.0, format, 0) 48 | ImGuiPopItemWidth() 49 | ImGuiSameLine(0, itemOutterSpacing) 50 | 51 | // Z 52 | ImGuiPushStyleColor(Im(ImGuiCol_Button), ImGuiTheme.componentZ) 53 | ImGuiPushStyleColor(Im(ImGuiCol_ButtonHovered), ImGuiTheme.componentHoveredZ) 54 | ImGuiPushStyleColor(Im(ImGuiCol_ButtonActive), ImGuiTheme.componentZ) 55 | if ImGuiButton("Z", ImVec2(0, 0)) { 56 | values.z = resetValue 57 | } 58 | ImGuiPopStyleColor(3) 59 | ImGuiSameLine(0, itemInnerSpacing) 60 | ImGuiDragFloat("##Z", &values.z, 0.1, 0.0, 0.0, format, 0) 61 | ImGuiPopItemWidth() 62 | 63 | ImGuiColumns(1, nil, false) 64 | ImGuiPopID() // pop ID label 65 | } 66 | 67 | func drawControlFloat2(_ label: String, _ values: inout Float2, _ format: String = "%.1f", _ resetValue: Float = 0.0, _ columnWidth: Float = 100.0) { 68 | ImGuiPushID(label) 69 | ImGuiColumns(2, nil, false) 70 | 71 | ImGuiSetColumnWidth(0, columnWidth) 72 | ImGuiTextV(label) 73 | ImGuiNextColumn() 74 | 75 | ImGuiPushMultiItemsWidths(2, ImGuiCalcItemWidth()) 76 | 77 | let itemInnerSpacing: Float = 4 78 | let itemOutterSpacing: Float = 4 79 | 80 | // X 81 | ImGuiPushStyleColor(Im(ImGuiCol_Button), ImGuiTheme.componentX) 82 | ImGuiPushStyleColor(Im(ImGuiCol_ButtonHovered), ImGuiTheme.componentHoveredX) 83 | ImGuiPushStyleColor(Im(ImGuiCol_ButtonActive), ImGuiTheme.componentX) 84 | if ImGuiButton("X", ImVec2(0, 0)) { 85 | values.x = resetValue 86 | } 87 | ImGuiPopStyleColor(3) 88 | ImGuiSameLine(0, itemInnerSpacing) 89 | ImGuiDragFloat("##X", &values.x, 0.1, 0.0, 0.0, format, 0) 90 | ImGuiPopItemWidth() 91 | ImGuiSameLine(0, itemOutterSpacing) 92 | 93 | // Y 94 | ImGuiPushStyleColor(Im(ImGuiCol_Button), ImGuiTheme.componentY) 95 | ImGuiPushStyleColor(Im(ImGuiCol_ButtonHovered), ImGuiTheme.componentHoveredY) 96 | ImGuiPushStyleColor(Im(ImGuiCol_ButtonActive), ImGuiTheme.componentY) 97 | if ImGuiButton("Y", ImVec2(0, 0)) { 98 | values.y = resetValue 99 | } 100 | ImGuiPopStyleColor(3) 101 | ImGuiSameLine(0, itemInnerSpacing) 102 | ImGuiDragFloat("##Y", &values.y, 0.1, 0.0, 0.0, format, 0) 103 | ImGuiPopItemWidth() 104 | 105 | ImGuiColumns(1, nil, false) 106 | ImGuiPopID() // pop ID label 107 | } 108 | 109 | func drawControlFloat(_ label: String, _ value: inout Float, _ format: String = "%.1f", _ resetValue: Float = 0.0, _ columnWidth: Float = 100.0) { 110 | ImGuiPushID(label) 111 | ImGuiColumns(2, nil, false) 112 | 113 | ImGuiSetColumnWidth(0, columnWidth) 114 | ImGuiTextV(label) 115 | ImGuiNextColumn() 116 | 117 | ImGuiPushMultiItemsWidths(1, ImGuiCalcItemWidth()) 118 | 119 | ImGuiDragFloat("##X", &value, 0.1, 0.0, 1.0, format, 0) 120 | ImGuiPopItemWidth() 121 | 122 | ImGuiColumns(1, nil, false) 123 | ImGuiPopID() // pop ID label 124 | } 125 | 126 | func drawControlFloatSlider(_ label: String, _ value: inout Float, _ format: String = "%.1f", _ min: Float, _ max: Float, _ columnWidth: Float = 100.0) { 127 | ImGuiPushID(label) 128 | ImGuiColumns(2, nil, false) 129 | 130 | ImGuiSetColumnWidth(0, columnWidth) 131 | ImGuiTextV(label) 132 | ImGuiNextColumn() 133 | 134 | ImGuiSetNextItemWidth(-Float.leastNormalMagnitude) 135 | ImGuiSliderFloat("##X", &value, min, max, format, 0) 136 | 137 | ImGuiColumns(1, nil, false) 138 | ImGuiPopID() // pop ID label 139 | } 140 | 141 | func drawControlColorEdit3(_ label: String, _ color: inout Color3, _ columnWidth: Float = 100.0) { 142 | ImGuiPushID(label) 143 | ImGuiColumns(2, nil, false) 144 | 145 | ImGuiSetColumnWidth(0, columnWidth) 146 | ImGuiTextV(label) 147 | ImGuiNextColumn() 148 | 149 | ImGuiSetNextItemWidth(-Float.leastNormalMagnitude) 150 | ImGuiColorEdit3("##Color3", &color, Im(ImGuiColorEditFlags_None)) 151 | 152 | ImGuiColumns(1, nil, false) 153 | ImGuiPopID() // pop ID label 154 | } 155 | 156 | func drawControlColorEdit4(_ label: String, _ color: inout Color4, _ columnWidth: Float = 100.0) { 157 | ImGuiPushID(label) 158 | ImGuiColumns(2, nil, false) 159 | 160 | ImGuiSetColumnWidth(0, columnWidth) 161 | ImGuiTextV(label) 162 | ImGuiNextColumn() 163 | 164 | ImGuiSetNextItemWidth(-Float.leastNormalMagnitude) 165 | ImGuiColorEdit4("##Color4", &color, Im(ImGuiColorEditFlags_None)) 166 | 167 | ImGuiColumns(1, nil, false) 168 | ImGuiPopID() // pop ID label 169 | } 170 | 171 | func drawControlCombo(_ label: String, _ selection: inout Int32, _ comboList: [String] , _ columnWidth: Float = 100.0) -> Bool { 172 | ImGuiPushID(label) 173 | ImGuiColumns(2, nil, false) 174 | 175 | ImGuiSetColumnWidth(0, columnWidth) 176 | ImGuiTextV(label) 177 | ImGuiNextColumn() 178 | 179 | ImGuiSetNextItemWidth(-Float.leastNormalMagnitude) 180 | let changed: Bool = ImGuiCombo("##Combo", &selection, comboList, Int32(comboList.count), -1) 181 | 182 | ImGuiColumns(1, nil, false) 183 | ImGuiPopID() // pop ID label 184 | 185 | return changed 186 | } 187 | 188 | func drawControlInputReadOnly(_ label: String, _ input: inout String?, _ columnWidth: Float = 100.0) { 189 | ImGuiPushID(label) 190 | ImGuiColumns(2, nil, false) 191 | 192 | ImGuiSetColumnWidth(0, columnWidth) 193 | ImGuiTextV(label) 194 | ImGuiNextColumn() 195 | 196 | ImGuiSetNextItemWidth(-Float.leastNormalMagnitude) 197 | if ImGuiInputText("##Input", &input, 50, Im(ImGuiInputTextFlags_EnterReturnsTrue) | Im(ImGuiInputTextFlags_ReadOnly), { a -> Int32 in 198 | return 1 199 | }, nil) { print(input ?? "") } 200 | 201 | ImGuiColumns(1, nil, false) 202 | ImGuiPopID() // pop ID label 203 | } 204 | -------------------------------------------------------------------------------- /Sources/Palico/Platform/GLFW/GlfwWindow.swift: -------------------------------------------------------------------------------- 1 | // 2 | // GlfwWindow.swift 3 | // Palico 4 | // 5 | // Created by Junhao Wang on 12/26/21. 6 | // 7 | 8 | /* 9 | import CGLFW3 10 | import Cocoa // Is it possible not to use Cocoa? 11 | import MathLib 12 | 13 | fileprivate struct WindowData { 14 | var windowDelegate: WindowDelegate? = nil 15 | var title: String = "" 16 | var width: UInt32 = 0 17 | var height: UInt32 = 0 18 | } 19 | 20 | fileprivate enum WindowDataOffsets { 21 | static let windowDelegateOffset = MemoryLayout.offset(of: \.windowDelegate)! 22 | static let titleOffset = MemoryLayout.offset(of: \.title)! 23 | static let widthOffset = MemoryLayout.offset(of: \.width)! 24 | static let heightOffset = MemoryLayout.offset(of: \.height)! 25 | } 26 | 27 | class GlfwWindow: Window { 28 | var title: String { data.title } 29 | var width: Int { Int(data.width) } 30 | var height: Int { Int(data.height) } 31 | var isMinimized: Bool { nsWindow.isMiniaturized } 32 | 33 | private var data: WindowData = WindowData() 34 | 35 | weak var windowDelegate: WindowDelegate? { 36 | get { data.windowDelegate } 37 | set { data.windowDelegate = newValue } 38 | } 39 | 40 | private var lastFrameTime: Double = Time.currentTime 41 | 42 | private let nativeWindow: OpaquePointer! 43 | private let nsWindow: NSWindow 44 | private let swapchain: CAMetalLayer! 45 | 46 | required init(descriptor: WindowDescriptor) { 47 | Log.info("Initializing GLFW window: \(descriptor.title) \(descriptor.width) x \(descriptor.height)") 48 | 49 | // Create Window 50 | guard let window = glfwCreateWindow(Int32(data.width), Int32(data.height), data.title, nil, nil) else { 51 | fatalError("Failed to create GLFW window!") 52 | } 53 | self.nativeWindow = window 54 | 55 | guard let nsWindow = glfwGetCocoaWindow(window) as? NSWindow else { 56 | fatalError("Failed to get Cocoa window!") 57 | } 58 | nsWindow.center() 59 | nsWindow.makeMain() 60 | self.nsWindow = nsWindow 61 | 62 | // Swapchain 63 | swapchain = CAMetalLayer() 64 | swapchain.device = MTLCreateSystemDefaultDevice() 65 | swapchain.pixelFormat = .bgra8Unorm 66 | 67 | nsWindow.contentView?.layer = swapchain 68 | nsWindow.contentView?.wantsLayer = true 69 | 70 | // Set Window Data 71 | glfwSetWindowUserPointer(nativeWindow, &data) 72 | 73 | // Set GLFW Callbacks 74 | glfwSetWindowSizeCallback(nativeWindow, glfwWindowResizeCallback) 75 | glfwSetWindowCloseCallback(nativeWindow, glfwWindowCloseCallback) 76 | glfwSetKeyCallback(nativeWindow, glfwKeyCallback) 77 | glfwSetCharCallback(nativeWindow, glfwCharCallback) 78 | glfwSetMouseButtonCallback(nativeWindow, glfwMouseButtonCallback) 79 | glfwSetScrollCallback(nativeWindow, glfwScrollCallback) 80 | glfwSetCursorPosCallback(nativeWindow, glfwCursorPosCallback) 81 | 82 | // TODO: Create a runloop that publish onUpdate back to application 83 | } 84 | } 85 | 86 | // Event Callbacks 87 | private func glfwWindowResizeCallback(window: OpaquePointer?, width: Int32, height: Int32) { 88 | guard let dataPointer = glfwGetWindowUserPointer(window) else { 89 | Log.warn("WindowResizeCallback::Not able to get window data!") 90 | return 91 | } 92 | 93 | let w = UInt32(width), h = UInt32(height) 94 | dataPointer.storeBytes(of: w, toByteOffset: WindowDataOffsets.widthOffset, as: UInt32.self) 95 | dataPointer.storeBytes(of: h, toByteOffset: WindowDataOffsets.heightOffset, as: UInt32.self) 96 | 97 | let event = WindowViewResizeEvent(size: Int2(Int(w), Int(h))) 98 | publishEvent(dataPointer: dataPointer, event: event) 99 | } 100 | 101 | private func glfwWindowCloseCallback(window: OpaquePointer?) { 102 | guard let dataPointer = glfwGetWindowUserPointer(window) else { 103 | Log.warn("WindowCloseCallback::Not able to get window data!") 104 | return 105 | } 106 | 107 | let event = WindowCloseEvent() 108 | publishEvent(dataPointer: dataPointer, event: event) 109 | } 110 | 111 | private func glfwKeyCallback(window: OpaquePointer?, key: Int32, scancode: Int32, action: Int32, mods: Int32) { 112 | guard let dataPointer = glfwGetWindowUserPointer(window) else { 113 | Log.warn("KeyCallback::Not able to get window data!") 114 | return 115 | } 116 | 117 | switch action { 118 | case 0: 119 | let event = KeyReleasedEvent(keyCode: KeyCode(key)) 120 | publishEvent(dataPointer: dataPointer, event: event) 121 | case 1: 122 | let event = KeyPressedEvent(keyCode: KeyCode(key), repeat: 0) // KeyCode is wrong, which is Cocoa version 123 | publishEvent(dataPointer: dataPointer, event: event) 124 | case 2: 125 | let event = KeyPressedEvent(keyCode: KeyCode(key), repeat: 1) 126 | publishEvent(dataPointer: dataPointer, event: event) 127 | default: 128 | assertionFailure("KeyCallback::Unsupported button state!") 129 | } 130 | } 131 | 132 | private func glfwCharCallback(window: OpaquePointer?, keycode: UInt32) { 133 | guard let dataPointer = glfwGetWindowUserPointer(window) else { 134 | Log.warn("CharCallback::Not able to get window data!") 135 | return 136 | } 137 | 138 | let event = CharTypedEvent(char: String()) // not properly set up yet 139 | publishEvent(dataPointer: dataPointer, event: event) 140 | } 141 | 142 | private func glfwMouseButtonCallback(window: OpaquePointer?, button: Int32, action: Int32, mods: Int32) { 143 | guard let dataPointer = glfwGetWindowUserPointer(window) else { 144 | Log.warn("MouseButtonCallback::Not able to get window data!") 145 | return 146 | } 147 | 148 | switch action { 149 | case 0: 150 | let event = MouseButtonReleasedEvent(mouseCode: MouseCode(button)) 151 | publishEvent(dataPointer: dataPointer, event: event) 152 | case 1: 153 | let event = MouseButtonPressedEvent(mouseCode: MouseCode(button)) 154 | publishEvent(dataPointer: dataPointer, event: event) 155 | case 2: 156 | assertionFailure("MouseButtonCallback::Released is not supported yet!") 157 | default: 158 | assertionFailure("MouseButtonCallback::Unsupported button state!") 159 | } 160 | } 161 | 162 | private func glfwScrollCallback(window: OpaquePointer?, xoffset: Double, yoffset: Double) { 163 | guard let dataPointer = glfwGetWindowUserPointer(window) else { 164 | Log.warn("ScrollCallback::Not able to get window data!") 165 | return 166 | } 167 | 168 | let event = MouseScrolledEvent(x: Float(xoffset), y: Float(yoffset)) 169 | publishEvent(dataPointer: dataPointer, event: event) 170 | } 171 | 172 | private func glfwCursorPosCallback(window: OpaquePointer?, xpos: Double, ypos: Double) { 173 | guard let dataPointer = glfwGetWindowUserPointer(window) else { 174 | Log.warn("CursorPosCallback::Not able to get window data!") 175 | return 176 | } 177 | 178 | let event = MouseMovedEvent(x: Float(xpos), y: Float(ypos)) 179 | publishEvent(dataPointer: dataPointer, event: event) 180 | } 181 | 182 | private func publishEvent(dataPointer: UnsafeMutableRawPointer, event: Event) { 183 | dataPointer.load(as: WindowData.self).windowDelegate?.onEvent(event: event) 184 | } 185 | */ 186 | -------------------------------------------------------------------------------- /Sources/Palico/ImGui/ImGui+Extra.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ImGui+Extra.swift 3 | // Palico 4 | // 5 | // Created by Junhao Wang on 1/5/22. 6 | // 7 | 8 | import ImGui 9 | 10 | public let ImGuiFlag_None: Int32 = 0 11 | 12 | public func ImGuiHelpMarker(_ label: String = "", _ string: String = "") { 13 | ImGuiTextV("\(FAIcon.questionCircle) \(label)") 14 | if igIsItemHovered(0) { 15 | ImGuiBeginTooltip() 16 | ImGuiTextUnformatted(string, nil) 17 | ImGuiEndTooltip() 18 | } 19 | } 20 | 21 | extension ImVec2 { 22 | public init(_ v0: Float, _ v1: Float) { 23 | self.init(x: v0, y: v1) 24 | } 25 | 26 | public init(_ v: Float) { 27 | self.init(v, v) 28 | } 29 | } 30 | 31 | extension ImVec4 { 32 | public init(_ v0: Float, _ v1: Float, _ v2: Float, _ v3: Float) { 33 | self.init(x: v0, y: v1, z: v2, w: v3) 34 | } 35 | 36 | public init(_ v: Float) { 37 | self.init(v, v, v, v) 38 | } 39 | } 40 | 41 | // Im Functions 42 | @inline(__always) public func Im(_ flag: ImGuiWindowFlags_) -> Int32 { 43 | return Int32(flag.rawValue) 44 | } 45 | 46 | @inline(__always) public func Im(_ flag: ImGuiInputTextFlags_) -> Int32 { 47 | return Int32(flag.rawValue) 48 | } 49 | 50 | @inline(__always) public func Im(_ flag: ImGuiTreeNodeFlags_) -> Int32 { 51 | return Int32(flag.rawValue) 52 | } 53 | 54 | @inline(__always) public func Im(_ flag: ImGuiPopupFlags_) -> Int32 { 55 | return Int32(flag.rawValue) 56 | } 57 | 58 | @inline(__always) public func Im(_ flag: ImGuiSelectableFlags_) -> Int32 { 59 | return Int32(flag.rawValue) 60 | } 61 | 62 | @inline(__always) public func Im(_ flag: ImGuiComboFlags_) -> Int32 { 63 | return Int32(flag.rawValue) 64 | } 65 | 66 | @inline(__always) public func Im(_ flag: ImGuiTabBarFlags_) -> Int32 { 67 | return Int32(flag.rawValue) 68 | } 69 | 70 | @inline(__always) public func Im(_ flag: ImGuiTabItemFlags_) -> Int32 { 71 | return Int32(flag.rawValue) 72 | } 73 | 74 | @inline(__always) public func Im(_ flag: ImGuiTableFlags_) -> Int32 { 75 | return Int32(flag.rawValue) 76 | } 77 | 78 | @inline(__always) public func Im(_ flag: ImGuiTableColumnFlags_) -> Int32 { 79 | return Int32(flag.rawValue) 80 | } 81 | 82 | @inline(__always) public func Im(_ flag: ImGuiTableRowFlags_) -> Int32 { 83 | return Int32(flag.rawValue) 84 | } 85 | 86 | @inline(__always) public func Im(_ flag: ImGuiTableBgTarget_) -> Int32 { 87 | return Int32(flag.rawValue) 88 | } 89 | 90 | @inline(__always) public func Im(_ flag: ImGuiFocusedFlags_) -> Int32 { 91 | return Int32(flag.rawValue) 92 | } 93 | 94 | @inline(__always) public func Im(_ flag: ImGuiHoveredFlags_) -> Int32 { 95 | return Int32(flag.rawValue) 96 | } 97 | 98 | @inline(__always) public func Im(_ flag: ImGuiDockNodeFlags_) -> Int32 { 99 | return Int32(flag.rawValue) 100 | } 101 | 102 | @inline(__always) public func Im(_ flag: ImGuiDragDropFlags_) -> Int32 { 103 | return Int32(flag.rawValue) 104 | } 105 | 106 | @inline(__always) public func Im(_ flag: ImGuiDataType_) -> Int32 { 107 | return Int32(flag.rawValue) 108 | } 109 | 110 | @inline(__always) public func Im(_ flag: ImGuiDir_) -> Int32 { 111 | return Int32(flag.rawValue) 112 | } 113 | 114 | @inline(__always) public func Im(_ flag: ImGuiSortDirection_) -> Int32 { 115 | return Int32(flag.rawValue) 116 | } 117 | 118 | @inline(__always) public func Im(_ flag: ImGuiKey_) -> Int32 { 119 | return Int32(flag.rawValue) 120 | } 121 | 122 | @inline(__always) public func Im(_ flag: ImGuiKeyModFlags_) -> Int32 { 123 | return Int32(flag.rawValue) 124 | } 125 | 126 | @inline(__always) public func Im(_ flag: ImGuiNavInput_) -> Int32 { 127 | return Int32(flag.rawValue) 128 | } 129 | 130 | @inline(__always) public func Im(_ flag: ImGuiConfigFlags_) -> Int32 { 131 | return Int32(flag.rawValue) 132 | } 133 | 134 | @inline(__always) public func Im(_ flag: ImGuiBackendFlags_) -> Int32 { 135 | return Int32(flag.rawValue) 136 | } 137 | 138 | @inline(__always) public func Im(_ flag: ImGuiCol_) -> Int32 { 139 | return Int32(flag.rawValue) 140 | } 141 | 142 | @inline(__always) public func Im(_ flag: ImGuiStyleVar_) -> Int32 { 143 | return Int32(flag.rawValue) 144 | } 145 | 146 | @inline(__always) public func Im(_ flag: ImGuiButtonFlags_) -> Int32 { 147 | return Int32(flag.rawValue) 148 | } 149 | 150 | @inline(__always) public func Im(_ flag: ImGuiColorEditFlags_) -> Int32 { 151 | return Int32(flag.rawValue) 152 | } 153 | 154 | @inline(__always) public func Im(_ flag: ImGuiSliderFlags_) -> Int32 { 155 | return Int32(flag.rawValue) 156 | } 157 | 158 | @inline(__always) public func Im(_ flag: ImGuiMouseButton_) -> Int32 { 159 | return Int32(flag.rawValue) 160 | } 161 | 162 | @inline(__always) public func Im(_ flag: ImGuiMouseCursor_) -> Int32 { 163 | return Int32(flag.rawValue) 164 | } 165 | 166 | @inline(__always) public func Im(_ flag: ImGuiCond_) -> Int32 { 167 | return Int32(flag.rawValue) 168 | } 169 | 170 | @inline(__always) public func Im(_ flag: ImGuiItemFlags_) -> Int32 { 171 | return Int32(flag.rawValue) 172 | } 173 | 174 | @inline(__always) public func Im(_ flag: ImGuiItemStatusFlags_) -> Int32 { 175 | return Int32(flag.rawValue) 176 | } 177 | 178 | @inline(__always) public func Im(_ flag: ImGuiInputTextFlagsPrivate_) -> Int32 { 179 | return Int32(flag.rawValue) 180 | } 181 | 182 | @inline(__always) public func Im(_ flag: ImGuiButtonFlagsPrivate_) -> Int32 { 183 | return Int32(flag.rawValue) 184 | } 185 | 186 | @inline(__always) public func Im(_ flag: ImGuiComboFlagsPrivate_) -> Int32 { 187 | return Int32(flag.rawValue) 188 | } 189 | 190 | @inline(__always) public func Im(_ flag: ImGuiSliderFlagsPrivate_) -> Int32 { 191 | return Int32(flag.rawValue) 192 | } 193 | 194 | @inline(__always) public func Im(_ flag: ImGuiSelectableFlagsPrivate_) -> Int32 { 195 | return Int32(flag.rawValue) 196 | } 197 | 198 | @inline(__always) public func Im(_ flag: ImGuiTreeNodeFlagsPrivate_) -> Int32 { 199 | return Int32(flag.rawValue) 200 | } 201 | 202 | @inline(__always) public func Im(_ flag: ImGuiSeparatorFlags_) -> Int32 { 203 | return Int32(flag.rawValue) 204 | } 205 | 206 | @inline(__always) public func Im(_ flag: ImGuiTextFlags_) -> Int32 { 207 | return Int32(flag.rawValue) 208 | } 209 | 210 | @inline(__always) public func Im(_ flag: ImGuiTooltipFlags_) -> Int32 { 211 | return Int32(flag.rawValue) 212 | } 213 | 214 | @inline(__always) public func Im(_ flag: ImGuiLayoutType_) -> Int32 { 215 | return Int32(flag.rawValue) 216 | } 217 | 218 | @inline(__always) public func Im(_ flag: ImGuiLogType) -> Int32 { 219 | return Int32(flag.rawValue) 220 | } 221 | 222 | @inline(__always) public func Im(_ flag: ImGuiAxis) -> Int32 { 223 | return Int32(flag.rawValue) 224 | } 225 | 226 | @inline(__always) public func Im(_ flag: ImGuiPlotType) -> Int32 { 227 | return Int32(flag.rawValue) 228 | } 229 | 230 | @inline(__always) public func Im(_ flag: ImGuiInputSource) -> Int32 { 231 | return Int32(flag.rawValue) 232 | } 233 | 234 | @inline(__always) public func Im(_ flag: ImGuiInputReadMode) -> Int32 { 235 | return Int32(flag.rawValue) 236 | } 237 | 238 | @inline(__always) public func Im(_ flag: ImGuiPopupPositionPolicy) -> Int32 { 239 | return Int32(flag.rawValue) 240 | } 241 | 242 | @inline(__always) public func Im(_ flag: ImGuiDataTypePrivate_) -> Int32 { 243 | return Int32(flag.rawValue) 244 | } 245 | 246 | @inline(__always) public func Im(_ flag: ImGuiNextWindowDataFlags_) -> Int32 { 247 | return Int32(flag.rawValue) 248 | } 249 | 250 | @inline(__always) public func Im(_ flag: ImGuiNextItemDataFlags_) -> Int32 { 251 | return Int32(flag.rawValue) 252 | } 253 | 254 | @inline(__always) public func Im(_ flag: ImGuiActivateFlags_) -> Int32 { 255 | return Int32(flag.rawValue) 256 | } 257 | 258 | @inline(__always) public func Im(_ flag: ImGuiScrollFlags_) -> Int32 { 259 | return Int32(flag.rawValue) 260 | } 261 | 262 | @inline(__always) public func Im(_ flag: ImGuiNavHighlightFlags_) -> Int32 { 263 | return Int32(flag.rawValue) 264 | } 265 | 266 | @inline(__always) public func Im(_ flag: ImGuiNavDirSourceFlags_) -> Int32 { 267 | return Int32(flag.rawValue) 268 | } 269 | 270 | @inline(__always) public func Im(_ flag: ImGuiNavMoveFlags_) -> Int32 { 271 | return Int32(flag.rawValue) 272 | } 273 | 274 | @inline(__always) public func Im(_ flag: ImGuiNavLayer) -> Int32 { 275 | return Int32(flag.rawValue) 276 | } 277 | 278 | @inline(__always) public func Im(_ flag: ImGuiOldColumnFlags_) -> Int32 { 279 | return Int32(flag.rawValue) 280 | } 281 | 282 | @inline(__always) public func Im(_ flag: ImGuiDockNodeFlagsPrivate_) -> Int32 { 283 | return Int32(flag.rawValue) 284 | } 285 | 286 | @inline(__always) public func Im(_ flag: ImGuiDataAuthority_) -> Int32 { 287 | return Int32(flag.rawValue) 288 | } 289 | 290 | @inline(__always) public func Im(_ flag: ImGuiDockNodeState) -> Int32 { 291 | return Int32(flag.rawValue) 292 | } 293 | 294 | @inline(__always) public func Im(_ flag: ImGuiWindowDockStyleCol) -> Int32 { 295 | return Int32(flag.rawValue) 296 | } 297 | 298 | @inline(__always) public func Im(_ flag: ImGuiContextHookType) -> Int32 { 299 | return Int32(flag.rawValue) 300 | } 301 | 302 | @inline(__always) public func Im(_ flag: ImGuiTabBarFlagsPrivate_) -> Int32 { 303 | return Int32(flag.rawValue) 304 | } 305 | 306 | @inline(__always) public func Im(_ flag: ImGuiTabItemFlagsPrivate_) -> Int32 { 307 | return Int32(flag.rawValue) 308 | } 309 | --------------------------------------------------------------------------------