├── .gitignore
├── .swiftpm
└── xcode
│ └── package.xcworkspace
│ └── contents.xcworkspacedata
├── Assets
├── Images
│ └── vertexkit-particle-noise.png
├── Pixels-3D_logo_1k.png
├── Pixels-3D_logo_1k_bg.png
├── Pixels-3D_logo_4k.png
└── Pixels-3D_logo_4k_bg.png
├── LICENSE
├── Package.resolved
├── Package.swift
├── README.md
├── Source
├── Shaders
│ └── Color3DPIX.metal
├── VTXs
│ └── Content
│ │ └── Generator
│ │ ├── Particles
│ │ ├── ParticlesPIX.swift
│ │ ├── ParticlesPixelModel.swift
│ │ └── ParticlesVTX.metal
│ │ └── UVParticles
│ │ ├── UVParticlesPIX.swift
│ │ ├── UVParticlesPixelModel.swift
│ │ └── UVParticlesVTX.metal
└── VertexKit.swift
└── VertexKit.xcworkspace
├── contents.xcworkspacedata
└── xcshareddata
├── IDEWorkspaceChecks.plist
└── swiftpm
└── Package.resolved
/.gitignore:
--------------------------------------------------------------------------------
1 | .DS_Store
2 |
3 | xcuserdata/
4 |
5 | build/*
6 | docs/*
7 |
8 | Pods/*
--------------------------------------------------------------------------------
/.swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/Assets/Images/vertexkit-particle-noise.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/heestand-xyz/VertexKit/2be56472a4fb43ce851dd7875864772b6b5c7fe3/Assets/Images/vertexkit-particle-noise.png
--------------------------------------------------------------------------------
/Assets/Pixels-3D_logo_1k.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/heestand-xyz/VertexKit/2be56472a4fb43ce851dd7875864772b6b5c7fe3/Assets/Pixels-3D_logo_1k.png
--------------------------------------------------------------------------------
/Assets/Pixels-3D_logo_1k_bg.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/heestand-xyz/VertexKit/2be56472a4fb43ce851dd7875864772b6b5c7fe3/Assets/Pixels-3D_logo_1k_bg.png
--------------------------------------------------------------------------------
/Assets/Pixels-3D_logo_4k.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/heestand-xyz/VertexKit/2be56472a4fb43ce851dd7875864772b6b5c7fe3/Assets/Pixels-3D_logo_4k.png
--------------------------------------------------------------------------------
/Assets/Pixels-3D_logo_4k_bg.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/heestand-xyz/VertexKit/2be56472a4fb43ce851dd7875864772b6b5c7fe3/Assets/Pixels-3D_logo_4k_bg.png
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2018 ⬢ Hexagons
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 |
--------------------------------------------------------------------------------
/Package.resolved:
--------------------------------------------------------------------------------
1 | {
2 | "object": {
3 | "pins": [
4 | {
5 | "package": "CoreGraphicsExtensions",
6 | "repositoryURL": "https://github.com/heestand-xyz/CoreGraphicsExtensions",
7 | "state": {
8 | "branch": null,
9 | "revision": "89b30426f001988a878905d9cdcec0647dc17281",
10 | "version": "1.3.1"
11 | }
12 | },
13 | {
14 | "package": "PixelColor",
15 | "repositoryURL": "https://github.com/heestand-xyz/PixelColor",
16 | "state": {
17 | "branch": null,
18 | "revision": "04d2444fc288edc5fa5cbfd85bbbc9128b63e6db",
19 | "version": "1.3.4"
20 | }
21 | },
22 | {
23 | "package": "PixelKit",
24 | "repositoryURL": "https://github.com/heestand-xyz/PixelKit",
25 | "state": {
26 | "branch": null,
27 | "revision": "f663ac4951ac04138797dfc5d819c875b7010325",
28 | "version": "3.0.3"
29 | }
30 | },
31 | {
32 | "package": "RenderKit",
33 | "repositoryURL": "https://github.com/heestand-xyz/RenderKit",
34 | "state": {
35 | "branch": null,
36 | "revision": "71c1363f8ea93405209b4d20303b3caa1058501a",
37 | "version": "2.0.2"
38 | }
39 | },
40 | {
41 | "package": "Resolution",
42 | "repositoryURL": "https://github.com/heestand-xyz/Resolution",
43 | "state": {
44 | "branch": null,
45 | "revision": "0fdee03d7b312f075897cdac437f366cc6631d6a",
46 | "version": "1.0.4"
47 | }
48 | },
49 | {
50 | "package": "TextureMap",
51 | "repositoryURL": "https://github.com/heestand-xyz/TextureMap",
52 | "state": {
53 | "branch": null,
54 | "revision": "dd04b7c1bf64853afa4c045a511bbca7f5b12f54",
55 | "version": "0.5.0"
56 | }
57 | }
58 | ]
59 | },
60 | "version": 1
61 | }
62 |
--------------------------------------------------------------------------------
/Package.swift:
--------------------------------------------------------------------------------
1 | // swift-tools-version:5.3
2 |
3 | import PackageDescription
4 |
5 | let package = Package(
6 | name: "VertexKit",
7 | platforms: [
8 | .iOS(.v13),
9 | .macOS(.v10_15),
10 | .tvOS(.v13)
11 | ],
12 | products: [
13 | .library(name: "VertexKit", targets: ["VertexKit"]),
14 | ],
15 | dependencies: [
16 | .package(url: "https://github.com/heestand-xyz/PixelKit", from: "3.0.1"),
17 | .package(url: "https://github.com/heestand-xyz/PixelColor", from: "1.3.2"),
18 | .package(url: "https://github.com/heestand-xyz/Resolution", from: "1.0.4"),
19 | .package(url: "https://github.com/heestand-xyz/CoreGraphicsExtensions", from: "1.3.2"),
20 | ],
21 | targets: [
22 | .target(name: "VertexKit", dependencies: ["PixelKit", "PixelColor", "Resolution", "CoreGraphicsExtensions"], path: "Source"),
23 | ]
24 | )
25 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
2 |
3 | # VertexKit
4 |
5 | a Framework for iOS & macOS
6 | written in Swift & Metal
7 | an extension of [PixelKit](https://github.com/heestand-xyz/pixelkit)
8 |
9 | ## Particles Example
10 |
11 |
12 |
13 | ```swift
14 | view.wantsLayer = true
15 | view.layer!.backgroundColor = .black
16 |
17 | PixelKit.main.render.bits = ._16
18 |
19 | let pres: Resolution = .square(Int(sqrt(1_000_000)))
20 |
21 | let noise = NoisePIX(at: pres)
22 | noise.colored = true
23 | noise.octaves = 5
24 | noise.zPosition = .live * 0.1
25 |
26 | let particles = UVParticlesPIX(at: .size(view.bounds.size) * 2)
27 | particles.vtxPixIn = noise - 0.5
28 | particles.color = LiveColor(lum: 1.0, a: 0.1)
29 |
30 | let finalPix: PIX = particles
31 | finalPix.view.frame = view.bounds
32 | finalPix.view.checker = false
33 | view.addSubview(finalPix.view)
34 | ```
35 |
--------------------------------------------------------------------------------
/Source/Shaders/Color3DPIX.metal:
--------------------------------------------------------------------------------
1 | //
2 | // Color3DPIX.metal
3 | // VertexKit
4 | //
5 | // Created by Hexagons on 2018-09-22.
6 | // Copyright © 2018 Hexagons. All rights reserved.
7 | //
8 |
9 | #include
10 | using namespace metal;
11 |
12 | struct VertexOut{
13 | float4 position [[position]];
14 | float2 texCoord;
15 | float4 color;
16 | };
17 |
18 | struct Uniforms{
19 | float r;
20 | float g;
21 | float b;
22 | float a;
23 | };
24 |
25 | fragment float4 color3DPIX(VertexOut out [[stage_in]],
26 | const device Uniforms& in [[ buffer(0) ]],
27 | sampler s [[ sampler(0) ]]) {
28 | return float4(in.r * out.color.r,
29 | in.g * out.color.g,
30 | in.b * out.color.b,
31 | in.a * out.color.a);
32 | }
33 |
34 |
--------------------------------------------------------------------------------
/Source/VTXs/Content/Generator/Particles/ParticlesPIX.swift:
--------------------------------------------------------------------------------
1 | //
2 | // UVParticlesPIX.swift
3 | // VertexKit
4 | //
5 | // Created by Anton Heestand on 2019-05-02.
6 | // Copyright © 2019 Hexagons. All rights reserved.
7 | //
8 |
9 | import CoreGraphics
10 | import CoreGraphicsExtensions
11 | import Metal
12 | import RenderKit
13 | import PixelKit
14 | import Resolution
15 | import PixelColor
16 |
17 | public class ParticlesPIX: PIXGenerator, CustomGeometryDelegate {
18 |
19 | public typealias Model = ParticlesPixelModel
20 |
21 | private var model: Model {
22 | get { generatorModel as! Model }
23 | set { generatorModel = newValue }
24 | }
25 |
26 | open override var customMetalLibrary: MTLLibrary { VertexKit.metalLibrary }
27 | open override var customVertexShaderName: String? { "particlesVTX" }
28 | open override var shaderName: String { "color3DPIX" }
29 |
30 | public override var additiveVertexBlending: Bool { true }
31 |
32 | struct Particle {
33 | var position: CGPoint
34 | var velocity: CGPoint
35 | var lifeTime: Double = 0.0
36 | }
37 | private var particles: [Particle] = []
38 |
39 | // MARK: Properties
40 |
41 | @LiveColor("clearBackgroundColor") public var clearBackgroundColor: PixelColor = .black
42 | @LiveFloat("lifeTime") public var lifeTime: CGFloat = 1.0
43 | @LiveInt("emitCount", range: 0...10) public var emitCount: Int = 1
44 | @LivePoint("emitPosition") public var emitPosition: CGPoint = .zero
45 | @LiveSize("emitSize") public var emitSize: CGSize = .zero
46 | @LivePoint("direction") public var direction: CGPoint = CGPoint(x: 0.0, y: 0.0)
47 | @LiveFloat("randomDirection") public var randomDirection: CGFloat = 1.0
48 | @LiveFloat("velocity", range: 0.0...0.01, increment: 0.001) public var velocity: CGFloat = 0.005
49 | @LiveFloat("randomVelocity", range: 0.0...0.01, increment: 0.001) public var randomVelocity: CGFloat = 0.0
50 | @LiveFloat("particleSize", range: 0.0...2.0, increment: 1.0) public var particleSize: CGFloat = 1.0
51 |
52 | // MARK: Property Helpers
53 |
54 | public override var liveList: [LiveWrap] {
55 | super.liveList.filter({ liveWrap in
56 | liveWrap.typeName != "backgroundColor"
57 | }) + [_clearBackgroundColor, _lifeTime, _emitCount, _emitPosition, _emitSize, _direction, _randomDirection, _velocity, _randomVelocity, _particleSize]
58 | }
59 |
60 | public override var uniforms: [CGFloat] {
61 | color.components
62 | }
63 |
64 | open override var vertexUniforms: [CGFloat] {
65 | [particleSize]
66 | }
67 |
68 | // MARK: - Life Cycle -
69 |
70 | public init(model: Model) {
71 | super.init(model: model)
72 | setup()
73 | }
74 |
75 | public required init(at resolution: Resolution = .auto) {
76 | let model = Model(resolution: resolution)
77 | super.init(model: model)
78 | setup()
79 | }
80 |
81 | // MARK: - Setup
82 |
83 | private func setup() {
84 |
85 | customGeometryActive = true
86 | customGeometryDelegate = self
87 |
88 | clearColor = clearBackgroundColor
89 | _clearBackgroundColor.didSetValue = { [weak self] in
90 | self?.clearColor = self?.clearBackgroundColor ?? .clear
91 | }
92 |
93 | PixelKit.main.render.listenToFrames(id: id) { [weak self] in
94 | self?.particleLoop()
95 | }
96 |
97 | }
98 |
99 | public override func destroy() {
100 | super.destroy()
101 |
102 | PixelKit.main.render.unlistenToFrames(for: id)
103 | }
104 |
105 | // MARK: - Live Model
106 |
107 | public override func modelUpdateLive() {
108 | super.modelUpdateLive()
109 |
110 | clearBackgroundColor = model.clearBackgroundColor
111 | lifeTime = model.lifeTime
112 | emitCount = model.emitCount
113 | emitPosition = model.emitPosition
114 | emitSize = model.emitSize
115 | direction = model.direction
116 | randomDirection = model.randomDirection
117 | velocity = model.velocity
118 | randomVelocity = model.randomVelocity
119 | particleSize = model.particleSize
120 |
121 | super.modelUpdateLiveDone()
122 | }
123 |
124 | public override func liveUpdateModel() {
125 | super.liveUpdateModel()
126 |
127 | model.clearBackgroundColor = clearBackgroundColor
128 | model.lifeTime = lifeTime
129 | model.emitCount = emitCount
130 | model.emitPosition = emitPosition
131 | model.emitSize = emitSize
132 | model.direction = direction
133 | model.randomDirection = randomDirection
134 | model.velocity = velocity
135 | model.randomVelocity = randomVelocity
136 | model.particleSize = particleSize
137 |
138 | super.liveUpdateModelDone()
139 | }
140 |
141 | // MARK: - Particle Loop
142 |
143 | private func particleLoop() {
144 | addParticles()
145 | moveParticles()
146 | removeParticles()
147 | render()
148 | }
149 |
150 | private func addParticles() {
151 | guard emitCount > 0 else { return }
152 | for _ in 0.. 0.0 {
159 | velocity += CGPoint(x: .random(in: -1.0...1.0) * randomDirection * self.velocity,
160 | y: .random(in: -1.0...1.0) * randomDirection * self.velocity)
161 | }
162 | let particle = Particle(position: position, velocity: velocity)
163 | particles.append(particle)
164 | }
165 | }
166 |
167 | private func moveParticles() {
168 | for (index, particle) in particles.enumerated() {
169 | var velocity = particle.velocity
170 | if randomVelocity > 0.0 {
171 | velocity += CGPoint(x: .random(in: -1.0...1.0) * randomVelocity,
172 | y: .random(in: -1.0...1.0) * randomVelocity)
173 | }
174 | particles[index].position += velocity
175 | particles[index].lifeTime += PixelKit.main.render.secondsPerFrame
176 | }
177 | }
178 |
179 | private func removeParticles() {
180 | for (index, particle) in particles.enumerated().reversed() {
181 | if particle.lifeTime >= lifeTime {
182 | particles.remove(at: index)
183 | }
184 | }
185 | }
186 |
187 | public func removeAllParticles() {
188 | particles = []
189 | render()
190 | }
191 |
192 | // MARK: - Custom Vertices
193 |
194 | public func customVertices() -> RenderKit.Vertices? {
195 |
196 | let count = particles.count
197 | var vertices: [RenderKit.Vertex] = []
198 | for particle in particles {
199 | vertices.append(Vertex(x: particle.position.x,
200 | y: particle.position.y))
201 | }
202 |
203 | let vertexBuffer = vertices.flatMap(\.buffer3d)
204 | let vertexBufferSize = max(vertexBuffer.count, 1) * MemoryLayout.size
205 | let vertexBufferBuffer = PixelKit.main.render.metalDevice.makeBuffer(bytes: !vertexBuffer.isEmpty ? vertexBuffer : [0.0], length: vertexBufferSize, options: [])!
206 |
207 | return RenderKit.Vertices(buffer: vertexBufferBuffer, vertexCount: count, type: .point, wireframe: false)
208 | }
209 |
210 | }
211 |
--------------------------------------------------------------------------------
/Source/VTXs/Content/Generator/Particles/ParticlesPixelModel.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Created by Anton Heestand on 2022-01-15.
3 | //
4 |
5 | import Foundation
6 | import CoreGraphics
7 | import RenderKit
8 | import PixelKit
9 | import Resolution
10 | import PixelColor
11 |
12 | public struct ParticlesPixelModel: PixelGeneratorModel {
13 |
14 | // MARK: Global
15 |
16 | public var id: UUID = UUID()
17 | public var name: String = "Particles"
18 | public var typeName: String = "vtx-pix-content-generator-particles"
19 | public var bypass: Bool = false
20 |
21 | public var outputNodeReferences: [NodeReference] = []
22 |
23 | public var viewInterpolation: ViewInterpolation = .linear
24 | public var interpolation: PixelInterpolation = .linear
25 | public var extend: ExtendMode = .zero
26 |
27 | public var premultiply: Bool = true
28 | public var resolution: Resolution = .auto
29 |
30 | public var backgroundColor: PixelColor = .black
31 | public var color: PixelColor = .white
32 |
33 | // MARK: Local
34 |
35 | public var clearBackgroundColor: PixelColor = .black
36 | public var lifeTime: CGFloat = 1.0
37 | public var emitCount: Int = 1
38 | public var emitPosition: CGPoint = .zero
39 | public var emitSize: CGSize = .zero
40 | public var direction: CGPoint = .zero
41 | public var randomDirection: CGFloat = 1.0
42 | public var velocity: CGFloat = 0.005
43 | public var randomVelocity: CGFloat = 0.0
44 | public var particleSize: CGFloat = 0.1
45 | }
46 |
47 | extension ParticlesPixelModel {
48 |
49 | enum LocalCodingKeys: String, CodingKey, CaseIterable {
50 | case clearBackgroundColor
51 | case lifeTime
52 | case emitCount
53 | case emitPosition
54 | case emitSize
55 | case direction
56 | case randomDirection
57 | case velocity
58 | case randomVelocity
59 | case particleSize
60 | }
61 |
62 | public init(from decoder: Decoder) throws {
63 |
64 | self = try PixelGeneratorModelDecoder.decode(from: decoder, model: self) as! Self
65 |
66 | let container = try decoder.container(keyedBy: LocalCodingKeys.self)
67 |
68 | if try PixelModelDecoder.isLiveListCodable(decoder: decoder) {
69 | let liveList: [LiveWrap] = try PixelModelDecoder.liveListDecode(from: decoder)
70 | for codingKey in LocalCodingKeys.allCases {
71 | guard let liveWrap: LiveWrap = liveList.first(where: { $0.typeName == codingKey.rawValue }) else { continue }
72 |
73 | switch codingKey {
74 | case .clearBackgroundColor:
75 | guard let live = liveWrap as? LiveColor else { continue }
76 | clearBackgroundColor = live.wrappedValue
77 | case .lifeTime:
78 | guard let live = liveWrap as? LiveFloat else { continue }
79 | lifeTime = live.wrappedValue
80 | case .emitCount:
81 | guard let live = liveWrap as? LiveInt else { continue }
82 | emitCount = live.wrappedValue
83 | case .emitPosition:
84 | guard let live = liveWrap as? LivePoint else { continue }
85 | emitPosition = live.wrappedValue
86 | case .emitSize:
87 | guard let live = liveWrap as? LiveSize else { continue }
88 | emitSize = live.wrappedValue
89 | case .direction:
90 | guard let live = liveWrap as? LivePoint else { continue }
91 | direction = live.wrappedValue
92 | case .randomDirection:
93 | guard let live = liveWrap as? LiveFloat else { continue }
94 | randomDirection = live.wrappedValue
95 | case .velocity:
96 | guard let live = liveWrap as? LiveFloat else { continue }
97 | velocity = live.wrappedValue
98 | case .randomVelocity:
99 | guard let live = liveWrap as? LiveFloat else { continue }
100 | randomVelocity = live.wrappedValue
101 | case .particleSize:
102 | guard let live = liveWrap as? LiveFloat else { continue }
103 | particleSize = live.wrappedValue
104 | }
105 | }
106 | return
107 | }
108 |
109 | clearBackgroundColor = try container.decode(PixelColor.self, forKey: .clearBackgroundColor)
110 | lifeTime = try container.decode(CGFloat.self, forKey: .lifeTime)
111 | emitCount = try container.decode(Int.self, forKey: .emitCount)
112 | emitPosition = try container.decode(CGPoint.self, forKey: .emitPosition)
113 | emitSize = try container.decode(CGSize.self, forKey: .emitSize)
114 | direction = try container.decode(CGPoint.self, forKey: .direction)
115 | randomDirection = try container.decode(CGFloat.self, forKey: .randomDirection)
116 | velocity = try container.decode(CGFloat.self, forKey: .velocity)
117 | randomVelocity = try container.decode(CGFloat.self, forKey: .randomVelocity)
118 | particleSize = try container.decode(CGFloat.self, forKey: .particleSize)
119 | }
120 | }
121 |
122 | extension ParticlesPixelModel {
123 |
124 | public func isEqual(to nodeModel: NodeModel) -> Bool {
125 | guard let pixelModel = nodeModel as? Self else { return false }
126 | guard isPixelGeneratorEqual(to: pixelModel) else { return false }
127 | guard clearBackgroundColor == pixelModel.clearBackgroundColor else { return false }
128 | guard lifeTime == pixelModel.lifeTime else { return false }
129 | guard emitCount == pixelModel.emitCount else { return false }
130 | guard emitPosition == pixelModel.emitPosition else { return false }
131 | guard emitSize == pixelModel.emitSize else { return false }
132 | guard direction == pixelModel.direction else { return false }
133 | guard randomDirection == pixelModel.randomDirection else { return false }
134 | guard velocity == pixelModel.velocity else { return false }
135 | guard randomVelocity == pixelModel.randomVelocity else { return false }
136 | guard particleSize == pixelModel.particleSize else { return false }
137 | return true
138 | }
139 | }
140 |
--------------------------------------------------------------------------------
/Source/VTXs/Content/Generator/Particles/ParticlesVTX.metal:
--------------------------------------------------------------------------------
1 | //
2 | // ParticleUV3DVTX.metal
3 | // VertexKitShaders
4 | //
5 | // Created by Anton Heestand on 2019-05-02.
6 | // Copyright © 2019 Hexagons. All rights reserved.
7 | //
8 |
9 | #include
10 | using namespace metal;
11 |
12 | struct VertexIn{
13 | packed_float3 position;
14 | packed_float2 texCoord;
15 | };
16 |
17 | struct VertexOut{
18 | float4 position [[position]];
19 | float pointSize [[point_size]];
20 | float4 color;
21 | };
22 |
23 | struct Uniforms {
24 | float size;
25 | };
26 |
27 | vertex VertexOut particlesVTX(const device VertexIn* vertices [[ buffer(0) ]],
28 | unsigned int vid [[ vertex_id ]],
29 | const device Uniforms& in [[ buffer(1) ]],
30 | sampler s [[ sampler(0) ]]) {
31 |
32 | int index = vid;
33 |
34 | float x = vertices[index].position.x * 2;
35 | float y = vertices[index].position.y * 2;
36 | float z = 0.0;
37 |
38 | float4 color = float4(1, 1, 1, 1);
39 |
40 | VertexOut vtxOut;
41 | vtxOut.position = float4(x, y, z, 1);
42 | vtxOut.pointSize = in.size;
43 | vtxOut.color = color;
44 |
45 | return vtxOut;
46 | }
47 |
--------------------------------------------------------------------------------
/Source/VTXs/Content/Generator/UVParticles/UVParticlesPIX.swift:
--------------------------------------------------------------------------------
1 | //
2 | // UVParticlesPIX.swift
3 | // VertexKit
4 | //
5 | // Created by Anton Heestand on 2019-05-02.
6 | // Copyright © 2019 Hexagons. All rights reserved.
7 | //
8 |
9 | import CoreGraphics
10 | import Metal
11 | import RenderKit
12 | import PixelKit
13 | import Resolution
14 | import PixelColor
15 |
16 | public class UVParticlesPIX: PIXGenerator, CustomGeometryDelegate {
17 |
18 | public typealias Model = UVParticlesPixelModel
19 |
20 | private var model: Model {
21 | get { generatorModel as! Model }
22 | set { generatorModel = newValue }
23 | }
24 |
25 | open override var customMetalLibrary: MTLLibrary { VertexKit.metalLibrary }
26 | open override var customVertexShaderName: String? { "uvParticlesVTX" }
27 | open override var shaderName: String { "color3DPIX" }
28 |
29 | public override var customVertexTextureActive: Bool { true }
30 | public override var customVertexNodeIn: (NODE & NODEOut)? {
31 | particlesInput
32 | }
33 |
34 | public override var additiveVertexBlending: Bool { true }
35 |
36 | @LiveColor("clearBackgroundColor") public var clearBackgroundColor: PixelColor = .black
37 |
38 | @LiveFloat("particleSize", range: 0.0...2.0, increment: 1.0) public var particleSize: CGFloat = 1.0
39 | /// Map Size of each particle from the blue channel
40 | @LiveBool("hasSize") public var hasSize: Bool = false
41 | /// Map Alpha of each particle from the alpha channel
42 | @LiveBool("hasAlpha") public var hasAlpha: Bool = false
43 | /// `hasAlpha` needs to be `true`. The clips any alpha below 1.0.
44 | @LiveBool("hasAlphaClip") public var hasAlphaClip: Bool = false
45 |
46 | @available(*, deprecated, renamed: "particlesInput")
47 | public var vtxPixIn: (PIX & NODEOut)? {
48 | get { particlesInput }
49 | set { particlesInput = newValue }
50 | }
51 | public var particlesInput: (PIX & NODEOut)? { didSet { render() } }
52 |
53 | public override var liveList: [LiveWrap] {
54 | super.liveList.filter({ liveWrap in
55 | liveWrap.typeName != "backgroundColor"
56 | }) + [_particleSize, _hasSize, _hasAlpha, _hasAlphaClip]
57 | }
58 |
59 | public override var uniforms: [CGFloat] {
60 | color.components
61 | }
62 | open override var vertexUniforms: [CGFloat] {
63 | [particleSize, particlesInput?.finalResolution.width ?? 1, particlesInput?.finalResolution.height ?? 1, hasSize ? 1 : 0, hasAlpha ? 1 : 0, hasAlphaClip ? 1 : 0, resolution.aspect]
64 | }
65 |
66 | // MARK: - Life Cycle -
67 |
68 | public init(model: Model) {
69 | super.init(model: model)
70 | setup()
71 | }
72 |
73 | public required init(at resolution: Resolution = .auto) {
74 | let model = Model(resolution: resolution)
75 | super.init(model: model)
76 | setup()
77 | }
78 |
79 | // MARK: - Setup
80 |
81 | private func setup() {
82 | customGeometryActive = true
83 | customGeometryDelegate = self
84 | clearColor = clearBackgroundColor
85 | _clearBackgroundColor.didSetValue = { [weak self] in
86 | self?.clearColor = self?.clearBackgroundColor ?? .clear
87 | }
88 | }
89 |
90 | // MARK: - Live Model
91 |
92 | public override func modelUpdateLive() {
93 | super.modelUpdateLive()
94 |
95 | clearBackgroundColor = model.clearBackgroundColor
96 | particleSize = model.particleSize
97 | hasSize = model.hasSize
98 | hasAlpha = model.hasAlpha
99 | hasAlphaClip = model.hasAlphaClip
100 |
101 | super.modelUpdateLiveDone()
102 | }
103 |
104 | public override func liveUpdateModel() {
105 | super.liveUpdateModel()
106 |
107 | model.clearBackgroundColor = clearBackgroundColor
108 | model.particleSize = particleSize
109 | model.hasSize = hasSize
110 | model.hasAlpha = hasAlpha
111 | model.hasAlphaClip = hasAlphaClip
112 |
113 | super.liveUpdateModelDone()
114 | }
115 |
116 | // MARK: Custom Geometry
117 |
118 | public func customVertices() -> RenderKit.Vertices? {
119 |
120 | let count = (particlesInput?.finalResolution.w ?? 1) * (particlesInput?.finalResolution.h ?? 1)
121 | let vertexBuffers: [Float] = [Float](repeating: 0.0, count: 1)//count)
122 |
123 | let vertexBuffersSize = vertexBuffers.count * MemoryLayout.size
124 | let verticesBuffer = PixelKit.main.render.metalDevice.makeBuffer(bytes: vertexBuffers, length: vertexBuffersSize, options: [])!
125 |
126 | return RenderKit.Vertices(buffer: verticesBuffer, vertexCount: count, type: .point, wireframe: false)
127 | }
128 |
129 | }
130 |
--------------------------------------------------------------------------------
/Source/VTXs/Content/Generator/UVParticles/UVParticlesPixelModel.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Created by Anton Heestand on 2022-01-15.
3 | //
4 |
5 | import Foundation
6 | import CoreGraphics
7 | import RenderKit
8 | import PixelKit
9 | import Resolution
10 | import PixelColor
11 |
12 | public struct UVParticlesPixelModel: PixelGeneratorModel {
13 |
14 | // MARK: Global
15 |
16 | public var id: UUID = UUID()
17 | public var name: String = "UV Particles"
18 | public var typeName: String = "vtx-pix-content-generator-uv-particles"
19 | public var bypass: Bool = false
20 |
21 | public var outputNodeReferences: [NodeReference] = []
22 |
23 | public var viewInterpolation: ViewInterpolation = .linear
24 | public var interpolation: PixelInterpolation = .linear
25 | public var extend: ExtendMode = .zero
26 |
27 | public var premultiply: Bool = true
28 | public var resolution: Resolution = .auto
29 |
30 | public var backgroundColor: PixelColor = .black
31 | public var color: PixelColor = .white
32 |
33 | // MARK: Local
34 |
35 | public var clearBackgroundColor: PixelColor = .black
36 | public var particleSize: CGFloat = 0.1
37 | public var hasSize: Bool = false
38 | public var hasAlpha: Bool = false
39 | public var hasAlphaClip: Bool = false
40 | }
41 |
42 | extension UVParticlesPixelModel {
43 |
44 | enum LocalCodingKeys: String, CodingKey, CaseIterable {
45 | case clearBackgroundColor
46 | case particleSize
47 | case hasSize
48 | case hasAlpha
49 | case hasAlphaClip
50 | }
51 |
52 | public init(from decoder: Decoder) throws {
53 |
54 | self = try PixelGeneratorModelDecoder.decode(from: decoder, model: self) as! Self
55 |
56 | let container = try decoder.container(keyedBy: LocalCodingKeys.self)
57 |
58 | if try PixelModelDecoder.isLiveListCodable(decoder: decoder) {
59 | let liveList: [LiveWrap] = try PixelModelDecoder.liveListDecode(from: decoder)
60 | for codingKey in LocalCodingKeys.allCases {
61 | guard let liveWrap: LiveWrap = liveList.first(where: { $0.typeName == codingKey.rawValue }) else { continue }
62 |
63 | switch codingKey {
64 | case .clearBackgroundColor:
65 | guard let live = liveWrap as? LiveColor else { continue }
66 | clearBackgroundColor = live.wrappedValue
67 | case .particleSize:
68 | guard let live = liveWrap as? LiveFloat else { continue }
69 | particleSize = live.wrappedValue
70 | case .hasSize:
71 | guard let live = liveWrap as? LiveBool else { continue }
72 | hasSize = live.wrappedValue
73 | case .hasAlpha:
74 | guard let live = liveWrap as? LiveBool else { continue }
75 | hasAlpha = live.wrappedValue
76 | case .hasAlphaClip:
77 | guard let live = liveWrap as? LiveBool else { continue }
78 | hasAlphaClip = live.wrappedValue
79 | }
80 | }
81 | return
82 | }
83 |
84 | clearBackgroundColor = try container.decode(PixelColor.self, forKey: .clearBackgroundColor)
85 | particleSize = try container.decode(CGFloat.self, forKey: .particleSize)
86 | hasSize = try container.decode(Bool.self, forKey: .hasSize)
87 | hasAlpha = try container.decode(Bool.self, forKey: .hasAlpha)
88 | hasAlphaClip = try container.decode(Bool.self, forKey: .hasAlphaClip)
89 | }
90 | }
91 |
92 | extension UVParticlesPixelModel {
93 |
94 | public func isEqual(to nodeModel: NodeModel) -> Bool {
95 | guard let pixelModel = nodeModel as? Self else { return false }
96 | guard isPixelGeneratorEqual(to: pixelModel) else { return false }
97 | guard clearBackgroundColor == pixelModel.clearBackgroundColor else { return false }
98 | guard particleSize == pixelModel.particleSize else { return false }
99 | guard hasSize == pixelModel.hasSize else { return false }
100 | guard hasAlpha == pixelModel.hasAlpha else { return false }
101 | guard hasAlphaClip == pixelModel.hasAlphaClip else { return false }
102 | return true
103 | }
104 | }
105 |
--------------------------------------------------------------------------------
/Source/VTXs/Content/Generator/UVParticles/UVParticlesVTX.metal:
--------------------------------------------------------------------------------
1 | //
2 | // ParticleUV3DVTX.metal
3 | // VertexKitShaders
4 | //
5 | // Created by Anton Heestand on 2019-05-02.
6 | // Copyright © 2019 Hexagons. All rights reserved.
7 | //
8 |
9 | #include
10 | using namespace metal;
11 |
12 | struct VertexIn {
13 | packed_float3 position;
14 | packed_float2 texCoord;
15 | };
16 |
17 | struct VertexOut {
18 | float4 position [[position]];
19 | float pointSize [[point_size]];
20 | float4 color;
21 | };
22 |
23 | struct Uniforms {
24 | float size;
25 | float resx;
26 | float resy;
27 | float mapSize;
28 | float mapAlpha;
29 | float mapAlphaClip;
30 | float aspect;
31 | };
32 |
33 | vertex VertexOut uvParticlesVTX(unsigned int vid [[ vertex_id ]],
34 | const device Uniforms& in [[ buffer(1) ]],
35 | texture2d inTex [[ texture(0) ]],
36 | sampler s [[ sampler(0) ]]) {
37 |
38 | int ux = vid % int(in.resx);
39 | int vy = int(float(vid) / in.resx);
40 | float u = float(ux) / in.resx;
41 | float v = float(vy) / in.resy;
42 | float2 uv = float2(u, v);
43 | float4 c = inTex.sample(s, uv);
44 | float x = (c.r / in.aspect) * 2;
45 | float y = c.g * 2;
46 | float z = 0.0;
47 |
48 | VertexOut vtxOut;
49 | vtxOut.position = float4(x, y, z, 1);
50 | vtxOut.pointSize = in.mapSize > 0 ? in.size * c.b : in.size;
51 | vtxOut.color = in.mapAlpha > 0 ? float4(1, 1, 1, in.mapAlphaClip ? c.a == 1.0 : c.a) : float4(1, 1, 1, 1);
52 |
53 | return vtxOut;
54 | }
55 |
--------------------------------------------------------------------------------
/Source/VertexKit.swift:
--------------------------------------------------------------------------------
1 | //
2 | // VertexKit.swift
3 | // VertexKit
4 | //
5 | // Created by Hexagons on 2018-08-02.
6 | // Copyright © 2018 Hexagons. All rights reserved.
7 | //
8 |
9 | import CoreGraphics
10 | import Metal
11 | import RenderKit
12 | import PixelKit
13 | import simd
14 | import Resolution
15 |
16 | public class VertexKit {
17 |
18 | public static let main = VertexKit()
19 |
20 | let pixelKit = PixelKit.main
21 |
22 | // MARK: - Life Cycle
23 |
24 | init() {
25 | print("VertexKit", "ready to render.")
26 | }
27 |
28 | // MARK: - Log
29 |
30 | public static func log(pix: PIX? = nil, _ level: Logger.LogLevel, _ category: Logger.LogCategory?, _ message: String, loop: Bool = false, clean: Bool = false, e error: Error? = nil, _ file: String = #file, _ function: String = #function, _ line: Int = #line) {
31 | PixelKit.main.logger.log(prefix: "VertexKit", level, category, message, loop: loop, clean: clean, e: error, file, function, line)
32 | }
33 |
34 | // MARK: - Setup
35 |
36 | // MARK: Shaders
37 |
38 | enum MetalLibraryError: Error {
39 | case runtimeERROR(String)
40 | }
41 |
42 | static let metalLibrary: MTLLibrary = {
43 | do {
44 | return try PixelKit.main.render.metalDevice.makeDefaultLibrary(bundle: Bundle.module)
45 | } catch {
46 | fatalError("Loading Metal Library Failed: \(error.localizedDescription)")
47 | }
48 | }()
49 |
50 | }
51 |
--------------------------------------------------------------------------------
/VertexKit.xcworkspace/contents.xcworkspacedata:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
7 |
9 |
10 |
12 |
13 |
14 |
--------------------------------------------------------------------------------
/VertexKit.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | IDEDidComputeMac32BitWarning
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/VertexKit.xcworkspace/xcshareddata/swiftpm/Package.resolved:
--------------------------------------------------------------------------------
1 | {
2 | "object": {
3 | "pins": [
4 | {
5 | "package": "CoreGraphicsExtensions",
6 | "repositoryURL": "https://github.com/heestand-xyz/CoreGraphicsExtensions",
7 | "state": {
8 | "branch": null,
9 | "revision": "a40dc230306daacb410f9a2f36f63a811ea2e88e",
10 | "version": "1.2.1"
11 | }
12 | },
13 | {
14 | "package": "PixelColor",
15 | "repositoryURL": "https://github.com/heestand-xyz/PixelColor",
16 | "state": {
17 | "branch": null,
18 | "revision": "23851e7ea5f2dc844dd6d9a4d730864199583921",
19 | "version": "1.3.2"
20 | }
21 | },
22 | {
23 | "package": "Resolution",
24 | "repositoryURL": "https://github.com/heestand-xyz/Resolution",
25 | "state": {
26 | "branch": null,
27 | "revision": "0fdee03d7b312f075897cdac437f366cc6631d6a",
28 | "version": "1.0.4"
29 | }
30 | },
31 | {
32 | "package": "TextureMap",
33 | "repositoryURL": "https://github.com/heestand-xyz/TextureMap",
34 | "state": {
35 | "branch": null,
36 | "revision": "34781125ed9bb07744ca7d63fc4ce75334d09aa0",
37 | "version": "0.3.0"
38 | }
39 | }
40 | ]
41 | },
42 | "version": 1
43 | }
44 |
--------------------------------------------------------------------------------