├── Tests └── SwiftyCreativesTests │ ├── Resources │ ├── SampleObject.mtl │ ├── Media.xcassets │ │ ├── Contents.json │ │ └── sampleImage.imageset │ │ │ ├── sampleImage.png │ │ │ └── Contents.json │ └── SampleObjectWithTexture.mtl │ └── SnapshotTests │ ├── Functions │ ├── __Snapshots__ │ │ ├── SVGTests │ │ │ └── testSVGIsDrawed.1.png │ │ └── SketchForTest │ │ │ ├── testBoxIsDrawed.1.png │ │ │ ├── testImgIsDrawed.1.png │ │ │ ├── testBoxVariations.1.png │ │ │ ├── testFogIsWorking.1.png │ │ │ ├── testLineIsDrawed.1.png │ │ │ ├── testMeshIsDrawed.1.png │ │ │ ├── testModelIsDrawed.1.png │ │ │ ├── testRectIsDrawed.1.png │ │ │ ├── testBoldLineIsDrawed.1.png │ │ │ ├── testBoxColorWorking.1.png │ │ │ ├── testCircleIsDrawed.1.png │ │ │ ├── testColorIsWorking.1.png │ │ │ ├── testLineColorWorking.1.png │ │ │ ├── testPushPopWorking.1.png │ │ │ ├── testRectColorWorking.1.png │ │ │ ├── testRectPosWorking.1.png │ │ │ ├── testText2DGradient.1.png │ │ │ ├── testText2DIsDrawed.1.png │ │ │ ├── testText3DIsDrawed.1.png │ │ │ ├── testTriangleIsDrawed.1.png │ │ │ ├── testBoldLineVariations.1.png │ │ │ ├── testBoxRotationWorking.1.png │ │ │ ├── testCircleColorWorking.1.png │ │ │ ├── testText3DRawIsDrawed.1.png │ │ │ ├── testTrianglePosWorking.1.png │ │ │ ├── testBoldLineColorWorking.1.png │ │ │ ├── testMeshWithVertexColor.1.png │ │ │ ├── testNestedPushPopWorking.1.png │ │ │ ├── testTextFactoryIsWorking.1.png │ │ │ ├── testTexturedMeshIsDrawed.1.png │ │ │ ├── testTriangleColorWorking.1.png │ │ │ ├── testBoldLineWithVertexColor.1.png │ │ │ ├── testCircleParameterWorking.1.png │ │ │ ├── testHitTestableBoxIsDrawed.1.png │ │ │ ├── testHitTestableImgIsDrawed.1.png │ │ │ ├── testModelIsDrawedWithTexture.1.png │ │ │ └── testModelIsDrawedWithCustomTexture.1.png │ ├── ColorTests.swift │ ├── HitTestableBoxTests.swift │ ├── ImgTests.swift │ ├── HitTestableImgTests.swift │ ├── LineTests.swift │ ├── SketchForTest.swift │ ├── FogTests.swift │ ├── PushPopTests.swift │ ├── RectTests.swift │ ├── CircleTests.swift │ ├── TriangleTests.swift │ ├── BoxTests.swift │ ├── BoldLineTests.swift │ └── SVGTests.swift │ ├── Renderers │ └── __Snapshots__ │ │ ├── AddRendererTests │ │ └── testAddRendering.1.png │ │ ├── NormalRendererTests │ │ └── testNormalRendering.1.png │ │ └── TransparentRendererTests │ │ └── testTransparentRendering.1.png │ └── SnapshotTestUtil.swift ├── Examples ├── ExampleVisionOS │ ├── Packages │ │ └── RealityKitContent │ │ │ ├── README.md │ │ │ ├── Sources │ │ │ └── RealityKitContent │ │ │ │ ├── RealityKitContent.swift │ │ │ │ └── RealityKitContent.rkassets │ │ │ │ ├── Immersive.usda │ │ │ │ └── Scene.usda │ │ │ ├── Package.realitycomposerpro │ │ │ ├── WorkspaceData │ │ │ │ └── Settings.rcprojectdata │ │ │ └── ProjectData │ │ │ │ └── main.json │ │ │ └── Package.swift │ ├── ExampleVisionOS │ │ ├── Assets.xcassets │ │ │ ├── Contents.json │ │ │ ├── AppIcon.solidimagestack │ │ │ │ ├── Back.solidimagestacklayer │ │ │ │ │ ├── Contents.json │ │ │ │ │ └── Content.imageset │ │ │ │ │ │ └── Contents.json │ │ │ │ ├── Front.solidimagestacklayer │ │ │ │ │ ├── Contents.json │ │ │ │ │ └── Content.imageset │ │ │ │ │ │ └── Contents.json │ │ │ │ ├── Middle.solidimagestacklayer │ │ │ │ │ ├── Contents.json │ │ │ │ │ └── Content.imageset │ │ │ │ │ │ └── Contents.json │ │ │ │ └── Contents.json │ │ │ ├── AccentColor.colorset │ │ │ │ └── Contents.json │ │ │ └── ColorMap.textureset │ │ │ │ ├── Universal.mipmapset │ │ │ │ ├── ColorMap.png │ │ │ │ └── Contents.json │ │ │ │ └── Contents.json │ │ ├── Preview Content │ │ │ └── Preview Assets.xcassets │ │ │ │ └── Contents.json │ │ ├── Info.plist │ │ ├── SampleSketch.swift │ │ ├── ExampleVisionOSApp.swift │ │ └── ContentView.swift │ └── ExampleVisionOS.xcodeproj │ │ └── project.xcworkspace │ │ ├── contents.xcworkspacedata │ │ └── xcshareddata │ │ ├── IDEWorkspaceChecks.plist │ │ └── swiftpm │ │ └── Package.resolved ├── ExampleiOSApp │ ├── ExampleiOSApp │ │ ├── Assets.xcassets │ │ │ ├── Contents.json │ │ │ ├── AccentColor.colorset │ │ │ │ └── Contents.json │ │ │ └── AppIcon.appiconset │ │ │ │ └── Contents.json │ │ ├── Preview Content │ │ │ └── Preview Assets.xcassets │ │ │ │ └── Contents.json │ │ ├── ExampleiOSApp.entitlements │ │ ├── ExampleiOSApp.swift │ │ ├── SketchSample1.swift │ │ ├── SketchSample2.swift │ │ └── TestView.xib │ └── ExampleiOSApp.xcodeproj │ │ └── project.xcworkspace │ │ ├── contents.xcworkspacedata │ │ └── xcshareddata │ │ ├── IDEWorkspaceChecks.plist │ │ └── swiftpm │ │ └── Package.resolved └── ExampleMacOSApp │ ├── ExampleMacOSApp │ ├── Assets.xcassets │ │ ├── Contents.json │ │ ├── apple.imageset │ │ │ ├── Image.png │ │ │ └── Contents.json │ │ ├── AccentColor.colorset │ │ │ └── Contents.json │ │ └── AppIcon.appiconset │ │ │ └── Contents.json │ ├── Preview Content │ │ └── Preview Assets.xcassets │ │ │ └── Contents.json │ ├── sphere.mtl │ ├── ExampleMacOSApp.entitlements │ ├── Samples │ │ ├── Sample3.swift │ │ ├── Sample2.swift │ │ ├── Sample6.swift │ │ ├── Sample1.swift │ │ ├── Sample8.swift │ │ ├── Sample4.swift │ │ ├── Sample9.swift │ │ ├── Sample7.swift │ │ ├── Sample10.swift │ │ └── Sample5.swift │ ├── ExampleMacOSApp.swift │ └── FeatureExamples │ │ ├── Feature1.swift │ │ └── Feature2.swift │ └── ExampleMacOSApp.xcodeproj │ └── project.xcworkspace │ ├── contents.xcworkspacedata │ └── xcshareddata │ ├── IDEWorkspaceChecks.plist │ └── swiftpm │ └── Package.resolved ├── .gitignore ├── Sources ├── SwiftyCreativesMacro │ ├── Utils │ │ └── String+Error.swift │ ├── SwiftyCreativesMacroPlugin.swift │ └── SketchObject │ │ └── SketchObject.swift ├── SwiftyCreatives │ ├── Extensions │ │ ├── Bool+.swift │ │ ├── Numeric+.swift │ │ ├── Float+.swift │ │ ├── MTLCommandBuffer+.swift │ │ ├── MTLTexture+.swift │ │ ├── UIView+.swift │ │ ├── Comparable+.swift │ │ ├── CGPoint+.swift │ │ └── CGSize+.swift │ ├── Primitives │ │ ├── Texts │ │ │ ├── Text │ │ │ │ ├── TextBufferCreationError.swift │ │ │ │ └── Text2D.swift │ │ │ ├── LetterCache.swift │ │ │ └── Factory │ │ │ │ └── TextFactory.swift │ │ ├── PrimitiveUtils │ │ │ ├── Abstract │ │ │ │ ├── ImageAdjustOption.swift │ │ │ │ ├── ScaleSettable.swift │ │ │ │ ├── ImageAdjuster.swift │ │ │ │ ├── HitTestablePrimitive.swift │ │ │ │ ├── RectanglePlanePrimitive.swift │ │ │ │ └── ImageLoadable.swift │ │ │ ├── Info │ │ │ │ ├── PrimitiveInfo.swift │ │ │ │ ├── ModelObjectInfo.swift │ │ │ │ ├── TriangleInfo.swift │ │ │ │ └── RectShapeInfo.swift │ │ │ └── Primitive.swift │ │ ├── HitTestables │ │ │ ├── HitTestableRect.swift │ │ │ ├── HitTestableImg.swift │ │ │ └── UIViewObject.swift │ │ └── Basics │ │ │ └── Img.swift │ ├── Sketch │ │ ├── FunctionBase │ │ │ ├── FunctionBase+Fog.swift │ │ │ ├── FunctionBase+PushPop.swift │ │ │ ├── FunctionBase+HitTestableImg.swift │ │ │ ├── FunctionBase+Translate.swift │ │ │ ├── FunctionBase+Line.swift │ │ │ ├── FunctionBase+Color.swift │ │ │ ├── FunctionBase+Scale.swift │ │ │ ├── FunctionBase+Box.swift │ │ │ ├── FunctionBase+ModelObject.swift │ │ │ ├── FunctionBase+Circle.swift │ │ │ ├── FunctionBase+SVG.swift │ │ │ ├── FunctionBase+Img.swift │ │ │ ├── FunctionBase+Rotate.swift │ │ │ ├── FunctionBase+HitTestableBox.swift │ │ │ ├── FunctionBase+Triangle.swift │ │ │ └── FunctionBase.swift │ │ ├── SCPacket.swift │ │ └── SketchFunctions │ │ │ └── Sketch+UtilFunctions.swift │ ├── Utils │ │ ├── EXPORTER.swift │ │ ├── AssetUtil.swift │ │ ├── DefaultBuffers.swift │ │ ├── SharedIndices.swift │ │ ├── Alias.swift │ │ └── BlendMode.swift │ ├── DrawUtils │ │ └── Config │ │ │ ├── DrawConfig.swift │ │ │ └── DefaultDrawConfig.swift │ ├── Macros │ │ ├── SketchObjectHasDraw.swift │ │ └── SketchObject.swift │ ├── Renderers │ │ ├── Base │ │ │ ├── RendererBase+Functions.swift │ │ │ └── RendererBase.swift │ │ └── visionOS │ │ │ └── visionOSSettings.swift │ ├── GeometryPresets │ │ └── Box.swift │ ├── Views │ │ ├── TouchableMTKView │ │ │ ├── TouchableMTKView+Deinitializer.swift │ │ │ ├── TouchableMTKView+Utils.swift │ │ │ ├── TouchableMTKView+Initializer.swift │ │ │ └── TouchableMTKView+Configure.swift │ │ ├── KitSketchView.swift │ │ └── SketchView.swift │ ├── Camera │ │ ├── EasyCameraType.swift │ │ └── Config │ │ │ ├── DefaultOrthographicConfig.swift │ │ │ ├── DefaultPerspectiveConfig.swift │ │ │ └── CameraConfig.swift │ ├── ShaderUtils │ │ └── ShaderCore.swift │ ├── Resources │ │ └── Shaders │ │ │ ├── SharedIndices.h │ │ │ └── Types.metal │ ├── PropertyWrappers │ │ └── SCAnimatable.swift │ └── PostProcess │ │ └── Presets │ │ └── CornerRadiusPostProcessor.swift └── SwiftyCreativesSound │ ├── FFT │ ├── FFTNoiseExtractionMethod.swift │ ├── FFTBandCalculationMethod.swift │ ├── TempiFFTWindowType.swift │ └── FFTResultComponent.swift │ ├── AudioCapturer.swift │ ├── FFTVisualizer.swift │ └── Utils │ └── NoiseExtractor.swift ├── SwiftyCreatives.xcworkspace ├── xcshareddata │ ├── IDEWorkspaceChecks.plist │ └── swiftpm │ │ └── Package.resolved └── contents.xcworkspacedata ├── .swiftpm └── xcode │ └── xcshareddata │ └── xcschemes │ ├── SwiftyCreativesTests.xcscheme │ └── SwiftyCreatives.xcscheme └── Package.resolved /Tests/SwiftyCreativesTests/Resources/SampleObject.mtl: -------------------------------------------------------------------------------- 1 | # Blender 3.4.1 MTL File: 'None' 2 | # www.blender.org 3 | -------------------------------------------------------------------------------- /Examples/ExampleVisionOS/Packages/RealityKitContent/README.md: -------------------------------------------------------------------------------- 1 | # RealityKitContent 2 | 3 | A description of this package. -------------------------------------------------------------------------------- /Examples/ExampleiOSApp/ExampleiOSApp/Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "author" : "xcode", 4 | "version" : 1 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /Tests/SwiftyCreativesTests/Resources/Media.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "author" : "xcode", 4 | "version" : 1 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /Examples/ExampleMacOSApp/ExampleMacOSApp/Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "author" : "xcode", 4 | "version" : 1 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /Examples/ExampleVisionOS/ExampleVisionOS/Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "author" : "xcode", 4 | "version" : 1 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /Examples/ExampleiOSApp/ExampleiOSApp/Preview Content/Preview Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "author" : "xcode", 4 | "version" : 1 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /Examples/ExampleMacOSApp/ExampleMacOSApp/Preview Content/Preview Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "author" : "xcode", 4 | "version" : 1 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /Examples/ExampleVisionOS/ExampleVisionOS/Preview Content/Preview Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "author" : "xcode", 4 | "version" : 1 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | /.build 3 | /Packages 4 | /*.xcodeproj 5 | xcuserdata/ 6 | DerivedData/ 7 | .swiftpm/config/registries.json 8 | .swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata 9 | .netrc -------------------------------------------------------------------------------- /Examples/ExampleVisionOS/ExampleVisionOS/Assets.xcassets/AppIcon.solidimagestack/Back.solidimagestacklayer/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "author" : "xcode", 4 | "version" : 1 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /Examples/ExampleVisionOS/ExampleVisionOS/Assets.xcassets/AppIcon.solidimagestack/Front.solidimagestacklayer/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "author" : "xcode", 4 | "version" : 1 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /Examples/ExampleVisionOS/ExampleVisionOS/Assets.xcassets/AppIcon.solidimagestack/Middle.solidimagestacklayer/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "author" : "xcode", 4 | "version" : 1 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /Examples/ExampleMacOSApp/ExampleMacOSApp/Assets.xcassets/apple.imageset/Image.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yukiny0811/swifty-creatives/HEAD/Examples/ExampleMacOSApp/ExampleMacOSApp/Assets.xcassets/apple.imageset/Image.png -------------------------------------------------------------------------------- /Sources/SwiftyCreativesMacro/Utils/String+Error.swift: -------------------------------------------------------------------------------- 1 | // 2 | // File.swift 3 | // 4 | // 5 | // Created by Yuki Kuwashima on 2024/02/04. 6 | // 7 | 8 | import Foundation 9 | 10 | extension String: Error {} 11 | -------------------------------------------------------------------------------- /Examples/ExampleVisionOS/Packages/RealityKitContent/Sources/RealityKitContent/RealityKitContent.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | /// Bundle for the RealityKitContent project 4 | public let realityKitContentBundle = Bundle.module 5 | -------------------------------------------------------------------------------- /Examples/ExampleMacOSApp/ExampleMacOSApp/sphere.mtl: -------------------------------------------------------------------------------- 1 | # Blender 3.4.1 MTL File: 'None' 2 | # www.blender.org 3 | 4 | newmtl _texture 5 | Kd 1.00 1.00 1.00 6 | Ka 0.00 0.00 0.00 7 | Tf 1.00 1.00 1.00 8 | Ni 1.00 9 | map_Kd apple 10 | -------------------------------------------------------------------------------- /Tests/SwiftyCreativesTests/Resources/Media.xcassets/sampleImage.imageset/sampleImage.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yukiny0811/swifty-creatives/HEAD/Tests/SwiftyCreativesTests/Resources/Media.xcassets/sampleImage.imageset/sampleImage.png -------------------------------------------------------------------------------- /Tests/SwiftyCreativesTests/SnapshotTests/Functions/__Snapshots__/SVGTests/testSVGIsDrawed.1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yukiny0811/swifty-creatives/HEAD/Tests/SwiftyCreativesTests/SnapshotTests/Functions/__Snapshots__/SVGTests/testSVGIsDrawed.1.png -------------------------------------------------------------------------------- /Examples/ExampleMacOSApp/ExampleMacOSApp.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /Examples/ExampleVisionOS/ExampleVisionOS.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /Examples/ExampleiOSApp/ExampleiOSApp.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /Tests/SwiftyCreativesTests/SnapshotTests/Functions/__Snapshots__/SketchForTest/testBoxIsDrawed.1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yukiny0811/swifty-creatives/HEAD/Tests/SwiftyCreativesTests/SnapshotTests/Functions/__Snapshots__/SketchForTest/testBoxIsDrawed.1.png -------------------------------------------------------------------------------- /Tests/SwiftyCreativesTests/SnapshotTests/Functions/__Snapshots__/SketchForTest/testImgIsDrawed.1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yukiny0811/swifty-creatives/HEAD/Tests/SwiftyCreativesTests/SnapshotTests/Functions/__Snapshots__/SketchForTest/testImgIsDrawed.1.png -------------------------------------------------------------------------------- /Examples/ExampleiOSApp/ExampleiOSApp/Assets.xcassets/AccentColor.colorset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "colors" : [ 3 | { 4 | "idiom" : "universal" 5 | } 6 | ], 7 | "info" : { 8 | "author" : "xcode", 9 | "version" : 1 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /Tests/SwiftyCreativesTests/SnapshotTests/Functions/__Snapshots__/SketchForTest/testBoxVariations.1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yukiny0811/swifty-creatives/HEAD/Tests/SwiftyCreativesTests/SnapshotTests/Functions/__Snapshots__/SketchForTest/testBoxVariations.1.png -------------------------------------------------------------------------------- /Tests/SwiftyCreativesTests/SnapshotTests/Functions/__Snapshots__/SketchForTest/testFogIsWorking.1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yukiny0811/swifty-creatives/HEAD/Tests/SwiftyCreativesTests/SnapshotTests/Functions/__Snapshots__/SketchForTest/testFogIsWorking.1.png -------------------------------------------------------------------------------- /Tests/SwiftyCreativesTests/SnapshotTests/Functions/__Snapshots__/SketchForTest/testLineIsDrawed.1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yukiny0811/swifty-creatives/HEAD/Tests/SwiftyCreativesTests/SnapshotTests/Functions/__Snapshots__/SketchForTest/testLineIsDrawed.1.png -------------------------------------------------------------------------------- /Tests/SwiftyCreativesTests/SnapshotTests/Functions/__Snapshots__/SketchForTest/testMeshIsDrawed.1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yukiny0811/swifty-creatives/HEAD/Tests/SwiftyCreativesTests/SnapshotTests/Functions/__Snapshots__/SketchForTest/testMeshIsDrawed.1.png -------------------------------------------------------------------------------- /Tests/SwiftyCreativesTests/SnapshotTests/Functions/__Snapshots__/SketchForTest/testModelIsDrawed.1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yukiny0811/swifty-creatives/HEAD/Tests/SwiftyCreativesTests/SnapshotTests/Functions/__Snapshots__/SketchForTest/testModelIsDrawed.1.png -------------------------------------------------------------------------------- /Tests/SwiftyCreativesTests/SnapshotTests/Functions/__Snapshots__/SketchForTest/testRectIsDrawed.1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yukiny0811/swifty-creatives/HEAD/Tests/SwiftyCreativesTests/SnapshotTests/Functions/__Snapshots__/SketchForTest/testRectIsDrawed.1.png -------------------------------------------------------------------------------- /Examples/ExampleMacOSApp/ExampleMacOSApp/Assets.xcassets/AccentColor.colorset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "colors" : [ 3 | { 4 | "idiom" : "universal" 5 | } 6 | ], 7 | "info" : { 8 | "author" : "xcode", 9 | "version" : 1 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /Examples/ExampleVisionOS/ExampleVisionOS/Assets.xcassets/AccentColor.colorset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "colors" : [ 3 | { 4 | "idiom" : "universal" 5 | } 6 | ], 7 | "info" : { 8 | "author" : "xcode", 9 | "version" : 1 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /Tests/SwiftyCreativesTests/SnapshotTests/Functions/__Snapshots__/SketchForTest/testBoldLineIsDrawed.1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yukiny0811/swifty-creatives/HEAD/Tests/SwiftyCreativesTests/SnapshotTests/Functions/__Snapshots__/SketchForTest/testBoldLineIsDrawed.1.png -------------------------------------------------------------------------------- /Tests/SwiftyCreativesTests/SnapshotTests/Functions/__Snapshots__/SketchForTest/testBoxColorWorking.1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yukiny0811/swifty-creatives/HEAD/Tests/SwiftyCreativesTests/SnapshotTests/Functions/__Snapshots__/SketchForTest/testBoxColorWorking.1.png -------------------------------------------------------------------------------- /Tests/SwiftyCreativesTests/SnapshotTests/Functions/__Snapshots__/SketchForTest/testCircleIsDrawed.1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yukiny0811/swifty-creatives/HEAD/Tests/SwiftyCreativesTests/SnapshotTests/Functions/__Snapshots__/SketchForTest/testCircleIsDrawed.1.png -------------------------------------------------------------------------------- /Tests/SwiftyCreativesTests/SnapshotTests/Functions/__Snapshots__/SketchForTest/testColorIsWorking.1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yukiny0811/swifty-creatives/HEAD/Tests/SwiftyCreativesTests/SnapshotTests/Functions/__Snapshots__/SketchForTest/testColorIsWorking.1.png -------------------------------------------------------------------------------- /Tests/SwiftyCreativesTests/SnapshotTests/Functions/__Snapshots__/SketchForTest/testLineColorWorking.1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yukiny0811/swifty-creatives/HEAD/Tests/SwiftyCreativesTests/SnapshotTests/Functions/__Snapshots__/SketchForTest/testLineColorWorking.1.png -------------------------------------------------------------------------------- /Tests/SwiftyCreativesTests/SnapshotTests/Functions/__Snapshots__/SketchForTest/testPushPopWorking.1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yukiny0811/swifty-creatives/HEAD/Tests/SwiftyCreativesTests/SnapshotTests/Functions/__Snapshots__/SketchForTest/testPushPopWorking.1.png -------------------------------------------------------------------------------- /Tests/SwiftyCreativesTests/SnapshotTests/Functions/__Snapshots__/SketchForTest/testRectColorWorking.1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yukiny0811/swifty-creatives/HEAD/Tests/SwiftyCreativesTests/SnapshotTests/Functions/__Snapshots__/SketchForTest/testRectColorWorking.1.png -------------------------------------------------------------------------------- /Tests/SwiftyCreativesTests/SnapshotTests/Functions/__Snapshots__/SketchForTest/testRectPosWorking.1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yukiny0811/swifty-creatives/HEAD/Tests/SwiftyCreativesTests/SnapshotTests/Functions/__Snapshots__/SketchForTest/testRectPosWorking.1.png -------------------------------------------------------------------------------- /Tests/SwiftyCreativesTests/SnapshotTests/Functions/__Snapshots__/SketchForTest/testText2DGradient.1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yukiny0811/swifty-creatives/HEAD/Tests/SwiftyCreativesTests/SnapshotTests/Functions/__Snapshots__/SketchForTest/testText2DGradient.1.png -------------------------------------------------------------------------------- /Tests/SwiftyCreativesTests/SnapshotTests/Functions/__Snapshots__/SketchForTest/testText2DIsDrawed.1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yukiny0811/swifty-creatives/HEAD/Tests/SwiftyCreativesTests/SnapshotTests/Functions/__Snapshots__/SketchForTest/testText2DIsDrawed.1.png -------------------------------------------------------------------------------- /Tests/SwiftyCreativesTests/SnapshotTests/Functions/__Snapshots__/SketchForTest/testText3DIsDrawed.1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yukiny0811/swifty-creatives/HEAD/Tests/SwiftyCreativesTests/SnapshotTests/Functions/__Snapshots__/SketchForTest/testText3DIsDrawed.1.png -------------------------------------------------------------------------------- /Tests/SwiftyCreativesTests/SnapshotTests/Functions/__Snapshots__/SketchForTest/testTriangleIsDrawed.1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yukiny0811/swifty-creatives/HEAD/Tests/SwiftyCreativesTests/SnapshotTests/Functions/__Snapshots__/SketchForTest/testTriangleIsDrawed.1.png -------------------------------------------------------------------------------- /Tests/SwiftyCreativesTests/SnapshotTests/Renderers/__Snapshots__/AddRendererTests/testAddRendering.1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yukiny0811/swifty-creatives/HEAD/Tests/SwiftyCreativesTests/SnapshotTests/Renderers/__Snapshots__/AddRendererTests/testAddRendering.1.png -------------------------------------------------------------------------------- /Tests/SwiftyCreativesTests/SnapshotTests/Functions/__Snapshots__/SketchForTest/testBoldLineVariations.1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yukiny0811/swifty-creatives/HEAD/Tests/SwiftyCreativesTests/SnapshotTests/Functions/__Snapshots__/SketchForTest/testBoldLineVariations.1.png -------------------------------------------------------------------------------- /Tests/SwiftyCreativesTests/SnapshotTests/Functions/__Snapshots__/SketchForTest/testBoxRotationWorking.1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yukiny0811/swifty-creatives/HEAD/Tests/SwiftyCreativesTests/SnapshotTests/Functions/__Snapshots__/SketchForTest/testBoxRotationWorking.1.png -------------------------------------------------------------------------------- /Tests/SwiftyCreativesTests/SnapshotTests/Functions/__Snapshots__/SketchForTest/testCircleColorWorking.1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yukiny0811/swifty-creatives/HEAD/Tests/SwiftyCreativesTests/SnapshotTests/Functions/__Snapshots__/SketchForTest/testCircleColorWorking.1.png -------------------------------------------------------------------------------- /Tests/SwiftyCreativesTests/SnapshotTests/Functions/__Snapshots__/SketchForTest/testText3DRawIsDrawed.1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yukiny0811/swifty-creatives/HEAD/Tests/SwiftyCreativesTests/SnapshotTests/Functions/__Snapshots__/SketchForTest/testText3DRawIsDrawed.1.png -------------------------------------------------------------------------------- /Tests/SwiftyCreativesTests/SnapshotTests/Functions/__Snapshots__/SketchForTest/testTrianglePosWorking.1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yukiny0811/swifty-creatives/HEAD/Tests/SwiftyCreativesTests/SnapshotTests/Functions/__Snapshots__/SketchForTest/testTrianglePosWorking.1.png -------------------------------------------------------------------------------- /Examples/ExampleVisionOS/ExampleVisionOS/Assets.xcassets/ColorMap.textureset/Universal.mipmapset/ColorMap.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yukiny0811/swifty-creatives/HEAD/Examples/ExampleVisionOS/ExampleVisionOS/Assets.xcassets/ColorMap.textureset/Universal.mipmapset/ColorMap.png -------------------------------------------------------------------------------- /Sources/SwiftyCreatives/Extensions/Bool+.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Bool+.swift 3 | // 4 | // 5 | // Created by Yuki Kuwashima on 2023/02/10. 6 | // 7 | 8 | extension Bool { 9 | static var memorySize: Int { 10 | return MemoryLayout.stride 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /Tests/SwiftyCreativesTests/SnapshotTests/Functions/__Snapshots__/SketchForTest/testBoldLineColorWorking.1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yukiny0811/swifty-creatives/HEAD/Tests/SwiftyCreativesTests/SnapshotTests/Functions/__Snapshots__/SketchForTest/testBoldLineColorWorking.1.png -------------------------------------------------------------------------------- /Tests/SwiftyCreativesTests/SnapshotTests/Functions/__Snapshots__/SketchForTest/testMeshWithVertexColor.1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yukiny0811/swifty-creatives/HEAD/Tests/SwiftyCreativesTests/SnapshotTests/Functions/__Snapshots__/SketchForTest/testMeshWithVertexColor.1.png -------------------------------------------------------------------------------- /Tests/SwiftyCreativesTests/SnapshotTests/Functions/__Snapshots__/SketchForTest/testNestedPushPopWorking.1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yukiny0811/swifty-creatives/HEAD/Tests/SwiftyCreativesTests/SnapshotTests/Functions/__Snapshots__/SketchForTest/testNestedPushPopWorking.1.png -------------------------------------------------------------------------------- /Tests/SwiftyCreativesTests/SnapshotTests/Functions/__Snapshots__/SketchForTest/testTextFactoryIsWorking.1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yukiny0811/swifty-creatives/HEAD/Tests/SwiftyCreativesTests/SnapshotTests/Functions/__Snapshots__/SketchForTest/testTextFactoryIsWorking.1.png -------------------------------------------------------------------------------- /Tests/SwiftyCreativesTests/SnapshotTests/Functions/__Snapshots__/SketchForTest/testTexturedMeshIsDrawed.1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yukiny0811/swifty-creatives/HEAD/Tests/SwiftyCreativesTests/SnapshotTests/Functions/__Snapshots__/SketchForTest/testTexturedMeshIsDrawed.1.png -------------------------------------------------------------------------------- /Tests/SwiftyCreativesTests/SnapshotTests/Functions/__Snapshots__/SketchForTest/testTriangleColorWorking.1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yukiny0811/swifty-creatives/HEAD/Tests/SwiftyCreativesTests/SnapshotTests/Functions/__Snapshots__/SketchForTest/testTriangleColorWorking.1.png -------------------------------------------------------------------------------- /Tests/SwiftyCreativesTests/SnapshotTests/Renderers/__Snapshots__/NormalRendererTests/testNormalRendering.1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yukiny0811/swifty-creatives/HEAD/Tests/SwiftyCreativesTests/SnapshotTests/Renderers/__Snapshots__/NormalRendererTests/testNormalRendering.1.png -------------------------------------------------------------------------------- /Tests/SwiftyCreativesTests/SnapshotTests/Functions/__Snapshots__/SketchForTest/testBoldLineWithVertexColor.1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yukiny0811/swifty-creatives/HEAD/Tests/SwiftyCreativesTests/SnapshotTests/Functions/__Snapshots__/SketchForTest/testBoldLineWithVertexColor.1.png -------------------------------------------------------------------------------- /Tests/SwiftyCreativesTests/SnapshotTests/Functions/__Snapshots__/SketchForTest/testCircleParameterWorking.1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yukiny0811/swifty-creatives/HEAD/Tests/SwiftyCreativesTests/SnapshotTests/Functions/__Snapshots__/SketchForTest/testCircleParameterWorking.1.png -------------------------------------------------------------------------------- /Tests/SwiftyCreativesTests/SnapshotTests/Functions/__Snapshots__/SketchForTest/testHitTestableBoxIsDrawed.1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yukiny0811/swifty-creatives/HEAD/Tests/SwiftyCreativesTests/SnapshotTests/Functions/__Snapshots__/SketchForTest/testHitTestableBoxIsDrawed.1.png -------------------------------------------------------------------------------- /Tests/SwiftyCreativesTests/SnapshotTests/Functions/__Snapshots__/SketchForTest/testHitTestableImgIsDrawed.1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yukiny0811/swifty-creatives/HEAD/Tests/SwiftyCreativesTests/SnapshotTests/Functions/__Snapshots__/SketchForTest/testHitTestableImgIsDrawed.1.png -------------------------------------------------------------------------------- /Sources/SwiftyCreatives/Extensions/Numeric+.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Numeric+.swift 3 | // 4 | // 5 | // Created by Yuki Kuwashima on 2023/05/16. 6 | // 7 | 8 | extension Numeric { 9 | static var memorySize: Int { 10 | return MemoryLayout.stride 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /Tests/SwiftyCreativesTests/SnapshotTests/Functions/__Snapshots__/SketchForTest/testModelIsDrawedWithTexture.1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yukiny0811/swifty-creatives/HEAD/Tests/SwiftyCreativesTests/SnapshotTests/Functions/__Snapshots__/SketchForTest/testModelIsDrawedWithTexture.1.png -------------------------------------------------------------------------------- /Sources/SwiftyCreatives/Primitives/Texts/Text/TextBufferCreationError.swift: -------------------------------------------------------------------------------- 1 | // 2 | // File.swift 3 | // 4 | // 5 | // Created by Yuki Kuwashima on 2024/02/04. 6 | // 7 | 8 | import Foundation 9 | 10 | enum TextBufferCreationError: LocalizedError { 11 | case noVertices 12 | } 13 | -------------------------------------------------------------------------------- /Tests/SwiftyCreativesTests/SnapshotTests/Functions/__Snapshots__/SketchForTest/testModelIsDrawedWithCustomTexture.1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yukiny0811/swifty-creatives/HEAD/Tests/SwiftyCreativesTests/SnapshotTests/Functions/__Snapshots__/SketchForTest/testModelIsDrawedWithCustomTexture.1.png -------------------------------------------------------------------------------- /Tests/SwiftyCreativesTests/SnapshotTests/Renderers/__Snapshots__/TransparentRendererTests/testTransparentRendering.1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yukiny0811/swifty-creatives/HEAD/Tests/SwiftyCreativesTests/SnapshotTests/Renderers/__Snapshots__/TransparentRendererTests/testTransparentRendering.1.png -------------------------------------------------------------------------------- /Sources/SwiftyCreativesSound/FFT/FFTNoiseExtractionMethod.swift: -------------------------------------------------------------------------------- 1 | // 2 | // File.swift 3 | // 4 | // 5 | // Created by Yuki Kuwashima on 2023/07/04. 6 | // 7 | 8 | public enum FFTNoiseExtractionMethod { 9 | case none 10 | case freqDomain(Double) 11 | case timeDomain(Double) 12 | } 13 | -------------------------------------------------------------------------------- /Sources/SwiftyCreativesSound/FFT/FFTBandCalculationMethod.swift: -------------------------------------------------------------------------------- 1 | // 2 | // File.swift 3 | // 4 | // 5 | // Created by Yuki Kuwashima on 2023/07/05. 6 | // 7 | 8 | public enum FFTBandCalculationMethod { 9 | case linear(Int) //bandCount 10 | case logarithmic(Int) //bandCountPerOctave 11 | } 12 | -------------------------------------------------------------------------------- /Examples/ExampleiOSApp/ExampleiOSApp/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "platform" : "ios", 6 | "size" : "1024x1024" 7 | } 8 | ], 9 | "info" : { 10 | "author" : "xcode", 11 | "version" : 1 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /Examples/ExampleVisionOS/ExampleVisionOS/Assets.xcassets/ColorMap.textureset/Universal.mipmapset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "version" : 1, 4 | "author" : "xcode" 5 | }, 6 | "levels" : [ 7 | { 8 | "filename" : "ColorMap.png", 9 | "mipmap-level" : "base" 10 | } 11 | ] 12 | } 13 | -------------------------------------------------------------------------------- /Sources/SwiftyCreatives/Primitives/PrimitiveUtils/Abstract/ImageAdjustOption.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ImageAdjustOption.swift 3 | // 4 | // 5 | // Created by Yuki Kuwashima on 2023/03/03. 6 | // 7 | 8 | public enum ImageAdjustOption { 9 | case basedOnWidth 10 | case basedOnHeight 11 | case basedOnLonger 12 | } 13 | -------------------------------------------------------------------------------- /Examples/ExampleiOSApp/ExampleiOSApp/ExampleiOSApp.entitlements: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | com.apple.security.app-sandbox 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /SwiftyCreatives.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /Examples/ExampleVisionOS/ExampleVisionOS/Assets.xcassets/AppIcon.solidimagestack/Back.solidimagestacklayer/Content.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "vision", 5 | "scale" : "2x" 6 | } 7 | ], 8 | "info" : { 9 | "author" : "xcode", 10 | "version" : 1 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /Examples/ExampleVisionOS/ExampleVisionOS/Assets.xcassets/AppIcon.solidimagestack/Front.solidimagestacklayer/Content.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "vision", 5 | "scale" : "2x" 6 | } 7 | ], 8 | "info" : { 9 | "author" : "xcode", 10 | "version" : 1 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /Examples/ExampleVisionOS/ExampleVisionOS/Assets.xcassets/AppIcon.solidimagestack/Middle.solidimagestacklayer/Content.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "vision", 5 | "scale" : "2x" 6 | } 7 | ], 8 | "info" : { 9 | "author" : "xcode", 10 | "version" : 1 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /Sources/SwiftyCreatives/Primitives/PrimitiveUtils/Abstract/ScaleSettable.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ScaleSettable.swift 3 | // 4 | // 5 | // Created by Yuki Kuwashima on 2023/03/03. 6 | // 7 | 8 | public protocol ScaleSettable: AnyObject { 9 | var scale: f3 { get } 10 | @discardableResult func setScale(_ value: f3) -> Self 11 | } 12 | -------------------------------------------------------------------------------- /Sources/SwiftyCreatives/Primitives/HitTestables/HitTestableRect.swift: -------------------------------------------------------------------------------- 1 | // 2 | // HitTestableRect.swift 3 | // 4 | // 5 | // Created by Yuki Kuwashima on 2023/02/25. 6 | // 7 | 8 | import Metal 9 | 10 | open class HitTestableRect: RectanglePlanePrimitive { 11 | public override init() { 12 | super.init() 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /Sources/SwiftyCreativesSound/FFT/TempiFFTWindowType.swift: -------------------------------------------------------------------------------- 1 | // 2 | // File.swift 3 | // 4 | // 5 | // Created by Yuki Kuwashima on 2023/07/04. 6 | // 7 | // Original code from https://github.com/jscalo/tempi-fft (CC0 1.0 Universal) 8 | 9 | public enum TempiFFTWindowType: Int { 10 | case none 11 | case hanning 12 | case hamming 13 | } 14 | -------------------------------------------------------------------------------- /Examples/ExampleMacOSApp/ExampleMacOSApp.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /Examples/ExampleVisionOS/ExampleVisionOS.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /Examples/ExampleiOSApp/ExampleiOSApp.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /Tests/SwiftyCreativesTests/Resources/SampleObjectWithTexture.mtl: -------------------------------------------------------------------------------- 1 | # Blender 3.4.1 MTL File: 'None' 2 | # www.blender.org 3 | 4 | newmtl Material 5 | Ns 250.000000 6 | Ka 1.000000 1.000000 1.000000 7 | Kd 0.800000 0.087901 0.294495 8 | Ks 0.500000 0.500000 0.500000 9 | Ke 0.000000 0.000000 0.000000 10 | Ni 1.450000 11 | d 1.000000 12 | illum 2 13 | map_Kd sampleImage 14 | -------------------------------------------------------------------------------- /Sources/SwiftyCreatives/Sketch/FunctionBase/FunctionBase+Fog.swift: -------------------------------------------------------------------------------- 1 | // 2 | // FunctionBase+Fog.swift 3 | // 4 | // 5 | // Created by Yuki Kuwashima on 2023/03/08. 6 | // 7 | 8 | import simd 9 | import SimpleSimdSwift 10 | 11 | public extension FunctionBase { 12 | func setFog(color: f4, density: Float) { 13 | setFogDensity(density) 14 | setFogColor(color) 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /Sources/SwiftyCreatives/Extensions/Float+.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Float+.swift 3 | // 4 | // 5 | // Created by Yuki Kuwashima on 2023/01/31. 6 | // 7 | 8 | extension Float { 9 | public static func degreesToRadians(_ deg: Float) -> Self { 10 | return Self(deg / 360 * Float.pi * 2) 11 | } 12 | public func degreesToRadians() -> Self { 13 | return Self(self / 360 * Float.pi * 2) 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /Examples/ExampleVisionOS/ExampleVisionOS/Assets.xcassets/AppIcon.solidimagestack/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "author" : "xcode", 4 | "version" : 1 5 | }, 6 | "layers" : [ 7 | { 8 | "filename" : "Front.solidimagestacklayer" 9 | }, 10 | { 11 | "filename" : "Middle.solidimagestacklayer" 12 | }, 13 | { 14 | "filename" : "Back.solidimagestacklayer" 15 | } 16 | ] 17 | } 18 | -------------------------------------------------------------------------------- /Examples/ExampleVisionOS/ExampleVisionOS/Assets.xcassets/ColorMap.textureset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "version" : 1, 4 | "author" : "xcode" 5 | }, 6 | "properties" : { 7 | "origin" : "bottom-left", 8 | "interpretation" : "non-premultiplied-colors" 9 | }, 10 | "textures" : [ 11 | { 12 | "idiom" : "universal", 13 | "filename" : "Universal.mipmapset" 14 | } 15 | ] 16 | } 17 | 18 | -------------------------------------------------------------------------------- /Examples/ExampleVisionOS/Packages/RealityKitContent/Package.realitycomposerpro/WorkspaceData/Settings.rcprojectdata: -------------------------------------------------------------------------------- 1 | { 2 | "cameraPresets" : { 3 | 4 | }, 5 | "secondaryToolbarData" : { 6 | "isGridVisible" : true, 7 | "sceneReverbPreset" : -1 8 | }, 9 | "unitDefaults" : { 10 | "°" : "°", 11 | "kg" : "g", 12 | "m" : "cm", 13 | "m\/s" : "m\/s", 14 | "m\/s²" : "m\/s²", 15 | "s" : "s" 16 | } 17 | } -------------------------------------------------------------------------------- /Sources/SwiftyCreatives/Extensions/MTLCommandBuffer+.swift: -------------------------------------------------------------------------------- 1 | // 2 | // File.swift 3 | // 4 | // 5 | // Created by Yuki Kuwashima on 2024/02/06. 6 | // 7 | 8 | import Metal 9 | 10 | public extension MTLCommandBuffer { 11 | func compute(_ f: @escaping (MTLComputeCommandEncoder) -> ()) { 12 | let encoder = self.makeComputeCommandEncoder()! 13 | f(encoder) 14 | encoder.endEncoding() 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /Sources/SwiftyCreatives/Primitives/PrimitiveUtils/Info/PrimitiveInfo.swift: -------------------------------------------------------------------------------- 1 | // 2 | // PrimitiveInfo.swift 3 | // 4 | // 5 | // Created by Yuki Kuwashima on 2022/12/17. 6 | // 7 | 8 | import Metal 9 | 10 | public protocol PrimitiveInfo { 11 | static var primitiveType: MTLPrimitiveType { get } 12 | static var vertices: [f3] { get } 13 | static var uvs: [f2] { get } 14 | static var normals: [f3] { get } 15 | } 16 | -------------------------------------------------------------------------------- /Sources/SwiftyCreativesMacro/SwiftyCreativesMacroPlugin.swift: -------------------------------------------------------------------------------- 1 | // 2 | // File.swift 3 | // 4 | // 5 | // Created by Yuki Kuwashima on 2024/02/04. 6 | // 7 | 8 | import SwiftCompilerPlugin 9 | import SwiftSyntax 10 | import SwiftSyntaxBuilder 11 | import SwiftSyntaxMacros 12 | 13 | @main 14 | struct SwiftyCreativesMacroPlugin: CompilerPlugin { 15 | let providingMacros: [Macro.Type] = [ 16 | SketchObject.self 17 | ] 18 | } 19 | -------------------------------------------------------------------------------- /Sources/SwiftyCreatives/Utils/EXPORTER.swift: -------------------------------------------------------------------------------- 1 | // 2 | // File.swift 3 | // 4 | // 5 | // Created by Yuki Kuwashima on 2024/02/03. 6 | // 7 | 8 | @_exported import SimpleSimdSwift 9 | @_exported import MetalKit 10 | @_exported import SwiftyCreativesSound 11 | @_exported import EasyMetalShader 12 | @_exported import FontVertexBuilder 13 | @_exported import SVGVertexBuilder 14 | @_exported import VectorTriangulator 15 | @_exported import SwiftyCoreText 16 | -------------------------------------------------------------------------------- /Sources/SwiftyCreativesSound/FFT/FFTResultComponent.swift: -------------------------------------------------------------------------------- 1 | // 2 | // File.swift 3 | // 4 | // 5 | // Created by Yuki Kuwashima on 2023/07/04. 6 | // 7 | 8 | import Foundation 9 | 10 | public struct FFTResultComponent { 11 | 12 | public var frequency: Float 13 | public var magnitude: Float 14 | 15 | public init(frequency: Float, magnitude: Float) { 16 | self.frequency = frequency 17 | self.magnitude = magnitude 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /Sources/SwiftyCreatives/Sketch/SCPacket.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SCPacket.swift 3 | // 4 | // 5 | // Created by Yuki Kuwashima on 2023/02/28. 6 | // 7 | 8 | public class SCPacket: FunctionBase { 9 | public var privateEncoder: SCEncoder? 10 | public var customMatrix: [f4x4] = [] 11 | public init(privateEncoder: SCEncoder, customMatrix: f4x4) { 12 | self.privateEncoder = privateEncoder 13 | self.customMatrix.append(customMatrix) 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /Sources/SwiftyCreatives/Primitives/Basics/Img.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Img.swift 3 | // 4 | // 5 | // Created by Yuki Kuwashima on 2022/12/19. 6 | // 7 | 8 | import MetalKit 9 | 10 | open class Img: Primitive, ImageLoadable { 11 | public override init() { super.init() } 12 | public var texture: MTLTexture? 13 | 14 | @available(*, unavailable, message: "Use img() in Sketch instead.") 15 | override public func draw(_ encoder: SCEncoder) {} 16 | } 17 | -------------------------------------------------------------------------------- /Examples/ExampleMacOSApp/ExampleMacOSApp/Assets.xcassets/apple.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "Image.png", 5 | "idiom" : "universal", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "author" : "xcode", 19 | "version" : 1 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /Tests/SwiftyCreativesTests/Resources/Media.xcassets/sampleImage.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "sampleImage.png", 5 | "idiom" : "universal", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "author" : "xcode", 19 | "version" : 1 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /Sources/SwiftyCreatives/DrawUtils/Config/DrawConfig.swift: -------------------------------------------------------------------------------- 1 | // 2 | // File.swift 3 | // 4 | // 5 | // Created by Yuki Kuwashima on 2024/02/03. 6 | // 7 | 8 | /// Config for draw config. 9 | public class DrawConfig { 10 | 11 | public var contentScaleFactor: Int 12 | public var frameRate: Int 13 | 14 | init(contentScaleFactor: Int, frameRate: Int) { 15 | self.contentScaleFactor = contentScaleFactor 16 | self.frameRate = frameRate 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /Sources/SwiftyCreatives/Macros/SketchObjectHasDraw.swift: -------------------------------------------------------------------------------- 1 | // 2 | // File.swift 3 | // 4 | // 5 | // Created by Yuki Kuwashima on 2024/02/04. 6 | // 7 | 8 | import Foundation 9 | 10 | public protocol SketchObjectHasDraw: FunctionBase { 11 | func draw() 12 | } 13 | 14 | public extension SketchObjectHasDraw { 15 | func draw(encoder: SCEncoder, customMatrix: f4x4) { 16 | self.privateEncoder = encoder 17 | self.customMatrix = [customMatrix] 18 | draw() 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /Sources/SwiftyCreatives/Renderers/Base/RendererBase+Functions.swift: -------------------------------------------------------------------------------- 1 | // 2 | // RendererBase+Functions.swift 3 | // 4 | // 5 | // Created by Yuki Kuwashima on 2023/02/26. 6 | // 7 | 8 | import Foundation 9 | 10 | #if !os(visionOS) 11 | 12 | extension RendererBase { 13 | func calculateDeltaTime() { 14 | let date = Date() 15 | let elapsed: Float = Float(date.timeIntervalSince(savedDate)) 16 | drawProcess.deltaTime = elapsed 17 | savedDate = date 18 | } 19 | } 20 | 21 | #endif 22 | -------------------------------------------------------------------------------- /Examples/ExampleMacOSApp/ExampleMacOSApp/ExampleMacOSApp.entitlements: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | com.apple.security.app-sandbox 6 | 7 | com.apple.security.device.audio-input 8 | 9 | com.apple.security.device.camera 10 | 11 | com.apple.security.files.user-selected.read-only 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /Examples/ExampleiOSApp/ExampleiOSApp/ExampleiOSApp.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ExampleiOSApp.swift 3 | // ExampleiOSApp 4 | // 5 | // Created by Yuki Kuwashima on 2022/12/15. 6 | // 7 | 8 | import SwiftUI 9 | import SwiftyCreatives 10 | 11 | @main 12 | struct ExampleiOSApp: App { 13 | var body: some Scene { 14 | WindowGroup { 15 | VStack { 16 | SketchView(SketchSample1()) 17 | SketchView(SketchSample2()) 18 | } 19 | .background(.black) 20 | } 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /SwiftyCreatives.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 9 | 10 | 12 | 13 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /Sources/SwiftyCreatives/DrawUtils/Config/DefaultDrawConfig.swift: -------------------------------------------------------------------------------- 1 | // 2 | // MainDrawConfig.swift 3 | // 4 | // 5 | // Created by Yuki Kuwashima on 2022/12/09. 6 | // 7 | 8 | #if !os(visionOS) 9 | 10 | // Default draw config. 11 | public class DefaultDrawConfig: DrawConfig { 12 | public override init( 13 | contentScaleFactor: Int = 3, 14 | frameRate: Int = 120 15 | ) { 16 | super.init( 17 | contentScaleFactor: contentScaleFactor, 18 | frameRate: frameRate 19 | ) 20 | } 21 | } 22 | 23 | #endif 24 | -------------------------------------------------------------------------------- /Examples/ExampleVisionOS/ExampleVisionOS/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | UIApplicationSceneManifest 6 | 7 | UIApplicationPreferredDefaultSceneSessionRole 8 | UIWindowSceneSessionRoleApplication 9 | UIApplicationSupportsMultipleScenes 10 | 11 | UISceneConfigurations 12 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /Sources/SwiftyCreatives/Primitives/PrimitiveUtils/Info/ModelObjectInfo.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ModelObjectInfo.swift 3 | // 4 | // 5 | // Created by Yuki Kuwashima on 2023/02/25. 6 | // 7 | 8 | import Metal 9 | 10 | public struct ModelObjectInfo: PrimitiveInfo { 11 | public static var vertices: [f3] = [] 12 | public static var uvs: [f2] = [] 13 | public static var normals: [f3] = [] 14 | public static let vertexCount: Int = 0 15 | public static let primitiveType: MTLPrimitiveType = .triangleStrip 16 | public static let hasTexture: [Bool] = [true] 17 | } 18 | -------------------------------------------------------------------------------- /Sources/SwiftyCreatives/GeometryPresets/Box.swift: -------------------------------------------------------------------------------- 1 | // 2 | // File.swift 3 | // 4 | // 5 | // Created by Yuki Kuwashima on 2024/02/04. 6 | // 7 | 8 | import Foundation 9 | 10 | @SketchObject 11 | public class Box { 12 | 13 | public var pos: f3 14 | public var col: f4 15 | public var scale: f3 16 | 17 | public init(pos: f3, col: f4, scale: f3) { 18 | self.pos = pos 19 | self.col = col 20 | self.scale = scale 21 | } 22 | 23 | public func draw() { 24 | color(col) 25 | box(pos, scale) 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /Sources/SwiftyCreatives/Primitives/Texts/LetterCache.swift: -------------------------------------------------------------------------------- 1 | // 2 | // File.swift 3 | // 4 | // 5 | // Created by Yuki Kuwashima on 2024/02/03. 6 | // 7 | 8 | import Foundation 9 | import Metal 10 | import SimpleSimdSwift 11 | 12 | public struct LetterCache { 13 | public var buffer: MTLBuffer 14 | public var verticeCount: Int 15 | public var offset: simd_double2 16 | public var size: simd_double2 17 | } 18 | 19 | public struct LetterCacheRaw { 20 | public var vertices: [simd_double3] 21 | public var offset: simd_double2 22 | public var size: simd_double2 23 | } 24 | -------------------------------------------------------------------------------- /Sources/SwiftyCreatives/Views/TouchableMTKView/TouchableMTKView+Deinitializer.swift: -------------------------------------------------------------------------------- 1 | // 2 | // TouchableMTKView+Deinitializer.swift 3 | // 4 | // 5 | // Created by Yuki Kuwashima on 2023/05/16. 6 | // 7 | 8 | import MetalKit 9 | 10 | #if !os(visionOS) 11 | 12 | extension TouchableMTKView { 13 | func deinitView() { 14 | #if os(iOS) 15 | if let recognizers = self.gestureRecognizers { 16 | for recognizer in recognizers { 17 | self.removeGestureRecognizer(recognizer) 18 | } 19 | } 20 | #endif 21 | } 22 | } 23 | 24 | #endif 25 | -------------------------------------------------------------------------------- /Sources/SwiftyCreativesSound/AudioCapturer.swift: -------------------------------------------------------------------------------- 1 | // 2 | // File.swift 3 | // 4 | // 5 | // Created by Yuki Kuwashima on 2023/07/06. 6 | // 7 | 8 | import Foundation 9 | 10 | public protocol AudioCapturer { 11 | var fftResult: [FFTResultComponent] { get set } 12 | var fftNoiseExtractionMethod: FFTNoiseExtractionMethod { get set } 13 | var fftWindowType: TempiFFTWindowType { get set } 14 | var fftMinFreq: Float { get set } 15 | var fftMaxFreq: Float { get set } 16 | var bandCalculationMethod: FFTBandCalculationMethod { get set } 17 | func start() 18 | func stop() 19 | } 20 | -------------------------------------------------------------------------------- /Sources/SwiftyCreatives/Utils/AssetUtil.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AssetUtil.swift 3 | // 4 | // 5 | // Created by Yuki Kuwashima on 2022/12/19. 6 | // 7 | 8 | import MetalKit 9 | 10 | class AssetUtil { 11 | private static let defaultMTLTextureDescriptor: MTLTextureDescriptor = MTLTextureDescriptor.texture2DDescriptor( 12 | pixelFormat: MTLPixelFormat.bgra8Unorm, 13 | width: 1, 14 | height: 1, 15 | mipmapped: false 16 | ) 17 | static let defaultMTLTexture: MTLTexture = ShaderCore.device.makeTexture( 18 | descriptor: defaultMTLTextureDescriptor 19 | )! 20 | } 21 | -------------------------------------------------------------------------------- /Sources/SwiftyCreatives/Camera/EasyCameraType.swift: -------------------------------------------------------------------------------- 1 | // 2 | // EasyCameraType.swift 3 | // 4 | // 5 | // Created by Yuki Kuwashima on 2023/02/12. 6 | // 7 | 8 | /// Easy Camera T 9 | public enum EasyCameraType { 10 | 11 | /// Manual 3D Camera 12 | case manual 13 | 14 | /// Standard 3D Camera 15 | /// - Parameters: 16 | /// - polarSpacing: defines how much camera can move along y axis in the screen. set this number small to allow the camera to move more freely. Should be 0.0...1.0 17 | case easy(polarSpacing: Float) 18 | 19 | /// Flexible 3D Camera 20 | case flexible 21 | } 22 | -------------------------------------------------------------------------------- /Sources/SwiftyCreatives/Macros/SketchObject.swift: -------------------------------------------------------------------------------- 1 | // 2 | // File.swift 3 | // 4 | // 5 | // Created by Yuki Kuwashima on 2024/02/04. 6 | // 7 | 8 | import Foundation 9 | 10 | @attached( 11 | extension, 12 | conformances: FunctionBase, SketchObjectHasDraw, 13 | names: 14 | named(privateEncoder), 15 | named(customMatrix) 16 | ) 17 | @attached( 18 | member, 19 | conformances: FunctionBase, SketchObjectHasDraw, 20 | names: 21 | named(privateEncoder), 22 | named(customMatrix) 23 | ) 24 | public macro SketchObject() = #externalMacro(module: "SwiftyCreativesMacro", type: "SketchObject") 25 | -------------------------------------------------------------------------------- /Sources/SwiftyCreatives/Sketch/FunctionBase/FunctionBase+PushPop.swift: -------------------------------------------------------------------------------- 1 | // 2 | // FunctionBase+PushPop.swift 3 | // 4 | // 5 | // Created by Yuki Kuwashima on 2023/03/08. 6 | // 7 | 8 | import simd 9 | import SimpleSimdSwift 10 | 11 | public extension FunctionBase { 12 | func pushMatrix() { 13 | self.customMatrix.append(f4x4.createIdentity()) 14 | setCustomMatrix() 15 | } 16 | func popMatrix() { 17 | let _ = self.customMatrix.popLast() 18 | setCustomMatrix() 19 | } 20 | func push(_ process: () -> Void) { 21 | pushMatrix() 22 | process() 23 | popMatrix() 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /Sources/SwiftyCreatives/Views/TouchableMTKView/TouchableMTKView+Utils.swift: -------------------------------------------------------------------------------- 1 | // 2 | // TouchableMTKView+Utils.swift 3 | // 4 | // 5 | // Created by Yuki Kuwashima on 2023/05/16. 6 | // 7 | 8 | import MetalKit 9 | 10 | #if !os(visionOS) 11 | 12 | extension TouchableMTKView { 13 | func checkIfExceedsPolarSpacing(rad: Float, polarSpacing: Float) -> Bool { 14 | let mockedMainMatrix = renderer.camera.mock_rotateAroundVisibleX(rad) 15 | let detectionValue = mockedMainMatrix.columns.1.y 16 | if detectionValue < polarSpacing { 17 | return true 18 | } 19 | return false 20 | } 21 | } 22 | 23 | #endif 24 | -------------------------------------------------------------------------------- /Sources/SwiftyCreatives/Extensions/MTLTexture+.swift: -------------------------------------------------------------------------------- 1 | // 2 | // MTLTexture+.swift 3 | // 4 | // 5 | // Created by Yuki Kuwashima on 2023/03/26. 6 | // 7 | 8 | import Metal 9 | import CoreGraphics 10 | import CoreImage 11 | 12 | public extension MTLTexture { 13 | var cgImage: CGImage? { 14 | guard let ciimage = CIImage(mtlTexture: self) else { 15 | return nil 16 | } 17 | let flipped = ciimage.transformed(by: CGAffineTransform(scaleX: 1, y: -1)) 18 | let cgimage = ShaderCore.context.createCGImage( 19 | flipped, 20 | from: flipped.extent 21 | ) 22 | return cgimage 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /Examples/ExampleMacOSApp/ExampleMacOSApp/Samples/Sample3.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Sample3.swift 3 | // ExampleMacOSApp 4 | // 5 | // Created by Yuki Kuwashima on 2023/02/02. 6 | // 7 | 8 | import SwiftyCreatives 9 | import SwiftUI 10 | 11 | final class Sample3: Sketch { 12 | 13 | var c = f4(1, 0.5, 1, 1) 14 | 15 | override func update(camera: MainCamera) { 16 | camera.rotateAroundY(0.01) 17 | } 18 | 19 | override func draw(encoder: SCEncoder, camera: MainCamera) { 20 | color(c) 21 | box(f3.one * 5) 22 | } 23 | } 24 | 25 | struct Sample3View: View { 26 | var body: some View { 27 | SketchView(Sample3()) 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /Sources/SwiftyCreatives/Extensions/UIView+.swift: -------------------------------------------------------------------------------- 1 | // 2 | // UIView+.swift 3 | // 4 | // 5 | // Created by Yuki Kuwashima on 2022/12/27. 6 | // 7 | 8 | #if os(iOS) 9 | 10 | import UIKit 11 | 12 | public extension UIView { 13 | class func fromNib(type: T.Type) -> T { 14 | return Bundle(for: T.self).loadNibNamed(String(describing: T.self), owner: nil, options: nil)![0] as! T 15 | } 16 | func convertToImage() -> UIImage { 17 | let imageRenderer = UIGraphicsImageRenderer.init(size: bounds.size) 18 | return imageRenderer.image { context in 19 | layer.render(in: context.cgContext) 20 | } 21 | } 22 | } 23 | 24 | #endif 25 | -------------------------------------------------------------------------------- /Examples/ExampleMacOSApp/ExampleMacOSApp/Samples/Sample2.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Sample2.swift 3 | // ExampleMacOSApp 4 | // 5 | // Created by Yuki Kuwashima on 2023/02/02. 6 | // 7 | 8 | import SwiftyCreatives 9 | import SwiftUI 10 | 11 | final class Sample2: Sketch { 12 | 13 | var c: f4 = .init(1, 0.5, 1, 1) 14 | 15 | override func update(camera: MainCamera) { 16 | camera.rotateAroundY(0.01) 17 | } 18 | 19 | override func draw(encoder: SCEncoder, camera: MainCamera) { 20 | color(c) 21 | rect(f3.one * 5) 22 | } 23 | } 24 | 25 | struct Sample2View: View { 26 | var body: some View { 27 | SketchView(Sample2()) 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /Sources/SwiftyCreatives/Primitives/PrimitiveUtils/Abstract/ImageAdjuster.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ImageAdjuster.swift 3 | // 4 | // 5 | // Created by Yuki Kuwashima on 2023/03/03. 6 | // 7 | 8 | public struct ImageAdjuster { 9 | public static func adjustedScale(width: Float, height: Float, with option: ImageAdjustOption) -> f3 { 10 | switch option { 11 | case .basedOnWidth: 12 | return f3(1, height / width, 1) 13 | case .basedOnHeight: 14 | return f3(width / height, 1, 1) 15 | case .basedOnLonger: 16 | let longer = max(width, height) 17 | return f3(width / longer, height / longer, 1) 18 | } 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /Sources/SwiftyCreatives/Extensions/Comparable+.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Comparable+.swift 3 | // 4 | // 5 | // Created by Yuki Kuwashima on 2023/02/28. 6 | // 7 | 8 | extension Comparable { 9 | func clamp(_ minValue: Self, _ maxValue: Self) -> Self { 10 | return min(max(minValue, self), maxValue) 11 | } 12 | func clamp(_ range: ClosedRange) -> Self { 13 | return self.clamp(range.lowerBound, range.upperBound) 14 | } 15 | static func clamp(value: Self, _ minValue: Self, _ maxValue: Self) -> Self { 16 | return min(max(minValue, value), maxValue) 17 | } 18 | static func clamp(value: Self, _ range: ClosedRange) -> Self { 19 | return value.clamp(range.lowerBound, range.upperBound) 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /Sources/SwiftyCreatives/Camera/Config/DefaultOrthographicConfig.swift: -------------------------------------------------------------------------------- 1 | // 2 | // DefaultOrthoConfig.swift 3 | // 4 | // 5 | // Created by Yuki Kuwashima on 2023/01/30. 6 | // 7 | 8 | /// Default camera config for orthographic projection. 9 | public class DefaultOrthographicConfig: CameraConfig { 10 | public override init( 11 | fov: Float = 85, 12 | near: Float = -1000, 13 | far: Float = 1000, 14 | easyCameraType: EasyCameraType = .manual, 15 | isPerspective: Bool = false 16 | ) { 17 | super.init( 18 | fov: fov, 19 | near: near, 20 | far: far, 21 | easyCameraType: easyCameraType, 22 | isPerspective: isPerspective 23 | ) 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /Sources/SwiftyCreatives/Primitives/PrimitiveUtils/Abstract/HitTestablePrimitive.swift: -------------------------------------------------------------------------------- 1 | // 2 | // HitTestablePrimitive.swift 3 | // 4 | // 5 | // Created by Yuki Kuwashima on 2023/01/31. 6 | // 7 | 8 | import simd 9 | 10 | open class HitTestablePrimitive: Primitive { 11 | override init() { super.init() } 12 | public var cachedCustomMatrix: f4x4 = f4x4.createIdentity() 13 | 14 | public func drawWithCache(encoder: SCEncoder, customMatrix: f4x4) { 15 | draw(encoder) 16 | cachedCustomMatrix = customMatrix 17 | } 18 | public func drawWithCache(packet: SCPacket) { 19 | draw(packet.privateEncoder!) 20 | cachedCustomMatrix = packet.customMatrix.reduce(f4x4.createIdentity(), *) 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /Sources/SwiftyCreatives/Views/KitSketchView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // KitSketchView.swift 3 | // 4 | // 5 | // Created by Yuki Kuwashima on 2022/12/17. 6 | // 7 | 8 | import MetalKit 9 | 10 | #if !os(visionOS) 11 | 12 | #if os(macOS) 13 | import AppKit 14 | #elseif os(iOS) 15 | import UIKit 16 | #endif 17 | 18 | public class KitSketchView: TouchableMTKView { 19 | var thisRenderer: RendererBase 20 | public init(_ sketch: Sketch, blendMode: RendererBase.BlendMode, cameraConfig: CameraConfig, drawConfig: DrawConfig) { 21 | thisRenderer = blendMode.getRenderer( 22 | sketch: sketch, 23 | cameraConfig: cameraConfig, 24 | drawConfig: drawConfig 25 | ) 26 | super.init(renderer: thisRenderer) 27 | } 28 | } 29 | 30 | #endif 31 | -------------------------------------------------------------------------------- /Sources/SwiftyCreatives/Camera/Config/DefaultPerspectiveConfig.swift: -------------------------------------------------------------------------------- 1 | // 2 | // File.swift 3 | // 4 | // 5 | // Created by Yuki Kuwashima on 2024/02/03. 6 | // 7 | 8 | import Foundation 9 | 10 | /// Default camera config for perspective projection. 11 | public class DefaultPerspectiveConfig: CameraConfig { 12 | public override init( 13 | fov: Float = 85, 14 | near: Float = 0.01, 15 | far: Float = 1000, 16 | easyCameraType: EasyCameraType = .easy(polarSpacing: 0.03), 17 | isPerspective: Bool = true 18 | ) { 19 | super.init( 20 | fov: fov, 21 | near: near, 22 | far: far, 23 | easyCameraType: easyCameraType, 24 | isPerspective: isPerspective 25 | ) 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /Examples/ExampleMacOSApp/ExampleMacOSApp.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved: -------------------------------------------------------------------------------- 1 | { 2 | "pins" : [ 3 | { 4 | "identity" : "swift-snapshot-testing", 5 | "kind" : "remoteSourceControl", 6 | "location" : "https://github.com/pointfreeco/swift-snapshot-testing", 7 | "state" : { 8 | "revision" : "e7b77228b34057041374ebef00c0fd7739d71a2b", 9 | "version" : "1.15.3" 10 | } 11 | }, 12 | { 13 | "identity" : "swift-syntax", 14 | "kind" : "remoteSourceControl", 15 | "location" : "https://github.com/apple/swift-syntax.git", 16 | "state" : { 17 | "revision" : "64889f0c732f210a935a0ad7cda38f77f876262d", 18 | "version" : "509.1.1" 19 | } 20 | } 21 | ], 22 | "version" : 2 23 | } 24 | -------------------------------------------------------------------------------- /Sources/SwiftyCreatives/Extensions/CGPoint+.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CGPoint+.swift 3 | // 4 | // 5 | // Created by Yuki Kuwashima on 2022/12/10. 6 | // 7 | 8 | import CoreGraphics 9 | 10 | extension CGPoint { 11 | public static func * (lhs: CGPoint, rhs: CGFloat) -> CGPoint { 12 | return CGPoint(x: lhs.x * rhs, y: lhs.y * rhs) 13 | } 14 | public static func * (lhs: CGPoint, rhs: Float) -> CGPoint { 15 | return CGPoint(x: lhs.x * CGFloat(rhs), y: lhs.y * CGFloat(rhs)) 16 | } 17 | public static func / (lhs: CGPoint, rhs: CGFloat) -> CGPoint { 18 | return CGPoint(x: lhs.x / rhs, y: lhs.y / rhs) 19 | } 20 | public static func / (lhs: CGPoint, rhs: Float) -> CGPoint { 21 | return CGPoint(x: lhs.x / CGFloat(rhs), y: lhs.y / CGFloat(rhs)) 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /Tests/SwiftyCreativesTests/SnapshotTests/SnapshotTestUtil.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SnapshotTestUtil.swift 3 | // 4 | // 5 | // Created by Yuki Kuwashima on 2023/03/27. 6 | // 7 | 8 | import XCTest 9 | import MetalKit 10 | @testable import SwiftyCreatives 11 | 12 | #if os(macOS) 13 | 14 | enum SnapshotTestUtil { 15 | 16 | static let isRecording = false 17 | 18 | static func testGPU() throws { 19 | try XCTSkipIf(MTLCreateSystemDefaultDevice() == nil) 20 | } 21 | 22 | static func render(sketch: SketchForTest) { 23 | let swiftuiView = SketchView(sketch, blendMode: .normalBlend) 24 | let mtkView = MTKView(frame: CGRect(x: 0, y: 0, width: 100, height: 100), device: ShaderCore.device) 25 | swiftuiView.renderer.draw(in: mtkView) 26 | } 27 | } 28 | 29 | #endif 30 | -------------------------------------------------------------------------------- /Sources/SwiftyCreatives/Utils/DefaultBuffers.swift: -------------------------------------------------------------------------------- 1 | // 2 | // DefaultBuffers.swift 3 | // 4 | // 5 | // Created by Yuki Kuwashima on 2023/03/26. 6 | // 7 | 8 | import Metal 9 | 10 | enum DefaultBuffers { 11 | static let default_false = ShaderCore.device.makeBuffer(bytes: [false], length: Bool.memorySize) 12 | static let default_true = ShaderCore.device.makeBuffer(bytes: [true], length: Bool.memorySize) 13 | static let default_f2 = ShaderCore.device.makeBuffer(bytes: [f2.zero], length: f2.memorySize) 14 | static let default_f3 = ShaderCore.device.makeBuffer(bytes: [f3.zero], length: f3.memorySize) 15 | static let default_f4 = ShaderCore.device.makeBuffer(bytes: [f4.zero], length: f4.memorySize) 16 | static let default_f4x4 = ShaderCore.device.makeBuffer(bytes: [f4x4.createIdentity()], length: f4x4.memorySize) 17 | } 18 | -------------------------------------------------------------------------------- /Sources/SwiftyCreatives/Primitives/HitTestables/HitTestableImg.swift: -------------------------------------------------------------------------------- 1 | // 2 | // HitTestableImg.swift 3 | // 4 | // 5 | // Created by Yuki Kuwashima on 2023/02/25. 6 | // 7 | 8 | import MetalKit 9 | 10 | open class HitTestableImg: RectanglePlanePrimitive, ImageLoadable { 11 | public override init() { super.init() } 12 | public var texture: MTLTexture? 13 | 14 | @available(*, unavailable, message: "Use img() in Sketch instead.") 15 | override public func draw(_ encoder: SCEncoder) {} 16 | 17 | @available(*, unavailable, message: "Use img() in Sketch instead.") 18 | public override func drawWithCache(packet: SCPacket) {} 19 | 20 | @available(*, unavailable, message: "Use img() in Sketch instead.") 21 | public override func drawWithCache(encoder: SCEncoder, customMatrix: f4x4) {} 22 | } 23 | -------------------------------------------------------------------------------- /Sources/SwiftyCreatives/Sketch/FunctionBase/FunctionBase+HitTestableImg.swift: -------------------------------------------------------------------------------- 1 | // 2 | // FunctionBase+HitTestableImg.swift 3 | // 4 | // 5 | // Created by Yuki Kuwashima on 2023/03/27. 6 | // 7 | 8 | import simd 9 | import SimpleSimdSwift 10 | 11 | public extension FunctionBase { 12 | func img(_ hitTestableImg: HitTestableImg) { 13 | setUniforms(modelPos: .zero, modelScale: hitTestableImg.scale, hasTexture: true) 14 | setVertices(RectShapeInfo.vertices) 15 | setUVs(RectShapeInfo.uvs) 16 | setNormals(RectShapeInfo.normals) 17 | setTexture(hitTestableImg.texture) 18 | privateEncoder?.drawPrimitives(type: RectShapeInfo.primitiveType, vertexStart: 0, vertexCount: RectShapeInfo.vertices.count) 19 | hitTestableImg.cachedCustomMatrix = customMatrix.reduce(f4x4.createIdentity(), *) 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /Sources/SwiftyCreatives/Sketch/FunctionBase/FunctionBase+Translate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // FunctionBase+Translate.swift 3 | // 4 | // 5 | // Created by Yuki Kuwashima on 2023/03/07. 6 | // 7 | 8 | import simd 9 | import SimpleSimdSwift 10 | 11 | public extension FunctionBase { 12 | 13 | func translate(_ x: Float, _ y: Float, _ z: Float) { 14 | let translateMatrix = f4x4.createTransform(x, y, z) 15 | self.customMatrix[self.customMatrix.count - 1] = self.customMatrix[self.customMatrix.count - 1] * translateMatrix 16 | setCustomMatrix() 17 | } 18 | 19 | func translate(_ value: f3) { 20 | let translateMatrix = f4x4.createTransform(value.x, value.y, value.z) 21 | self.customMatrix[self.customMatrix.count - 1] = self.customMatrix[self.customMatrix.count - 1] * translateMatrix 22 | setCustomMatrix() 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /Examples/ExampleMacOSApp/ExampleMacOSApp/Samples/Sample6.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Sample6.swift 3 | // ExampleMacOSApp 4 | // 5 | // Created by Yuki Kuwashima on 2023/02/12. 6 | // 7 | 8 | import SwiftyCreatives 9 | import SwiftUI 10 | 11 | final class Sample6: Sketch { 12 | override init() { 13 | super.init() 14 | } 15 | let count = 10 16 | override func draw(encoder: SCEncoder, camera: MainCamera) { 17 | setFog(color: f4.one, density: 0.02) 18 | color(0.8, 0.1, 0.1, 0.8) 19 | for z in -count...count { 20 | for y in -count...count { 21 | for x in -count...count { 22 | box(Float(x) * 5, Float(y) * 5, Float(z) * 5, 1, 1, 1) 23 | } 24 | } 25 | } 26 | } 27 | } 28 | 29 | struct Sample6View: View { 30 | var body: some View { 31 | SketchView(Sample6()) 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /Tests/SwiftyCreativesTests/SnapshotTests/Functions/ColorTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // File.swift 3 | // 4 | // 5 | // Created by Yuki Kuwashima on 2024/02/05. 6 | // 7 | 8 | @testable import SwiftyCreatives 9 | import XCTest 10 | import SwiftUI 11 | import SnapshotTesting 12 | import MetalKit 13 | 14 | #if os(macOS) 15 | final class ColorTests: XCTestCase { 16 | 17 | @MainActor 18 | func testColorIsWorking() async throws { 19 | try SnapshotTestUtil.testGPU() 20 | class TestSketch: SketchForTest { 21 | override func draw(encoder: SCEncoder) { 22 | color(0.3, 0.6, 1.0, 1.0) 23 | box(7) 24 | } 25 | } 26 | let expectation = XCTestExpectation() 27 | let sketch = TestSketch(expectation, testName: "testColorIsWorking") 28 | SnapshotTestUtil.render(sketch: sketch) 29 | await fulfillment(of: [expectation], timeout: 5.0) 30 | } 31 | } 32 | #endif 33 | -------------------------------------------------------------------------------- /Sources/SwiftyCreatives/Sketch/FunctionBase/FunctionBase+Line.swift: -------------------------------------------------------------------------------- 1 | // 2 | // FunctionBase+Line.swift 3 | // 4 | // 5 | // Created by Yuki Kuwashima on 2023/03/07. 6 | // 7 | 8 | import simd 9 | import SimpleSimdSwift 10 | 11 | public extension FunctionBase { 12 | 13 | func line(_ x1: Float, _ y1: Float, _ z1: Float, _ x2: Float, _ y2: Float, _ z2: Float) { 14 | setUniforms(modelPos: .zero, modelScale: .one, hasTexture: false) 15 | setVertices([f3(x1, y1, z1), f3(x2, y2, z2)]) 16 | setUVs([f2.zero, f2.zero]) 17 | setNormals([f3.zero, f3.zero]) 18 | privateEncoder?.drawPrimitives(type: .line, vertexStart: 0, vertexCount: 2) 19 | } 20 | 21 | func line(_ pos1: f3, _ pos2: f3) { 22 | setUniforms(modelPos: .zero, modelScale: .one, hasTexture: false) 23 | setVertices([pos1, pos2]) 24 | setUVs([f2.zero, f2.zero]) 25 | setNormals([f3.zero, f3.zero]) 26 | privateEncoder?.drawPrimitives(type: .line, vertexStart: 0, vertexCount: 2) 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /Sources/SwiftyCreatives/Sketch/FunctionBase/FunctionBase+Color.swift: -------------------------------------------------------------------------------- 1 | // 2 | // FunctionBase+Color.swift 3 | // 4 | // 5 | // Created by Yuki Kuwashima on 2023/03/07. 6 | // 7 | 8 | import simd 9 | import SimpleSimdSwift 10 | 11 | public extension FunctionBase { 12 | 13 | func color(_ r: Float, _ g: Float, _ b: Float, _ a: Float) { 14 | setColor(.init(r, g, b, a)) 15 | } 16 | 17 | func color(_ r: Float, _ g: Float, _ b: Float) { 18 | setColor(.init(r, g, b, 1)) 19 | } 20 | 21 | func color(_ color: f4) { 22 | setColor(color) 23 | } 24 | 25 | func color(_ rgb: f3, alpha: Float) { 26 | setColor(f4(rgb.x, rgb.y, rgb.z, alpha)) 27 | } 28 | 29 | func color(_ rgb: f3) { 30 | setColor(f4(rgb.x, rgb.y, rgb.z, 1)) 31 | } 32 | 33 | func color(_ gray: Float) { 34 | setColor(f4(gray, gray, gray, 1)) 35 | } 36 | 37 | func color(_ gray: Float, _ alpha: Float) { 38 | setColor(f4(gray, gray, gray, alpha)) 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /Sources/SwiftyCreatives/Primitives/PrimitiveUtils/Info/TriangleInfo.swift: -------------------------------------------------------------------------------- 1 | // 2 | // TriangleInfo.swift 3 | // 4 | // 5 | // Created by Yuki Kuwashima on 2023/02/25. 6 | // 7 | 8 | import Metal 9 | 10 | public struct TriangleInfo: PrimitiveInfo { 11 | public final class VertexPoint { 12 | static let A: f3 = f3(x: 0, y: 1.0, z: 0.0) 13 | static let B: f3 = f3(x: cos(Float.pi * 7.0 / 6.0), y: sin(Float.pi * 7.0 / 6.0), z: 0.0) 14 | static let C: f3 = f3(x: cos(Float.pi * 11.0 / 6.0), y: sin(Float.pi * 11.0 / 6.0), z: 0.0) 15 | } 16 | public static let primitiveType: MTLPrimitiveType = .triangle 17 | 18 | public static var vertices: [f3] = [ 19 | Self.VertexPoint.A, 20 | Self.VertexPoint.B, 21 | Self.VertexPoint.C 22 | ] 23 | 24 | public static var uvs: [f2] = [ 25 | f2(0, 0), 26 | f2(0, 0), 27 | f2(0, 0) 28 | ] 29 | 30 | public static var normals: [f3] = [ 31 | f3(0, 0, 1), 32 | f3(0, 0, 1), 33 | f3(0, 0, 1) 34 | ] 35 | } 36 | -------------------------------------------------------------------------------- /Sources/SwiftyCreatives/Camera/Config/CameraConfig.swift: -------------------------------------------------------------------------------- 1 | // 2 | // File.swift 3 | // 4 | // 5 | // Created by Yuki Kuwashima on 2024/02/03. 6 | // 7 | 8 | import Foundation 9 | 10 | /// Config for camera 11 | public class CameraConfig { 12 | 13 | /// field of view in radians 14 | public var fov: Float 15 | 16 | /// near clipping distance 17 | public var near: Float 18 | 19 | /// far clipping distance 20 | public var far: Float 21 | 22 | /// easy camera type 23 | public var easyCameraType: EasyCameraType 24 | 25 | /// perspective or orthographic camera. 26 | /// orthographic if false 27 | public var isPerspective: Bool 28 | 29 | public init( 30 | fov: Float, 31 | near: Float, 32 | far: Float, 33 | easyCameraType: EasyCameraType, 34 | isPerspective: Bool 35 | ) { 36 | self.fov = fov 37 | self.near = near 38 | self.far = far 39 | self.easyCameraType = easyCameraType 40 | self.isPerspective = isPerspective 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /Examples/ExampleMacOSApp/ExampleMacOSApp/Samples/Sample1.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Sample1.swift 3 | // ExampleMacOSApp 4 | // 5 | // Created by Yuki Kuwashima on 2023/02/02. 6 | // 7 | 8 | import SwiftyCreatives 9 | import SwiftUI 10 | 11 | final class Sample1: Sketch { 12 | 13 | let bloomPostProcessor = BloomPostProcessor() 14 | 15 | override func draw(encoder: SCEncoder, camera: MainCamera) { 16 | let count = 20 17 | for i in 0.. Self { 12 | return Self.init(width: lhs.width + rhs.width, height: lhs.height + rhs.height) 13 | } 14 | public static func - (lhs: Self, rhs: Self) -> Self { 15 | return Self.init(width: lhs.width - rhs.width, height: lhs.height - rhs.height) 16 | } 17 | public static func * (lhs: Self, rhs: CGFloat) -> Self { 18 | return Self.init(width: lhs.width * rhs, height: lhs.height * rhs) 19 | } 20 | public static func * (lhs: Self, rhs: Float) -> Self { 21 | return Self.init(width: lhs.width * CGFloat(rhs), height: lhs.height * CGFloat(rhs)) 22 | } 23 | public static func / (lhs: Self, rhs: CGFloat) -> Self { 24 | return Self.init(width: lhs.width / rhs, height: lhs.height / rhs) 25 | } 26 | public static func / (lhs: Self, rhs: Float) -> Self { 27 | return Self.init(width: lhs.width / CGFloat(rhs), height: lhs.height / CGFloat(rhs)) 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /Tests/SwiftyCreativesTests/SnapshotTests/Functions/HitTestableImgTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // File.swift 3 | // 4 | // 5 | // Created by Yuki Kuwashima on 2024/02/05. 6 | // 7 | 8 | @testable import SwiftyCreatives 9 | import XCTest 10 | import SwiftUI 11 | import SnapshotTesting 12 | import MetalKit 13 | 14 | #if os(macOS) 15 | final class HitTestableImgTests: XCTestCase { 16 | 17 | @MainActor 18 | func testHitTestableImgIsDrawed() async throws { 19 | try SnapshotTestUtil.testGPU() 20 | class TestSketch: SketchForTest { 21 | let testImage = HitTestableImg().load(name: "sampleImage", bundle: .module).adjustScale(with: .basedOnWidth).multiplyScale(3) 22 | override func draw(encoder: SCEncoder) { 23 | color(1, 0, 1, 1) 24 | rotateY(0.5) 25 | rotateZ(0.5) 26 | img(testImage) 27 | } 28 | } 29 | let expectation = XCTestExpectation() 30 | let sketch = TestSketch(expectation, testName: "testHitTestableImgIsDrawed") 31 | SnapshotTestUtil.render(sketch: sketch) 32 | await fulfillment(of: [expectation], timeout: 5.0) 33 | } 34 | } 35 | #endif 36 | -------------------------------------------------------------------------------- /Sources/SwiftyCreatives/Sketch/FunctionBase/FunctionBase+ModelObject.swift: -------------------------------------------------------------------------------- 1 | // 2 | // File.swift 3 | // 4 | // 5 | // Created by Yuki Kuwashima on 2024/02/05. 6 | // 7 | 8 | import simd 9 | import SimpleSimdSwift 10 | import Metal 11 | import CoreImage 12 | 13 | public extension FunctionBase { 14 | 15 | func model(_ modelObject: ModelObject) { 16 | setUniforms(modelPos: .zero, modelScale: .one, hasTexture: false) 17 | modelObject.draw(privateEncoder!) 18 | } 19 | 20 | func model(_ modelObject: ModelObject, primitiveType: MTLPrimitiveType) { 21 | setUniforms(modelPos: .zero, modelScale: .one, hasTexture: false) 22 | modelObject.draw(privateEncoder!, primitiveType: primitiveType) 23 | } 24 | 25 | func model(_ modelObject: ModelObject, with image: CGImage) { 26 | setUniforms(modelPos: .zero, modelScale: .one, hasTexture: false) 27 | modelObject.draw(privateEncoder!, with: image) 28 | } 29 | 30 | func model(_ modelObject: ModelObject, with texture: MTLTexture) { 31 | setUniforms(modelPos: .zero, modelScale: .one, hasTexture: false) 32 | modelObject.draw(privateEncoder!, with: texture) 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /Examples/ExampleMacOSApp/ExampleMacOSApp/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "mac", 5 | "scale" : "1x", 6 | "size" : "16x16" 7 | }, 8 | { 9 | "idiom" : "mac", 10 | "scale" : "2x", 11 | "size" : "16x16" 12 | }, 13 | { 14 | "idiom" : "mac", 15 | "scale" : "1x", 16 | "size" : "32x32" 17 | }, 18 | { 19 | "idiom" : "mac", 20 | "scale" : "2x", 21 | "size" : "32x32" 22 | }, 23 | { 24 | "idiom" : "mac", 25 | "scale" : "1x", 26 | "size" : "128x128" 27 | }, 28 | { 29 | "idiom" : "mac", 30 | "scale" : "2x", 31 | "size" : "128x128" 32 | }, 33 | { 34 | "idiom" : "mac", 35 | "scale" : "1x", 36 | "size" : "256x256" 37 | }, 38 | { 39 | "idiom" : "mac", 40 | "scale" : "2x", 41 | "size" : "256x256" 42 | }, 43 | { 44 | "idiom" : "mac", 45 | "scale" : "1x", 46 | "size" : "512x512" 47 | }, 48 | { 49 | "idiom" : "mac", 50 | "scale" : "2x", 51 | "size" : "512x512" 52 | } 53 | ], 54 | "info" : { 55 | "author" : "xcode", 56 | "version" : 1 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /Sources/SwiftyCreatives/Utils/Alias.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Alias.swift 3 | // 4 | // 5 | // Created by Yuki Kuwashima on 2022/12/10. 6 | // 7 | 8 | // MARK: - SwiftUI 9 | import SwiftUI 10 | 11 | #if os(macOS) 12 | typealias ViewRepresentable = NSViewRepresentable 13 | #elseif os(iOS) || os(visionOS) || os(tvOS) 14 | typealias ViewRepresentable = UIViewRepresentable 15 | #endif 16 | 17 | // MARK: - simd 18 | import simd 19 | 20 | public typealias f2 = simd_float2 21 | public typealias f3 = simd_float3 22 | public typealias f4 = simd_float4 23 | public typealias f4x4 = simd_float4x4 24 | 25 | // MARK: - View 26 | #if os(macOS) 27 | public typealias NSSketchView = KitSketchView 28 | #elseif os(iOS) || os(tvOS) 29 | public typealias UISketchView = KitSketchView 30 | #endif 31 | 32 | // MARK: - Metal 33 | import Metal 34 | public typealias SCEncoder = MTLRenderCommandEncoder 35 | public typealias SCCommandBuffer = MTLCommandBuffer 36 | 37 | // MARK: - Font 38 | #if os(macOS) 39 | public typealias FontAlias = NSFont 40 | #elseif os(iOS) || os(visionOS) || os(tvOS) 41 | public typealias FontAlias = UIFont 42 | #endif 43 | 44 | // MARK: - Color 45 | #if os(macOS) 46 | public typealias ColorAlias = NSColor 47 | #elseif os(iOS) || os(visionOS) || os(tvOS) 48 | public typealias ColorAlias = UIColor 49 | #endif 50 | -------------------------------------------------------------------------------- /Sources/SwiftyCreatives/Sketch/FunctionBase/FunctionBase+Circle.swift: -------------------------------------------------------------------------------- 1 | // 2 | // FunctionBase+Circle.swift 3 | // 4 | // 5 | // Created by Yuki Kuwashima on 2023/03/07. 6 | // 7 | 8 | import simd 9 | import SimpleSimdSwift 10 | 11 | public extension FunctionBase { 12 | 13 | func circle(_ x: Float, _ y: Float, _ z: Float, _ radX: Float, _ radY: Float) { 14 | setUniforms(modelPos: f3(x, y, z), modelScale: f3(radX, radY, 1), hasTexture: false) 15 | setVertices(CircleInfo.vertices) 16 | setUVs(CircleInfo.uvs) 17 | setNormals(CircleInfo.normals) 18 | setVertexColors(CircleInfo.vertices.map { _ in f4.zero }) 19 | privateEncoder?.drawIndexedPrimitives(type: CircleInfo.primitiveType, indexCount: 28 * 3, indexType: .uint16, indexBuffer:CircleInfo.indexBuffer, indexBufferOffset: 0) 20 | } 21 | 22 | func circle(_ pos: f3, _ radX: Float, _ radY: Float) { 23 | circle(pos.x, pos.y, pos.z, radX, radY) 24 | } 25 | 26 | func circle(_ pos: f3, _ rad: Float) { 27 | circle(pos.x, pos.y, pos.z, rad, rad) 28 | } 29 | 30 | func circle(_ rad: Float) { 31 | circle(0, 0, 0, rad, rad) 32 | } 33 | 34 | func circle(_ radX: Float, _ radY: Float) { 35 | circle(0, 0, 0, radX, radY) 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /Examples/ExampleMacOSApp/ExampleMacOSApp/Samples/Sample8.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Sample8.swift 3 | // ExampleMacOSApp 4 | // 5 | // Created by Yuki Kuwashima on 2023/02/19. 6 | // 7 | 8 | import SwiftyCreatives 9 | import CoreGraphics 10 | import AppKit 11 | import SwiftUI 12 | 13 | final class Sample8: Sketch { 14 | 15 | let box = HitTestableBox().setScale(f3(2, 3, 4)) 16 | var boxColor: f4 = .one 17 | var testBoxPos: f3 = .zero 18 | 19 | override func setupCamera(camera: MainCamera) { 20 | camera.setTranslate(0, 0, -20) 21 | } 22 | 23 | override func draw(encoder: SCEncoder, camera: MainCamera) { 24 | color(1, 0, 0, 1) 25 | box(testBoxPos, f3.one * 0.1) 26 | translate(0, 3, 0) 27 | color(boxColor) 28 | box(box) 29 | } 30 | 31 | override func mouseMoved(camera: MainCamera, location: f2) { 32 | let ray = camera.screenToWorldDirection(x: location.x, y: location.y) 33 | if let hitPos = box.hitTest(origin: ray.origin, direction: ray.direction) { 34 | boxColor = f4(0, 1, 0, 1) 35 | testBoxPos = hitPos 36 | } else { 37 | boxColor = .one 38 | } 39 | } 40 | } 41 | 42 | struct Sample8View: View { 43 | var body: some View { 44 | SketchView(Sample8()) 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /Sources/SwiftyCreatives/Resources/Shaders/SharedIndices.h: -------------------------------------------------------------------------------- 1 | // 2 | // SharedIndices.h 3 | // 4 | // 5 | // Created by Yuki Kuwashima on 2023/02/19. 6 | // 7 | 8 | #ifndef SharedIndices_h 9 | #define SharedIndices_h 10 | 11 | typedef enum { 12 | VertexAttribute_Position = 0, 13 | VertexAttribute_UV = 1, 14 | VertexAttribute_Normal = 2, 15 | VertexAttribute_Color = 3, 16 | } VertexAttributeIndex; 17 | 18 | typedef enum { 19 | VertexBuffer_Position = 0, 20 | VertexBuffer_ModelPos = 1, 21 | VertexBuffer_ModelRot = 2, 22 | VertexBuffer_ModelScale = 3, 23 | VertexBuffer_ProjectionMatrix = 4, 24 | VertexBuffer_ViewMatrix = 5, 25 | VertexBuffer_CameraPos = 6, 26 | VertexBuffer_Color = 10, 27 | VertexBuffer_UV = 11, 28 | VertexBuffer_Normal = 12, 29 | VertexBuffer_CustomMatrix = 15, 30 | VertexBuffer_UseVertexColor = 18, 31 | VertexBuffer_VertexColor = 19, 32 | } VertexBufferIndex; 33 | 34 | typedef enum { 35 | FragmentBuffer_Material = 1, 36 | FragmentBuffer_LightCount = 2, 37 | FragmentBuffer_Lights = 3, 38 | FragmentBuffer_HasTexture = 6, 39 | FragmentBuffer_IsActiveToLight = 7, 40 | FragmentBuffer_FogDensity = 16, 41 | FragmentBuffer_FogColor = 17, 42 | } FragmentBufferIndex; 43 | 44 | typedef enum { 45 | FragmentTexture_MainTexture = 0 46 | } FragmentTextureIndex; 47 | 48 | #endif /* SharedIndices_h */ 49 | -------------------------------------------------------------------------------- /Examples/ExampleMacOSApp/ExampleMacOSApp/Samples/Sample4.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Sample4.swift 3 | // ExampleMacOSApp 4 | // 5 | // Created by Yuki Kuwashima on 2023/02/02. 6 | // 7 | 8 | import SwiftyCreatives 9 | import SwiftUI 10 | 11 | @SketchObject 12 | class MyBox { 13 | var pos: f3 14 | var col: f4 15 | var scale: f3 16 | init(pos: f3, col: f4, scale: f3) { 17 | self.pos = pos 18 | self.col = col 19 | self.scale = scale 20 | } 21 | func draw() { 22 | color(col) 23 | box(pos, scale) 24 | } 25 | } 26 | 27 | final class Sample4: Sketch { 28 | var objects: [MyBox] = [] 29 | override init() { 30 | super.init() 31 | for _ in 0..<100 { 32 | let box = MyBox( 33 | pos: f3.randomPoint(-7...7), 34 | col: f4.randomPoint(0...1), 35 | scale: f3.randomPoint(1...2) 36 | ) 37 | objects.append(box) 38 | } 39 | } 40 | override func update(camera: MainCamera) { 41 | camera.rotateAroundY(0.01) 42 | } 43 | override func draw(encoder: SCEncoder, camera: MainCamera) { 44 | for o in objects { 45 | o.draw(encoder: encoder, customMatrix: getCustomMatrix()) 46 | } 47 | } 48 | } 49 | 50 | struct Sample4View: View { 51 | var body: some View { 52 | SketchView(Sample4()) 53 | } 54 | } 55 | 56 | -------------------------------------------------------------------------------- /Sources/SwiftyCreatives/Sketch/FunctionBase/FunctionBase+SVG.swift: -------------------------------------------------------------------------------- 1 | // 2 | // File.swift 3 | // 4 | // 5 | // Created by Yuki Kuwashima on 2024/02/06. 6 | // 7 | 8 | import SimpleSimdSwift 9 | import SVGVertexBuilder 10 | import Metal 11 | 12 | public extension FunctionBase { 13 | 14 | func svg(_ object: SVGObj, colors: [f4], primitiveType: MTLPrimitiveType = .triangle) { 15 | for (i, path) in object.triangulated.enumerated() { 16 | let index = min(i, colors.count-1) 17 | color(colors[index]) 18 | if path.count * f3.memorySize < 4096 { 19 | mesh(path, primitiveType: primitiveType) 20 | } else { 21 | if let buffer = ShaderCore.device.makeBuffer(bytes: path, length: path.count * f3.memorySize) { 22 | mesh(buffer) 23 | } 24 | } 25 | } 26 | } 27 | 28 | func svg(_ object: SVGObj, primitiveType: MTLPrimitiveType = .triangle) { 29 | for (i, path) in object.triangulated.enumerated() { 30 | color(object.colors[i]) 31 | if path.count * f3.memorySize < 4096 { 32 | mesh(path, primitiveType: primitiveType) 33 | } else { 34 | if let buffer = ShaderCore.device.makeBuffer(bytes: path, length: path.count * f3.memorySize) { 35 | mesh(buffer) 36 | } 37 | } 38 | } 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /Sources/SwiftyCreatives/Sketch/FunctionBase/FunctionBase+Img.swift: -------------------------------------------------------------------------------- 1 | // 2 | // FunctionBase+Img.swift 3 | // 4 | // 5 | // Created by Yuki Kuwashima on 2023/03/07. 6 | // 7 | 8 | import Metal 9 | import simd 10 | import SimpleSimdSwift 11 | 12 | public extension FunctionBase { 13 | func img(texture: MTLTexture, with option: ImageAdjustOption) { 14 | let adjustedScale = ImageAdjuster.adjustedScale(width: Float(texture.width), height: Float(texture.height), with: option) 15 | setUniforms(modelPos: .zero, modelScale: adjustedScale, hasTexture: true) 16 | setVertices(RectShapeInfo.vertices) 17 | setUVs(RectShapeInfo.uvs) 18 | setNormals(RectShapeInfo.normals) 19 | setVertexColors(RectShapeInfo.vertices.map { _ in f4.zero }) 20 | setTexture(texture) 21 | privateEncoder?.drawPrimitives(type: RectShapeInfo.primitiveType, vertexStart: 0, vertexCount: RectShapeInfo.vertices.count) 22 | } 23 | 24 | func img(_ imgObj: Img) { 25 | setUniforms(modelPos: .zero, modelScale: imgObj.scale, hasTexture: true) 26 | setVertices(RectShapeInfo.vertices) 27 | setUVs(RectShapeInfo.uvs) 28 | setNormals(RectShapeInfo.normals) 29 | setVertexColors(RectShapeInfo.vertices.map { _ in f4.zero }) 30 | setTexture(imgObj.texture) 31 | privateEncoder?.drawPrimitives(type: RectShapeInfo.primitiveType, vertexStart: 0, vertexCount: RectShapeInfo.vertices.count) 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /Examples/ExampleMacOSApp/ExampleMacOSApp/Samples/Sample9.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Sample9.swift 3 | // ExampleMacOSApp 4 | // 5 | // Created by Yuki Kuwashima on 2024/02/04. 6 | // 7 | 8 | import SwiftyCreatives 9 | import SwiftUI 10 | import Observation 11 | 12 | @Observable 13 | final class Sample9: Sketch { 14 | 15 | var fov: Float = 85 16 | 17 | override func update(camera: MainCamera) { 18 | camera.rotateAroundY(0.01) 19 | camera.setFov(to: fov) 20 | } 21 | 22 | override func draw(encoder: SCEncoder, camera: MainCamera) { 23 | color(1, 0.5, 1, 0.3) 24 | let count = 10 25 | let spacing: Float = 10 26 | let size: Float = 1 27 | for z in -count...count { 28 | for y in -count...count { 29 | for x in -count...count { 30 | box(Float(x) * spacing, Float(y) * spacing, Float(z) * spacing, size, size, size) 31 | } 32 | } 33 | } 34 | } 35 | } 36 | 37 | struct Sample9View: View { 38 | @State var sketch = Sample9() 39 | var body: some View { 40 | SketchView(sketch) 41 | .toolbar { 42 | ToolbarItem { 43 | HStack { 44 | Text("fov") 45 | Slider(value: $sketch.fov, in: 10...180) 46 | .frame(width: 400) 47 | } 48 | } 49 | } 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /Sources/SwiftyCreatives/Views/SketchView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SketchView.swift 3 | // 4 | // 5 | // Created by Yuki Kuwashima on 2023/02/28. 6 | // 7 | 8 | import MetalKit 9 | import SwiftUI 10 | 11 | #if !os(visionOS) 12 | 13 | #if os(macOS) 14 | import AppKit 15 | #elseif os(iOS) 16 | import UIKit 17 | #endif 18 | 19 | public struct SketchView: ViewRepresentable { 20 | let renderer: RendererBase 21 | let drawProcess: Sketch 22 | public init( 23 | _ sketch: Sketch, 24 | blendMode: RendererBase.BlendMode = .alphaBlend, 25 | cameraConfig: CameraConfig = DefaultPerspectiveConfig(), 26 | drawConfig: DrawConfig = DefaultDrawConfig() 27 | ) { 28 | self.drawProcess = sketch 29 | self.renderer = blendMode.getRenderer( 30 | sketch: self.drawProcess, 31 | cameraConfig: cameraConfig, 32 | drawConfig: drawConfig 33 | ) 34 | } 35 | 36 | #if os(macOS) 37 | public func makeNSView(context: Context) -> MTKView { 38 | let mtkView = TouchableMTKView(renderer: renderer) 39 | return mtkView 40 | } 41 | public func updateNSView(_ nsView: MTKView, context: Context) {} 42 | #elseif os(iOS) || os(tvOS) 43 | public func makeUIView(context: Context) -> MTKView { 44 | let mtkView = TouchableMTKView(renderer: renderer) 45 | return mtkView 46 | } 47 | public func updateUIView(_ uiView: MTKView, context: Context) {} 48 | #endif 49 | } 50 | 51 | #endif 52 | -------------------------------------------------------------------------------- /Sources/SwiftyCreatives/PropertyWrappers/SCAnimatable.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SCAnimatable.swift 3 | // 4 | // 5 | // Created by Yuki Kuwashima on 2023/02/27. 6 | // 7 | 8 | @propertyWrapper 9 | public class SCAnimatable { 10 | public enum SCAnimationType { 11 | case linear 12 | case easeOut 13 | } 14 | private var value: Float 15 | private var targetValue: Float 16 | public var projectedValue: SCAnimatable { self } 17 | public init (wrappedValue: Float) { 18 | value = 0 19 | targetValue = wrappedValue 20 | } 21 | public var wrappedValue: Float { 22 | get { targetValue } 23 | set { targetValue = newValue } 24 | } 25 | private(set) public var animationValue: Float { 26 | get { value } 27 | set { 28 | value = newValue 29 | targetValue = newValue 30 | } 31 | } 32 | public func directSet(_ value: Float) { 33 | animationValue = value 34 | } 35 | public func update(multiplier: Float, with type: SCAnimationType = .easeOut) { 36 | switch type { 37 | case .easeOut: 38 | let clampedMultiplier = multiplier.clamp(0...1) 39 | let diff = targetValue - value 40 | let changeValue = diff * clampedMultiplier 41 | value += changeValue 42 | case .linear: 43 | let diff = targetValue - value 44 | let clamped = diff.clamp(-multiplier...multiplier) 45 | value += clamped 46 | } 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /Examples/ExampleMacOSApp/ExampleMacOSApp/Samples/Sample7.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Sample7.swift 3 | // ExampleMacOSApp 4 | // 5 | // Created by Yuki Kuwashima on 2023/02/19. 6 | // 7 | 8 | import SwiftyCreatives 9 | import CoreGraphics 10 | import AppKit 11 | import SwiftUI 12 | 13 | class MyHitTestableRect: HitTestableRect { 14 | var color: f4 = .zero 15 | } 16 | 17 | final class Sample7: Sketch { 18 | override func setupCamera(camera: MainCamera) { 19 | camera.setTranslate(0, 0, -10) 20 | } 21 | var rect: [MyHitTestableRect] = [ 22 | MyHitTestableRect(), 23 | MyHitTestableRect(), 24 | MyHitTestableRect() 25 | ] 26 | override func draw(encoder: SCEncoder, camera: MainCamera) { 27 | for r in rect { 28 | translate(0, 3, 0) 29 | rotateY(0.3) 30 | color(r.color) 31 | r.drawWithCache(encoder: encoder, customMatrix: getCustomMatrix()) 32 | } 33 | } 34 | 35 | override func mouseMoved(camera: MainCamera, location: f2) { 36 | 37 | let ray = camera.screenToWorldDirection(x: location.x, y: location.y) 38 | 39 | for r in rect { 40 | if let _ = r.hitTestGetPos(origin: ray.origin, direction: ray.direction) { 41 | r.color = f4(1, 0.5, 1, 1) 42 | } else { 43 | r.color = .one 44 | } 45 | } 46 | } 47 | } 48 | 49 | struct Sample7View: View { 50 | var body: some View { 51 | SketchView(Sample7()) 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /Tests/SwiftyCreativesTests/SnapshotTests/Functions/LineTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // LineTests.swift 3 | // 4 | // 5 | // Created by Yuki Kuwashima on 2023/03/28. 6 | // 7 | 8 | @testable import SwiftyCreatives 9 | import XCTest 10 | import SwiftUI 11 | import SnapshotTesting 12 | import MetalKit 13 | 14 | #if os(macOS) 15 | final class LineTests: XCTestCase { 16 | 17 | @MainActor 18 | func testLineIsDrawed() async throws { 19 | try SnapshotTestUtil.testGPU() 20 | class TestSketch: SketchForTest { 21 | override func draw(encoder: SCEncoder) { 22 | color(1) 23 | line(0, 0, 0, 10, 10, 10) 24 | } 25 | } 26 | let expectation = XCTestExpectation() 27 | let sketch = TestSketch(expectation, testName: "testLineIsDrawed") 28 | SnapshotTestUtil.render(sketch: sketch) 29 | await fulfillment(of: [expectation], timeout: 5.0) 30 | } 31 | 32 | @MainActor 33 | func testLineColorWorking() async throws { 34 | try SnapshotTestUtil.testGPU() 35 | class TestSketch: SketchForTest { 36 | override func draw(encoder: SCEncoder) { 37 | color(1, 0.5, 0.2, 0.8) 38 | line(0, 0, 0, 10, 10, 10) 39 | } 40 | } 41 | let expectation = XCTestExpectation() 42 | let sketch = TestSketch(expectation, testName: "testLineColorWorking") 43 | SnapshotTestUtil.render(sketch: sketch) 44 | await fulfillment(of: [expectation], timeout: 5.0) 45 | } 46 | } 47 | #endif 48 | -------------------------------------------------------------------------------- /Sources/SwiftyCreativesSound/FFTVisualizer.swift: -------------------------------------------------------------------------------- 1 | // 2 | // File.swift 3 | // 4 | // 5 | // Created by Yuki Kuwashima on 2023/07/04. 6 | // 7 | 8 | import Foundation 9 | 10 | public class FFTVisualizer { 11 | 12 | public var baseUpOffset: Float = 20 13 | public var historyCount: Int = 5 14 | public var averageMags: [Float] = [] 15 | public var freqArray: [Float] = [] 16 | 17 | var magsHistory: [[Float]] = [] 18 | 19 | public init() {} 20 | 21 | public func updateData(_ capturer: AudioCapturer) { 22 | 23 | let mags = capturer.fftResult.map { 24 | var db = TempiFFT.toDB($0.magnitude) 25 | if db.isNaN { 26 | db = 0 27 | } 28 | return db + baseUpOffset 29 | } 30 | freqArray = capturer.fftResult.map { $0.frequency } 31 | 32 | if averageMags.count != mags.count { 33 | averageMags = mags 34 | magsHistory = [] 35 | } 36 | 37 | magsHistory.append(mags) 38 | while magsHistory.count > historyCount { 39 | magsHistory.removeFirst() 40 | } 41 | 42 | for i in 0.. f2 { 14 | var location = loc 15 | if isPerspective { 16 | location.y = event.window!.contentRect( 17 | forFrameRect: event.window!.frame 18 | ).height - location.y 19 | location.x -= viewFrame.minX 20 | location.y -= viewFrame.minY 21 | return f2(Float(location.x), Float(location.y)) 22 | } else { 23 | let contentRect = event.window!.contentRect(forFrameRect: event.window!.frame) 24 | var adjustedLocation = f2( 25 | Float(location.x), 26 | Float(contentRect.height - location.y) 27 | ) 28 | adjustedLocation.x -= Float(viewFrame.minX) 29 | adjustedLocation.y -= Float(viewFrame.minY) 30 | let normalizedLocation: f2 = f2( 31 | adjustedLocation.x / Float(viewFrame.width), 32 | 1 - (adjustedLocation.y / Float(viewFrame.height)) 33 | ) 34 | let signedNormalizedLocation = normalizedLocation * 2 - f2.one 35 | return f2(signedNormalizedLocation.x * metalDrawableSize.x / 2, signedNormalizedLocation.y * metalDrawableSize.y / 2) 36 | } 37 | } 38 | } 39 | #endif 40 | -------------------------------------------------------------------------------- /Tests/SwiftyCreativesTests/SnapshotTests/Functions/SketchForTest.swift: -------------------------------------------------------------------------------- 1 | // 2 | // File.swift 3 | // 4 | // 5 | // Created by Yuki Kuwashima on 2024/02/03. 6 | // 7 | 8 | #if os(macOS) 9 | 10 | @testable import SwiftyCreatives 11 | import XCTest 12 | import SnapshotTesting 13 | 14 | class SketchForTest: Sketch { 15 | 16 | let expectation: XCTestExpectation 17 | let testName: String 18 | 19 | init(_ expectation: XCTestExpectation, testName: String) { 20 | self.expectation = expectation 21 | self.testName = testName 22 | super.init() 23 | } 24 | 25 | override func draw(encoder: SCEncoder) {} 26 | 27 | override func afterCommit(texture: MTLTexture?) { 28 | self.expectation.fulfill() 29 | let desc = MTLTextureDescriptor() 30 | desc.width = texture!.width 31 | desc.height = texture!.height 32 | desc.textureType = .type2D 33 | desc.pixelFormat = .bgra8Unorm 34 | let tex = ShaderCore.device.makeTexture(descriptor: desc)! 35 | 36 | let cb = ShaderCore.commandQueue.makeCommandBuffer()! 37 | let blitEncoder = cb.makeBlitCommandEncoder()! 38 | blitEncoder.copy(from: texture!, to: tex) 39 | blitEncoder.endEncoding() 40 | cb.commit() 41 | cb.waitUntilCompleted() 42 | 43 | let cgimage = tex.cgImage! 44 | let finalimage = NSImage(cgImage: cgimage, size: NSSize(width: 100, height: 100)) 45 | assertSnapshot(matching: finalimage, as: .image, record: SnapshotTestUtil.isRecording, testName: testName) 46 | } 47 | } 48 | 49 | #endif 50 | -------------------------------------------------------------------------------- /Examples/ExampleMacOSApp/ExampleMacOSApp/Samples/Sample10.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Sample10.swift 3 | // ExampleMacOSApp 4 | // 5 | // Created by Yuki Kuwashima on 2024/02/04. 6 | // 7 | 8 | import SwiftyCreatives 9 | import SwiftUI 10 | 11 | class Sample10: Sketch { 12 | 13 | var swiftTextObj: Text2D = Text2D( 14 | text: "Swifty\nCreatives", 15 | fontName: "Avenir-BlackOblique", 16 | fontSize: 10 17 | ) 18 | 19 | override func draw(encoder: SCEncoder, camera: MainCamera) { 20 | color(1) 21 | text(swiftTextObj) 22 | } 23 | } 24 | 25 | struct Sample10View: View { 26 | 27 | let sketch = Sample10() 28 | 29 | var body: some View { 30 | SketchView(sketch) 31 | .toolbar { 32 | ToolbarItem { 33 | Menu("Font") { 34 | ForEach(getAvailableFonts(), id: \.self) { font in 35 | Button(font) { 36 | sketch.swiftTextObj = Text2D( 37 | text: "Swifty\nCreatives", 38 | fontName: font, 39 | fontSize: 10 40 | ) 41 | } 42 | } 43 | } 44 | } 45 | } 46 | } 47 | 48 | func getAvailableFontFamilies() -> [String] { 49 | return NSFontManager.shared.availableFontFamilies 50 | } 51 | 52 | func getAvailableFonts() -> [String] { 53 | return NSFontManager.shared.availableFonts 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /Tests/SwiftyCreativesTests/SnapshotTests/Functions/FogTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // File.swift 3 | // 4 | // 5 | // Created by Yuki Kuwashima on 2024/02/05. 6 | // 7 | 8 | @testable import SwiftyCreatives 9 | import XCTest 10 | import SwiftUI 11 | import SnapshotTesting 12 | import MetalKit 13 | 14 | #if os(macOS) 15 | final class FogTests: XCTestCase { 16 | 17 | @MainActor 18 | func testFogIsWorking() async throws { 19 | try SnapshotTestUtil.testGPU() 20 | class TestSketch: SketchForTest { 21 | override func draw(encoder: SCEncoder) { 22 | setFog(color: f4(1, 0, 0, 1), density: 0.03) 23 | color(1) 24 | let count = 5 25 | rotateY(0.5) 26 | rotateZ(0.5) 27 | for x in -count...count { 28 | for y in -count...count { 29 | for z in -count...count { 30 | box( 31 | Float(x) * 10, 32 | Float(y) * 10, 33 | Float(z) * 10, 34 | 1, 35 | 1, 36 | 1 37 | ) 38 | } 39 | } 40 | } 41 | } 42 | } 43 | let expectation = XCTestExpectation() 44 | let sketch = TestSketch(expectation, testName: "testFogIsWorking") 45 | SnapshotTestUtil.render(sketch: sketch) 46 | await fulfillment(of: [expectation], timeout: 5.0) 47 | } 48 | } 49 | #endif 50 | 51 | -------------------------------------------------------------------------------- /Examples/ExampleVisionOS/ExampleVisionOS/ExampleVisionOSApp.swift: -------------------------------------------------------------------------------- 1 | // 2 | // App.swift 3 | // 4 | 5 | import SwiftUI 6 | import CompositorServices 7 | import SwiftyCreatives 8 | 9 | struct ContentStageConfiguration: CompositorLayerConfiguration { 10 | func makeConfiguration(capabilities: LayerRenderer.Capabilities, configuration: inout LayerRenderer.Configuration) { 11 | configuration.depthFormat = .depth32Float_stencil8 12 | configuration.colorFormat = .bgra8Unorm_srgb 13 | 14 | let foveationEnabled = capabilities.supportsFoveation 15 | configuration.isFoveationEnabled = foveationEnabled 16 | 17 | let options: LayerRenderer.Capabilities.SupportedLayoutsOptions = foveationEnabled ? [.foveationEnabled] : [] 18 | let supportedLayouts = capabilities.supportedLayouts(options: options) 19 | 20 | configuration.layout = supportedLayouts.contains(.layered) ? .layered : .dedicated 21 | } 22 | } 23 | 24 | @main 25 | struct TestingApp: App { 26 | init() { 27 | print("wow", ShaderCore.device.supportsFamily(.apple4)) 28 | } 29 | var body: some Scene { 30 | WindowGroup { 31 | ContentView() 32 | } 33 | 34 | ImmersiveSpace(id: "ImmersiveSpace") { 35 | CompositorLayer(configuration: ContentStageConfiguration()) { layerRenderer in 36 | let renderer = RendererBase.BlendMode.normalBlend.getRenderer(sketch: SampleSketch(), layerRenderer: layerRenderer) 37 | renderer.startRenderLoop() 38 | } 39 | }.immersionStyle(selection: .constant(.full), in: .full) 40 | } 41 | } 42 | 43 | -------------------------------------------------------------------------------- /Sources/SwiftyCreatives/Resources/Shaders/Types.metal: -------------------------------------------------------------------------------- 1 | // 2 | // Types.metal 3 | // 4 | // 5 | // Created by Yuki Kuwashima on 2022/12/16. 6 | // 7 | 8 | #include 9 | #include "SharedIndices.h" 10 | using namespace metal; 11 | 12 | struct FrameUniforms_ProjectionMatrix { 13 | float4x4 value; 14 | }; 15 | 16 | struct FrameUniforms_ViewMatrix { 17 | float4x4 value; 18 | }; 19 | 20 | struct FrameUniforms_ModelPos { 21 | float3 value; 22 | }; 23 | 24 | struct FrameUniforms_ModelRot { 25 | float3 value; 26 | }; 27 | 28 | struct FrameUniforms_ModelScale { 29 | float3 value; 30 | }; 31 | 32 | struct FrameUniforms_HasTexture { 33 | bool value; 34 | }; 35 | 36 | struct FrameUniforms_CameraPos { 37 | float3 value; 38 | }; 39 | 40 | struct FrameUniforms_IsActiveToLight { 41 | bool value; 42 | }; 43 | 44 | struct FrameUniforms_CustomMatrix { 45 | float4x4 value; 46 | }; 47 | 48 | struct FrameUniforms_FogDensity { 49 | float value; 50 | }; 51 | 52 | struct FrameUniforms_FogColor { 53 | float4 value; 54 | }; 55 | 56 | struct FrameUniforms_UseVertexColor { 57 | bool value; 58 | }; 59 | 60 | struct RasterizerData { 61 | float4 position [[ position ]]; 62 | float4 color; 63 | float2 uv; 64 | float3 worldPosition; 65 | float3 surfaceNormal; 66 | float3 toCameraVector; 67 | float size [[ point_size ]]; 68 | }; 69 | 70 | struct Vertex { 71 | float3 position [[ attribute(VertexAttribute_Position) ]]; 72 | float2 uv [[ attribute(VertexAttribute_UV) ]]; 73 | float3 normal [[ attribute(VertexAttribute_Normal) ]]; 74 | float4 color [[ attribute(VertexAttribute_Color) ]]; 75 | }; 76 | -------------------------------------------------------------------------------- /Examples/ExampleiOSApp/ExampleiOSApp/SketchSample1.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SketchSample1.swift 3 | // ExampleiOSApp 4 | // 5 | // Created by Yuki Kuwashima on 2022/12/27. 6 | // 7 | 8 | import SwiftyCreatives 9 | import UIKit 10 | 11 | struct MyBox { 12 | var pos: f3 = f3.random(in: -7...7) 13 | var color: f4 = f4.randomPoint(0...1) 14 | var scale: f3 = f3.one * Float.random(in: 0.3...0.5) 15 | } 16 | 17 | final class SketchSample1: Sketch { 18 | 19 | var boxes: [MyBox] = [] 20 | var elapsed: Float = 0.0 21 | 22 | override init() { 23 | super.init() 24 | for _ in 0...100 { 25 | boxes.append(MyBox()) 26 | } 27 | } 28 | 29 | override func update(camera: MainCamera) { 30 | camera.rotateAroundY(deltaTime) 31 | elapsed += deltaTime 32 | } 33 | 34 | override func draw(encoder: SCEncoder, camera: MainCamera) { 35 | let elapsedSin = sin(elapsed) 36 | for b in boxes { 37 | color(elapsedSin, b.color.y, b.color.z, b.color.w) 38 | pushMatrix() 39 | box(b.pos, b.scale) 40 | popMatrix() 41 | } 42 | } 43 | 44 | override func touchesBegan(camera: MainCamera, touchLocations: [f2]) { 45 | let location = touchLocations.first! 46 | let processed = camera.screenToWorldDirection(x: location.x, y: location.y) 47 | let origin = processed.origin 48 | let direction = processed.direction 49 | let finalPos = origin + direction * 20 50 | 51 | var box = MyBox() 52 | box.color = f4.one 53 | box.pos = finalPos 54 | box.scale = f3.one * 0.2 55 | boxes.append(box) 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /Examples/ExampleVisionOS/ExampleVisionOS.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved: -------------------------------------------------------------------------------- 1 | { 2 | "pins" : [ 3 | { 4 | "identity" : "easymetalshader", 5 | "kind" : "remoteSourceControl", 6 | "location" : "https://github.com/yukiny0811/EasyMetalShader.git", 7 | "state" : { 8 | "revision" : "d482c6defd91980b78583a4f567f6be9b0533400", 9 | "version" : "3.1.1" 10 | } 11 | }, 12 | { 13 | "identity" : "fontvertexbuilder", 14 | "kind" : "remoteSourceControl", 15 | "location" : "https://github.com/yukiny0811/FontVertexBuilder", 16 | "state" : { 17 | "revision" : "b34c94e079cd95b03e44ad0bec962f7526e26530", 18 | "version" : "1.0.0" 19 | } 20 | }, 21 | { 22 | "identity" : "simplesimdswift", 23 | "kind" : "remoteSourceControl", 24 | "location" : "https://github.com/yukiny0811/SimpleSimdSwift", 25 | "state" : { 26 | "revision" : "4a0b195450e42f8a46b0a77f183b1ff116696697", 27 | "version" : "1.0.1" 28 | } 29 | }, 30 | { 31 | "identity" : "swift-syntax", 32 | "kind" : "remoteSourceControl", 33 | "location" : "https://github.com/apple/swift-syntax.git", 34 | "state" : { 35 | "revision" : "64889f0c732f210a935a0ad7cda38f77f876262d", 36 | "version" : "509.1.1" 37 | } 38 | }, 39 | { 40 | "identity" : "swiftycoretext", 41 | "kind" : "remoteSourceControl", 42 | "location" : "https://github.com/yukiny0811/SwiftyCoreText", 43 | "state" : { 44 | "revision" : "ed0583f3ecc5c4e4d3ec56c8a9752d6e9a1e9b86", 45 | "version" : "1.0.0" 46 | } 47 | } 48 | ], 49 | "version" : 2 50 | } 51 | -------------------------------------------------------------------------------- /Sources/SwiftyCreatives/PostProcess/Presets/CornerRadiusPostProcessor.swift: -------------------------------------------------------------------------------- 1 | // 2 | // File.swift 3 | // 4 | // 5 | // Created by Yuki Kuwashima on 2024/02/03. 6 | // 7 | 8 | import EasyMetalShader 9 | 10 | @EMComputeShader 11 | public class CornerRadiusPostProcessor { 12 | 13 | public var tex: MTLTexture? 14 | public var radius: Float = 10 15 | 16 | public var impl: String { 17 | "float width = tex.get_width();" 18 | "float height = tex.get_height();" 19 | 20 | "float4 color = tex.read(gid);" 21 | 22 | "if (gid.x < radius) {" 23 | "if (gid.y < radius) {" 24 | "if (distance(float2(gid.x, gid.y), float2(radius, radius)) > radius) {" 25 | "color = float4(0);" 26 | "}" 27 | "} else if (gid.y > height - radius) {" 28 | "if (distance(float2(gid.x, gid.y), float2(radius, height - radius)) > radius) {" 29 | "color = float4(0);" 30 | "}" 31 | "}" 32 | "} else if (gid.x > width - radius) {" 33 | "if (gid.y < radius) {" 34 | "if (distance(float2(gid.x, gid.y), float2(width - radius, radius)) > radius) {" 35 | "color = float4(0);" 36 | "}" 37 | "} else if (gid.y > height - radius) {" 38 | "if (distance(float2(gid.x, gid.y), float2(width - radius, height - radius)) > radius) {" 39 | "color = float4(0);" 40 | "}" 41 | "}" 42 | "}" 43 | 44 | "tex.write(color, gid);" 45 | } 46 | 47 | public var customMetalCode: String { 48 | "" 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /Sources/SwiftyCreatives/Sketch/FunctionBase/FunctionBase+Rotate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // FunctionBase+Rotate.swift 3 | // 4 | // 5 | // Created by Yuki Kuwashima on 2023/03/07. 6 | // 7 | 8 | import simd 9 | import SimpleSimdSwift 10 | 11 | public extension FunctionBase { 12 | 13 | func rotate(_ rad: Float, axis: f3) { 14 | let rotateMatrix = f4x4.createRotation(angle: rad, axis: axis) 15 | self.customMatrix[self.customMatrix.count - 1] = self.customMatrix[self.customMatrix.count - 1] * rotateMatrix 16 | setCustomMatrix() 17 | } 18 | 19 | func rotate(_ rad: Float, _ axisX: Float, _ axisY: Float, _ axisZ: Float) { 20 | let rotateMatrix = f4x4.createRotation(angle: rad, axis: f3(axisX, axisY, axisZ)) 21 | self.customMatrix[self.customMatrix.count - 1] = self.customMatrix[self.customMatrix.count - 1] * rotateMatrix 22 | setCustomMatrix() 23 | } 24 | 25 | func rotateX(_ rad: Float) { 26 | let rotateMatrix = f4x4.createRotation(angle: rad, axis: f3(1, 0, 0)) 27 | self.customMatrix[self.customMatrix.count - 1] = self.customMatrix[self.customMatrix.count - 1] * rotateMatrix 28 | setCustomMatrix() 29 | } 30 | 31 | func rotateY(_ rad: Float) { 32 | let rotateMatrix = f4x4.createRotation(angle: rad, axis: f3(0, 1, 0)) 33 | self.customMatrix[self.customMatrix.count - 1] = self.customMatrix[self.customMatrix.count - 1] * rotateMatrix 34 | setCustomMatrix() 35 | } 36 | 37 | func rotateZ(_ rad: Float) { 38 | let rotateMatrix = f4x4.createRotation(angle: rad, axis: f3(0, 0, 1)) 39 | self.customMatrix[self.customMatrix.count - 1] = self.customMatrix[self.customMatrix.count - 1] * rotateMatrix 40 | setCustomMatrix() 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /Examples/ExampleVisionOS/Packages/RealityKitContent/Sources/RealityKitContent/RealityKitContent.rkassets/Immersive.usda: -------------------------------------------------------------------------------- 1 | #usda 1.0 2 | ( 3 | defaultPrim = "Root" 4 | metersPerUnit = 1 5 | upAxis = "Y" 6 | ) 7 | 8 | def Xform "Root" 9 | { 10 | reorder nameChildren = ["Sphere_Left", "Sphere_Right", "GridMaterial"] 11 | def Sphere "Sphere_Right" ( 12 | active = true 13 | prepend apiSchemas = ["MaterialBindingAPI"] 14 | ) 15 | { 16 | rel material:binding = ( 17 | bindMaterialAs = "weakerThanDescendants" 18 | ) 19 | double radius = 0.1 20 | quatf xformOp:orient = (1, 0, 0, 0) 21 | float3 xformOp:scale = (1, 1, 1) 22 | float3 xformOp:translate = (0.5, 1.5, -1.5) 23 | uniform token[] xformOpOrder = ["xformOp:translate", "xformOp:orient", "xformOp:scale"] 24 | } 25 | 26 | def Sphere "Sphere_Left" ( 27 | active = true 28 | prepend apiSchemas = ["MaterialBindingAPI"] 29 | ) 30 | { 31 | rel material:binding = ( 32 | bindMaterialAs = "weakerThanDescendants" 33 | ) 34 | double radius = 0.1 35 | quatf xformOp:orient = (1, 0, 0, 0) 36 | float3 xformOp:scale = (1, 1, 1) 37 | float3 xformOp:translate = (-0.5, 1.5, -1.5) 38 | uniform token[] xformOpOrder = ["xformOp:translate", "xformOp:orient", "xformOp:scale"] 39 | } 40 | 41 | def "GridMaterial" ( 42 | active = true 43 | prepend references = @Materials/GridMaterial.usda@ 44 | ) 45 | { 46 | float3 xformOp:scale = (1, 1, 1) 47 | uniform token[] xformOpOrder = ["xformOp:translate", "xformOp:orient", "xformOp:scale"] 48 | } 49 | } 50 | 51 | -------------------------------------------------------------------------------- /Examples/ExampleVisionOS/ExampleVisionOS/ContentView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ContentView.swift 3 | // ExampleVisionOS 4 | // 5 | // Created by Yuki Kuwashima on 2024/02/02. 6 | // 7 | 8 | import SwiftUI 9 | import RealityKit 10 | import RealityKitContent 11 | 12 | struct ContentView: View { 13 | 14 | @State private var showImmersiveSpace = false 15 | @State private var immersiveSpaceIsShown = false 16 | 17 | @Environment(\.openImmersiveSpace) var openImmersiveSpace 18 | @Environment(\.dismissImmersiveSpace) var dismissImmersiveSpace 19 | 20 | var body: some View { 21 | VStack { 22 | Model3D(named: "Scene", bundle: realityKitContentBundle) 23 | .padding(.bottom, 50) 24 | 25 | Text("Hello, world!") 26 | 27 | Toggle("Show Immersive Space", isOn: $showImmersiveSpace) 28 | .toggleStyle(.button) 29 | .padding(.top, 50) 30 | } 31 | .padding() 32 | .onChange(of: showImmersiveSpace) { _, newValue in 33 | Task { 34 | if newValue { 35 | switch await openImmersiveSpace(id: "ImmersiveSpace") { 36 | case .opened: 37 | immersiveSpaceIsShown = true 38 | case .error, .userCancelled: 39 | fallthrough 40 | @unknown default: 41 | immersiveSpaceIsShown = false 42 | showImmersiveSpace = false 43 | } 44 | } else if immersiveSpaceIsShown { 45 | await dismissImmersiveSpace() 46 | immersiveSpaceIsShown = false 47 | } 48 | } 49 | } 50 | } 51 | } 52 | 53 | #Preview(windowStyle: .automatic) { 54 | ContentView() 55 | } 56 | -------------------------------------------------------------------------------- /Sources/SwiftyCreatives/Renderers/visionOS/visionOSSettings.swift: -------------------------------------------------------------------------------- 1 | // 2 | // File.swift 3 | // 4 | // 5 | // Created by Yuki Kuwashima on 2024/02/03. 6 | // 7 | 8 | #if os(visionOS) 9 | 10 | import Foundation 11 | import Spatial 12 | import CompositorServices 13 | 14 | extension LayerRenderer.Clock.Instant.Duration { 15 | var timeInterval: TimeInterval { 16 | let nanoseconds = TimeInterval(components.attoseconds / 1_000_000_000) 17 | return TimeInterval(components.seconds) + (nanoseconds / TimeInterval(NSEC_PER_SEC)) 18 | } 19 | } 20 | 21 | func matrix4x4_rotation(radians: Float, axis: SIMD3) -> matrix_float4x4 { 22 | let unitAxis = normalize(axis) 23 | let ct = cosf(radians) 24 | let st = sinf(radians) 25 | let ci = 1 - ct 26 | let x = unitAxis.x, y = unitAxis.y, z = unitAxis.z 27 | return matrix_float4x4.init(columns:(vector_float4( ct + x * x * ci, y * x * ci + z * st, z * x * ci - y * st, 0), 28 | vector_float4(x * y * ci - z * st, ct + y * y * ci, z * y * ci + x * st, 0), 29 | vector_float4(x * z * ci + y * st, y * z * ci - x * st, ct + z * z * ci, 0), 30 | vector_float4( 0, 0, 0, 1))) 31 | } 32 | 33 | func matrix4x4_translation(_ translationX: Float, _ translationY: Float, _ translationZ: Float) -> matrix_float4x4 { 34 | return matrix_float4x4.init(columns:(vector_float4(1, 0, 0, 0), 35 | vector_float4(0, 1, 0, 0), 36 | vector_float4(0, 0, 1, 0), 37 | vector_float4(translationX, translationY, translationZ, 1))) 38 | } 39 | 40 | func radians_from_degrees(_ degrees: Float) -> Float { 41 | return (degrees / 180) * .pi 42 | } 43 | 44 | #endif 45 | -------------------------------------------------------------------------------- /Examples/ExampleVisionOS/Packages/RealityKitContent/Sources/RealityKitContent/RealityKitContent.rkassets/Scene.usda: -------------------------------------------------------------------------------- 1 | #usda 1.0 2 | ( 3 | defaultPrim = "Root" 4 | metersPerUnit = 1 5 | upAxis = "Y" 6 | ) 7 | 8 | def Xform "Root" 9 | { 10 | reorder nameChildren = ["GridMaterial", "Sphere"] 11 | rel material:binding = None ( 12 | bindMaterialAs = "weakerThanDescendants" 13 | ) 14 | 15 | def Sphere "Sphere" ( 16 | active = true 17 | prepend apiSchemas = ["MaterialBindingAPI"] 18 | ) 19 | { 20 | rel material:binding = ( 21 | bindMaterialAs = "weakerThanDescendants" 22 | ) 23 | double radius = 0.05 24 | quatf xformOp:orient = (1, 0, 0, 0) 25 | float3 xformOp:scale = (1, 1, 1) 26 | float3 xformOp:translate = (0, 0, 0.0004) 27 | uniform token[] xformOpOrder = ["xformOp:translate", "xformOp:orient", "xformOp:scale"] 28 | 29 | def RealityKitComponent "Collider" 30 | { 31 | uint group = 1 32 | uniform token info:id = "RealityKit.Collider" 33 | uint mask = 4294967295 34 | token type = "Default" 35 | 36 | def RealityKitStruct "Shape" 37 | { 38 | float3 extent = (0.2, 0.2, 0.2) 39 | float radius = 0.05 40 | token shapeType = "Sphere" 41 | } 42 | } 43 | 44 | def RealityKitComponent "InputTarget" 45 | { 46 | uniform token info:id = "RealityKit.InputTarget" 47 | } 48 | } 49 | 50 | def "GridMaterial" ( 51 | active = true 52 | prepend references = @Materials/GridMaterial.usda@ 53 | ) 54 | { 55 | float3 xformOp:scale = (1, 1, 1) 56 | uniform token[] xformOpOrder = ["xformOp:translate", "xformOp:orient", "xformOp:scale"] 57 | } 58 | } 59 | 60 | -------------------------------------------------------------------------------- /Sources/SwiftyCreatives/Primitives/PrimitiveUtils/Primitive.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Primitive.swift 3 | // 4 | // 5 | // Created by Yuki Kuwashima on 2022/12/17. 6 | // 7 | 8 | import simd 9 | 10 | open class Primitive: ScaleSettable { 11 | 12 | open var hasTexture: [Bool] = [false] 13 | open var isActiveToLight: [Bool] = [false] 14 | 15 | internal var _mScale: [f3] = [f3.one] 16 | 17 | public var scale: f3 { _mScale[0] } 18 | 19 | init() {} 20 | 21 | @discardableResult 22 | public func setScale(_ value: f3) -> Self { 23 | _mScale[0] = value 24 | return self 25 | } 26 | 27 | public func draw(_ encoder: SCEncoder) { 28 | encoder.setVertexBytes(Info.vertices, length: Info.vertices.count * f3.memorySize, index: VertexBufferIndex.Position.rawValue) 29 | encoder.setVertexBytes(_mScale, length: f3.memorySize, index: VertexBufferIndex.ModelScale.rawValue) 30 | encoder.setVertexBytes(Info.uvs, length: Info.uvs.count * f2.memorySize, index: VertexBufferIndex.UV.rawValue) 31 | encoder.setVertexBytes(Info.normals, length: Info.normals.count * f3.memorySize, index: VertexBufferIndex.Normal.rawValue) 32 | encoder.setVertexBytes([f4](repeating: .zero, count: Info.vertices.count), length: Info.vertices.count * f4.memorySize, index: VertexBufferIndex.VertexColor.rawValue) 33 | encoder.setFragmentBytes(self.hasTexture, length: Bool.memorySize, index: FragmentBufferIndex.HasTexture.rawValue) 34 | encoder.setFragmentBytes(self.isActiveToLight, length: Bool.memorySize, index: FragmentBufferIndex.IsActiveToLight.rawValue) 35 | encoder.drawPrimitives(type: Info.primitiveType, vertexStart: 0, vertexCount: Info.vertices.count) 36 | } 37 | 38 | // util functions 39 | @discardableResult 40 | public func multiplyScale(_ value: Float) -> Self { 41 | _mScale[0] *= value 42 | return self 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /Tests/SwiftyCreativesTests/SnapshotTests/Functions/PushPopTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // PushPopTests.swift 3 | // 4 | // 5 | // Created by Yuki Kuwashima on 2023/03/28. 6 | // 7 | 8 | @testable import SwiftyCreatives 9 | import XCTest 10 | import SwiftUI 11 | import SnapshotTesting 12 | import MetalKit 13 | 14 | #if os(macOS) 15 | final class PushPopTests: XCTestCase { 16 | 17 | @MainActor 18 | func testPushPopWorking() async throws { 19 | try SnapshotTestUtil.testGPU() 20 | class TestSketch: SketchForTest { 21 | override func draw(encoder: SCEncoder) { 22 | pushMatrix() 23 | translate(5, 0, 0) 24 | color(1) 25 | box(3) 26 | popMatrix() 27 | color(1, 0, 1) 28 | box(3) 29 | } 30 | } 31 | let expectation = XCTestExpectation() 32 | let sketch = TestSketch(expectation, testName: "testPushPopWorking") 33 | SnapshotTestUtil.render(sketch: sketch) 34 | await fulfillment(of: [expectation], timeout: 5.0) 35 | } 36 | 37 | @MainActor 38 | func testNestedPushPopWorking() async throws { 39 | try SnapshotTestUtil.testGPU() 40 | class TestSketch: SketchForTest { 41 | override func draw(encoder: SCEncoder) { 42 | pushMatrix() 43 | translate(5, 0, 0) 44 | color(1) 45 | box(3) 46 | pushMatrix() 47 | translate(0, 5, 0) 48 | color(1, 1, 0) 49 | box(3) 50 | popMatrix() 51 | popMatrix() 52 | color(1, 0, 1) 53 | box(3) 54 | } 55 | } 56 | let expectation = XCTestExpectation() 57 | let sketch = TestSketch(expectation, testName: "testNestedPushPopWorking") 58 | SnapshotTestUtil.render(sketch: sketch) 59 | await fulfillment(of: [expectation], timeout: 5.0) 60 | } 61 | } 62 | #endif 63 | -------------------------------------------------------------------------------- /Examples/ExampleiOSApp/ExampleiOSApp.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved: -------------------------------------------------------------------------------- 1 | { 2 | "pins" : [ 3 | { 4 | "identity" : "easymetalshader", 5 | "kind" : "remoteSourceControl", 6 | "location" : "https://github.com/yukiny0811/EasyMetalShader.git", 7 | "state" : { 8 | "revision" : "d482c6defd91980b78583a4f567f6be9b0533400", 9 | "version" : "3.1.1" 10 | } 11 | }, 12 | { 13 | "identity" : "fontvertexbuilder", 14 | "kind" : "remoteSourceControl", 15 | "location" : "https://github.com/yukiny0811/FontVertexBuilder", 16 | "state" : { 17 | "revision" : "b34c94e079cd95b03e44ad0bec962f7526e26530", 18 | "version" : "1.0.0" 19 | } 20 | }, 21 | { 22 | "identity" : "simplesimdswift", 23 | "kind" : "remoteSourceControl", 24 | "location" : "https://github.com/yukiny0811/SimpleSimdSwift", 25 | "state" : { 26 | "revision" : "4a0b195450e42f8a46b0a77f183b1ff116696697", 27 | "version" : "1.0.1" 28 | } 29 | }, 30 | { 31 | "identity" : "swift-snapshot-testing", 32 | "kind" : "remoteSourceControl", 33 | "location" : "https://github.com/pointfreeco/swift-snapshot-testing", 34 | "state" : { 35 | "revision" : "e7b77228b34057041374ebef00c0fd7739d71a2b", 36 | "version" : "1.15.3" 37 | } 38 | }, 39 | { 40 | "identity" : "swift-syntax", 41 | "kind" : "remoteSourceControl", 42 | "location" : "https://github.com/apple/swift-syntax.git", 43 | "state" : { 44 | "revision" : "64889f0c732f210a935a0ad7cda38f77f876262d", 45 | "version" : "509.1.1" 46 | } 47 | }, 48 | { 49 | "identity" : "swiftycoretext", 50 | "kind" : "remoteSourceControl", 51 | "location" : "https://github.com/yukiny0811/SwiftyCoreText", 52 | "state" : { 53 | "revision" : "ed0583f3ecc5c4e4d3ec56c8a9752d6e9a1e9b86", 54 | "version" : "1.0.0" 55 | } 56 | } 57 | ], 58 | "version" : 2 59 | } 60 | -------------------------------------------------------------------------------- /.swiftpm/xcode/xcshareddata/xcschemes/SwiftyCreativesTests.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 14 | 15 | 17 | 23 | 24 | 25 | 26 | 27 | 37 | 38 | 44 | 45 | 47 | 48 | 51 | 52 | 53 | -------------------------------------------------------------------------------- /Sources/SwiftyCreatives/Views/TouchableMTKView/TouchableMTKView+Configure.swift: -------------------------------------------------------------------------------- 1 | // 2 | // TouchableMTKView+Configure.swift 3 | // 4 | // 5 | // Created by Yuki Kuwashima on 2023/05/16. 6 | // 7 | 8 | import MetalKit 9 | 10 | #if !os(visionOS) 11 | 12 | extension TouchableMTKView { 13 | func configure() { 14 | #if os(macOS) 15 | configureMacOS() 16 | #elseif os(iOS) 17 | configureiOS() 18 | #endif 19 | } 20 | } 21 | 22 | extension TouchableMTKView { 23 | #if os(macOS) 24 | func configureMacOS() { 25 | let options: NSTrackingArea.Options = [ 26 | .mouseMoved, 27 | .activeAlways, 28 | .inVisibleRect 29 | ] 30 | let trackingArea = NSTrackingArea(rect: bounds, options: options, owner: self, userInfo: nil) 31 | self.addTrackingArea(trackingArea) 32 | } 33 | #endif 34 | 35 | #if os(iOS) 36 | func configureiOS() { 37 | let scrollGestureRecognizer = UIPanGestureRecognizer(target: self, action: #selector(onScroll)) 38 | scrollGestureRecognizer.allowedScrollTypesMask = .continuous 39 | scrollGestureRecognizer.minimumNumberOfTouches = 2 40 | scrollGestureRecognizer.maximumNumberOfTouches = 2 41 | self.addGestureRecognizer(scrollGestureRecognizer) 42 | 43 | let pinch = UIPinchGestureRecognizer(target: self, action: #selector(onPinch)) 44 | pinch.delegate = self 45 | addGestureRecognizer(pinch) 46 | 47 | let rotationRecognizer = UIRotationGestureRecognizer(target: self, action: #selector(onRotate)) 48 | rotationRecognizer.delegate = self 49 | addGestureRecognizer(rotationRecognizer) 50 | } 51 | #endif 52 | } 53 | 54 | #if os(iOS) 55 | extension TouchableMTKView: UIGestureRecognizerDelegate { 56 | public func gestureRecognizer( 57 | _ gestureRecognizer: UIGestureRecognizer, 58 | shouldRecognizeSimultaneouslyWith otherGestureRecognizer: UIGestureRecognizer 59 | ) -> Bool { 60 | return true 61 | } 62 | } 63 | #endif 64 | 65 | #endif 66 | -------------------------------------------------------------------------------- /Sources/SwiftyCreatives/Sketch/FunctionBase/FunctionBase+HitTestableBox.swift: -------------------------------------------------------------------------------- 1 | // 2 | // FunctionBase+HitTestableBox.swift 3 | // 4 | // 5 | // Created by Yuki Kuwashima on 2023/03/08. 6 | // 7 | 8 | import simd 9 | import SimpleSimdSwift 10 | 11 | public extension FunctionBase { 12 | func box(_ hitTestableBox: HitTestableBox) { 13 | setUniforms(modelPos: .zero, modelScale: .one, hasTexture: false) 14 | 15 | push { 16 | push { 17 | translate(0, 0, hitTestableBox.scale.z) 18 | hitTestableBox.front.drawWithCache(encoder: privateEncoder!, customMatrix: self.customMatrix.reduce(f4x4.createIdentity(), *)) 19 | } 20 | push { 21 | translate(0, 0, -hitTestableBox.scale.z) 22 | hitTestableBox.back.drawWithCache(encoder: privateEncoder!, customMatrix: self.customMatrix.reduce(f4x4.createIdentity(), *)) 23 | } 24 | push { 25 | rotateY(Float.pi/2) 26 | translate(0, 0, hitTestableBox.scale.x) 27 | hitTestableBox.l.drawWithCache(encoder: privateEncoder!, customMatrix: self.customMatrix.reduce(f4x4.createIdentity(), *)) 28 | } 29 | push { 30 | rotateY(Float.pi/2) 31 | translate(0, 0, -hitTestableBox.scale.x) 32 | hitTestableBox.r.drawWithCache(encoder: privateEncoder!, customMatrix: self.customMatrix.reduce(f4x4.createIdentity(), *)) 33 | } 34 | push { 35 | rotateX(Float.pi/2) 36 | translate(0, 0, hitTestableBox.scale.y) 37 | hitTestableBox.top.drawWithCache(encoder: privateEncoder!, customMatrix: self.customMatrix.reduce(f4x4.createIdentity(), *)) 38 | } 39 | push { 40 | rotateX(Float.pi/2) 41 | translate(0, 0, -hitTestableBox.scale.y) 42 | hitTestableBox.bottom.drawWithCache(encoder: privateEncoder!, customMatrix: self.customMatrix.reduce(f4x4.createIdentity(), *)) 43 | } 44 | } 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /Tests/SwiftyCreativesTests/SnapshotTests/Functions/RectTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // RectTests.swift 3 | // 4 | // 5 | // Created by Yuki Kuwashima on 2023/03/27. 6 | // 7 | 8 | @testable import SwiftyCreatives 9 | import XCTest 10 | import SwiftUI 11 | import SnapshotTesting 12 | import MetalKit 13 | 14 | #if os(macOS) 15 | final class RectTests: XCTestCase { 16 | 17 | @MainActor 18 | func testRectIsDrawed() async throws { 19 | try SnapshotTestUtil.testGPU() 20 | class TestSketch: SketchForTest { 21 | override func draw(encoder: SCEncoder) { 22 | color(1) 23 | rect(3) 24 | } 25 | } 26 | let expectation = XCTestExpectation() 27 | let sketch = TestSketch(expectation, testName: "testRectIsDrawed") 28 | SnapshotTestUtil.render(sketch: sketch) 29 | await fulfillment(of: [expectation], timeout: 5.0) 30 | } 31 | 32 | @MainActor 33 | func testRectPosWorking() async throws { 34 | try SnapshotTestUtil.testGPU() 35 | class TestSketch: SketchForTest { 36 | override func draw(encoder: SCEncoder) { 37 | color(1) 38 | rect(1, 1, 1, 2, 3) 39 | } 40 | } 41 | let expectation = XCTestExpectation() 42 | let sketch = TestSketch(expectation, testName: "testRectPosWorking") 43 | SnapshotTestUtil.render(sketch: sketch) 44 | await fulfillment(of: [expectation], timeout: 5.0) 45 | } 46 | 47 | @MainActor 48 | func testRectColorWorking() async throws { 49 | try SnapshotTestUtil.testGPU() 50 | class TestSketch: SketchForTest { 51 | override func draw(encoder: SCEncoder) { 52 | color(1, 0.5, 0.2, 0.8) 53 | rect(3) 54 | } 55 | } 56 | let expectation = XCTestExpectation() 57 | let sketch = TestSketch(expectation, testName: "testRectColorWorking") 58 | SnapshotTestUtil.render(sketch: sketch) 59 | await fulfillment(of: [expectation], timeout: 5.0) 60 | } 61 | } 62 | #endif 63 | -------------------------------------------------------------------------------- /Tests/SwiftyCreativesTests/SnapshotTests/Functions/CircleTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CircleTests.swift 3 | // 4 | // 5 | // Created by Yuki Kuwashima on 2023/03/27. 6 | // 7 | 8 | @testable import SwiftyCreatives 9 | import XCTest 10 | import SwiftUI 11 | import SnapshotTesting 12 | import MetalKit 13 | 14 | #if os(macOS) 15 | final class CircleTests: XCTestCase { 16 | 17 | @MainActor 18 | func testCircleIsDrawed() async throws { 19 | try SnapshotTestUtil.testGPU() 20 | class TestSketch: SketchForTest { 21 | override func draw(encoder: SCEncoder) { 22 | color(1) 23 | circle(3) 24 | } 25 | } 26 | let expectation = XCTestExpectation() 27 | let sketch = TestSketch(expectation, testName: "testCircleIsDrawed") 28 | SnapshotTestUtil.render(sketch: sketch) 29 | await fulfillment(of: [expectation], timeout: 5.0) 30 | } 31 | 32 | @MainActor 33 | func testCircleParameterWorking() async throws { 34 | try SnapshotTestUtil.testGPU() 35 | class TestSketch: SketchForTest { 36 | override func draw(encoder: SCEncoder) { 37 | color(1) 38 | circle(1, 1, 1, 2, 3) 39 | } 40 | } 41 | let expectation = XCTestExpectation() 42 | let sketch = TestSketch(expectation, testName: "testCircleParameterWorking") 43 | SnapshotTestUtil.render(sketch: sketch) 44 | await fulfillment(of: [expectation], timeout: 5.0) 45 | } 46 | 47 | @MainActor 48 | func testCircleColorWorking() async throws { 49 | try SnapshotTestUtil.testGPU() 50 | class TestSketch: SketchForTest { 51 | override func draw(encoder: SCEncoder) { 52 | color(1, 0.5, 0.2, 0.8) 53 | circle(3) 54 | } 55 | } 56 | let expectation = XCTestExpectation() 57 | let sketch = TestSketch(expectation, testName: "testCircleColorWorking") 58 | SnapshotTestUtil.render(sketch: sketch) 59 | await fulfillment(of: [expectation], timeout: 5.0) 60 | } 61 | } 62 | #endif 63 | -------------------------------------------------------------------------------- /Examples/ExampleiOSApp/ExampleiOSApp/SketchSample2.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SketchSample2.swift 3 | // ExampleiOSApp 4 | // 5 | // Created by Yuki Kuwashima on 2022/12/27. 6 | // 7 | 8 | import SwiftyCreatives 9 | import UIKit 10 | 11 | class RotatingViewObject: UIViewObject { 12 | @SCAnimatable var rotation: Float = 0 13 | } 14 | 15 | final class SketchSample2: Sketch { 16 | 17 | let postProcessor = CornerRadiusPostProcessor() 18 | var viewObj = RotatingViewObject() 19 | 20 | override init() { 21 | super.init() 22 | let view: TestView = TestView.fromNib(type: TestView.self) 23 | view.onHit = { 24 | self.viewObj.rotation += Float.pi / 2 25 | } 26 | viewObj.load(view: view) 27 | viewObj.multiplyScale(6) 28 | 29 | let dispatch = EMMetalDispatch() 30 | dispatch.compute { [weak self] encoder in 31 | guard let self else { return } 32 | postProcessor.tex = viewObj.texture 33 | postProcessor.radius = 50 34 | postProcessor.dispatch(encoder, textureSizeReference: viewObj.texture!) 35 | } 36 | dispatch.commit() 37 | } 38 | 39 | override func update(camera: MainCamera) { 40 | viewObj.$rotation.update(multiplier: deltaTime * 5) 41 | } 42 | 43 | override func draw(encoder: SCEncoder, camera: MainCamera) { 44 | rotateZ(viewObj.$rotation.animationValue) 45 | viewObj.drawWithCache(encoder: encoder, customMatrix: getCustomMatrix()) 46 | } 47 | 48 | override func touchesBegan(camera: MainCamera, touchLocations: [f2]) { 49 | let location = touchLocations.first! 50 | let processed = camera.screenToWorldDirection(x: location.x, y: location.y) 51 | let origin = processed.origin 52 | let direction = processed.direction 53 | viewObj.buttonTest(origin: origin, direction: direction) 54 | } 55 | } 56 | 57 | class TestView: UIView { 58 | 59 | var onHit: (() -> Void)? 60 | 61 | @IBAction func onButtonTap() { 62 | if let onHit = onHit { 63 | onHit() 64 | } 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /Tests/SwiftyCreativesTests/SnapshotTests/Functions/TriangleTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // TriangleTests.swift 3 | // 4 | // 5 | // Created by Yuki Kuwashima on 2023/03/27. 6 | // 7 | 8 | @testable import SwiftyCreatives 9 | import XCTest 10 | import SwiftUI 11 | import SnapshotTesting 12 | import MetalKit 13 | 14 | #if os(macOS) 15 | final class TriangleTests: XCTestCase { 16 | 17 | @MainActor 18 | func testTriangleIsDrawed() async throws { 19 | try SnapshotTestUtil.testGPU() 20 | class TestSketch: SketchForTest { 21 | override func draw(encoder: SCEncoder) { 22 | color(1) 23 | triangle(3) 24 | } 25 | } 26 | let expectation = XCTestExpectation() 27 | let sketch = TestSketch(expectation, testName: "testTriangleIsDrawed") 28 | SnapshotTestUtil.render(sketch: sketch) 29 | await fulfillment(of: [expectation], timeout: 5.0) 30 | } 31 | 32 | @MainActor 33 | func testTrianglePosWorking() async throws { 34 | try SnapshotTestUtil.testGPU() 35 | class TestSketch: SketchForTest { 36 | override func draw(encoder: SCEncoder) { 37 | color(1) 38 | triangle(1, 1, 1, 2, 3) 39 | } 40 | } 41 | let expectation = XCTestExpectation() 42 | let sketch = TestSketch(expectation, testName: "testTrianglePosWorking") 43 | SnapshotTestUtil.render(sketch: sketch) 44 | await fulfillment(of: [expectation], timeout: 5.0) 45 | } 46 | 47 | @MainActor 48 | func testTriangleColorWorking() async throws { 49 | try SnapshotTestUtil.testGPU() 50 | class TestSketch: SketchForTest { 51 | override func draw(encoder: SCEncoder) { 52 | color(1, 0.5, 0.2, 0.8) 53 | triangle(3) 54 | } 55 | } 56 | let expectation = XCTestExpectation() 57 | let sketch = TestSketch(expectation, testName: "testTriangleColorWorking") 58 | SnapshotTestUtil.render(sketch: sketch) 59 | await fulfillment(of: [expectation], timeout: 5.0) 60 | } 61 | } 62 | #endif 63 | -------------------------------------------------------------------------------- /Examples/ExampleMacOSApp/ExampleMacOSApp/Samples/Sample5.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Sample5.swift 3 | // ExampleMacOSApp 4 | // 5 | // Created by Yuki Kuwashima on 2023/02/10. 6 | // 7 | 8 | import SwiftyCreatives 9 | import CoreGraphics 10 | import AppKit 11 | import SwiftUI 12 | 13 | final class Sample5: Sketch { 14 | 15 | var tree = "F" 16 | 17 | override init() { 18 | super.init() 19 | for _ in 0..<4 { 20 | var currentTree = "" 21 | for t in tree { 22 | if t == "F" { 23 | currentTree += "FyF+[+FyF-yB]-y[-yF+YP]" 24 | } else { 25 | currentTree += String(t) 26 | } 27 | } 28 | tree = currentTree 29 | } 30 | } 31 | 32 | override func setupCamera(camera: MainCamera) { 33 | camera.setTranslate(0, -20, -40) 34 | } 35 | 36 | override func update(camera: MainCamera) { 37 | camera.rotateAroundY(0.01) 38 | } 39 | 40 | override func draw(encoder: SCEncoder, camera: MainCamera) { 41 | for t in tree { 42 | compile(char: t) 43 | } 44 | } 45 | 46 | func compile(char: Character) { 47 | switch char { 48 | case "B": 49 | color(0.5, 0.5, 1.0, 1.0) 50 | boldline(0, 0, 0, 0, 1, 0, width: 0.1) 51 | translate(0, 1, 0) 52 | case "P": 53 | color(1.0, 0.3, 1.0, 1.0) 54 | boldline(0, 0, 0, 0, 1, 0, width: 0.1) 55 | translate(0, 1, 0) 56 | case "F": 57 | color(0.9, 1.0, 1.0, 0.8) 58 | boldline(0, 0, 0, 0, 1, 0, width: 0.1) 59 | translate(0, 1, 0) 60 | case "+": 61 | rotateZ(0.3) 62 | case "-": 63 | rotateZ(-0.3) 64 | case "[": 65 | pushMatrix() 66 | case "]": 67 | popMatrix() 68 | case "y": 69 | rotateY(0.15) 70 | case "Y": 71 | rotateY(-0.15) 72 | default: 73 | break 74 | } 75 | } 76 | } 77 | 78 | struct Sample5View: View { 79 | var body: some View { 80 | SketchView(Sample5()) 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /Sources/SwiftyCreativesMacro/SketchObject/SketchObject.swift: -------------------------------------------------------------------------------- 1 | // 2 | // File.swift 3 | // 4 | // 5 | // Created by Yuki Kuwashima on 2024/02/04. 6 | // 7 | 8 | import SwiftCompilerPlugin 9 | import SwiftSyntax 10 | import SwiftSyntaxBuilder 11 | import SwiftSyntaxMacros 12 | 13 | public struct SketchObject {} 14 | 15 | extension SketchObject: ExtensionMacro { 16 | public static func expansion( 17 | of node: SwiftSyntax.AttributeSyntax, 18 | attachedTo declaration: some SwiftSyntax.DeclGroupSyntax, 19 | providingExtensionsOf type: some SwiftSyntax.TypeSyntaxProtocol, 20 | conformingTo protocols: [SwiftSyntax.TypeSyntax], 21 | in context: some SwiftSyntaxMacros.MacroExpansionContext 22 | ) throws -> [SwiftSyntax.ExtensionDeclSyntax] { 23 | 24 | if protocols.isEmpty { return [] } 25 | 26 | let thisExtension1: DeclSyntax = 27 | """ 28 | extension \(type.trimmed): FunctionBase {} 29 | """ 30 | 31 | let thisExtension2: DeclSyntax = 32 | """ 33 | extension \(type.trimmed): SketchObjectHasDraw {} 34 | """ 35 | 36 | guard let extensionDecl1 = thisExtension1.as(ExtensionDeclSyntax.self) else { 37 | return [] 38 | } 39 | guard let extensionDecl2 = thisExtension2.as(ExtensionDeclSyntax.self) else { 40 | return [] 41 | } 42 | 43 | return [extensionDecl1, extensionDecl2] 44 | } 45 | } 46 | 47 | extension SketchObject: MemberMacro { 48 | 49 | public static func expansion( 50 | of node: AttributeSyntax, 51 | providingMembersOf declaration: some DeclGroupSyntax, 52 | conformingTo protocols: [TypeSyntax], 53 | in context: some MacroExpansionContext 54 | ) throws -> [DeclSyntax] { 55 | if protocols.isEmpty { return [] } 56 | 57 | let thisDecl1: DeclSyntax = 58 | """ 59 | public var privateEncoder: SCEncoder? 60 | """ 61 | 62 | let thisDecl2: DeclSyntax = 63 | """ 64 | public var customMatrix: [f4x4] = [] 65 | """ 66 | 67 | return [thisDecl1, thisDecl2] 68 | } 69 | 70 | } 71 | -------------------------------------------------------------------------------- /Sources/SwiftyCreatives/Sketch/FunctionBase/FunctionBase+Triangle.swift: -------------------------------------------------------------------------------- 1 | // 2 | // FunctionBase+Triangle.swift 3 | // 4 | // 5 | // Created by Yuki Kuwashima on 2023/03/07. 6 | // 7 | 8 | import simd 9 | import SimpleSimdSwift 10 | 11 | public extension FunctionBase { 12 | 13 | func triangle(_ x: Float, _ y: Float, _ z: Float, _ scaleX: Float, _ scaleY: Float) { 14 | setUniforms(modelPos: f3(x, y, z), modelScale: f3(scaleX, scaleY, 1), hasTexture: false) 15 | setVertices(TriangleInfo.vertices) 16 | setUVs(TriangleInfo.uvs) 17 | setNormals(TriangleInfo.normals) 18 | privateEncoder?.drawPrimitives(type: TriangleInfo.primitiveType, vertexStart: 0, vertexCount: TriangleInfo.vertices.count) 19 | } 20 | 21 | func triangle(_ pos: f3, _ scaleX: Float, _ scaleY: Float) { 22 | setUniforms(modelPos: pos, modelScale: f3(scaleX, scaleY, 1), hasTexture: false) 23 | setVertices(TriangleInfo.vertices) 24 | setUVs(TriangleInfo.uvs) 25 | setNormals(TriangleInfo.normals) 26 | privateEncoder?.drawPrimitives(type: TriangleInfo.primitiveType, vertexStart: 0, vertexCount: TriangleInfo.vertices.count) 27 | } 28 | 29 | func triangle(_ x: Float, _ y: Float, _ z: Float, _ scale: Float) { 30 | setUniforms(modelPos: f3(x, y, z), modelScale: f3(scale, scale, 1), hasTexture: false) 31 | setVertices(TriangleInfo.vertices) 32 | setUVs(TriangleInfo.uvs) 33 | setNormals(TriangleInfo.normals) 34 | privateEncoder?.drawPrimitives(type: TriangleInfo.primitiveType, vertexStart: 0, vertexCount: TriangleInfo.vertices.count) 35 | } 36 | 37 | func triangle(_ scale: Float) { 38 | setUniforms(modelPos: .zero, modelScale: f3(scale, scale, 1), hasTexture: false) 39 | setVertices(TriangleInfo.vertices) 40 | setUVs(TriangleInfo.uvs) 41 | setNormals(TriangleInfo.normals) 42 | privateEncoder?.drawPrimitives(type: TriangleInfo.primitiveType, vertexStart: 0, vertexCount: TriangleInfo.vertices.count) 43 | } 44 | 45 | func triangle(_ scaleX: Float, _ scaleY: Float) { 46 | setUniforms(modelPos: .zero, modelScale: f3(scaleX, scaleY, 1), hasTexture: false) 47 | setVertices(TriangleInfo.vertices) 48 | setUVs(TriangleInfo.uvs) 49 | setNormals(TriangleInfo.normals) 50 | privateEncoder?.drawPrimitives(type: TriangleInfo.primitiveType, vertexStart: 0, vertexCount: TriangleInfo.vertices.count) 51 | } 52 | } 53 | 54 | -------------------------------------------------------------------------------- /Sources/SwiftyCreatives/Primitives/Texts/Text/Text2D.swift: -------------------------------------------------------------------------------- 1 | // 2 | // File.swift 3 | // 4 | // 5 | // Created by Yuki Kuwashima on 2024/02/03. 6 | // 7 | 8 | import Metal 9 | import CoreText 10 | import SimpleSimdSwift 11 | import FontVertexBuilder 12 | 13 | open class Text2D: PathText { 14 | public var posBuffer: MTLBuffer? 15 | public var finalVertices: [simd_double3] = [] 16 | public var finalVerticesNormalized: [simd_double3] = [] 17 | func createAndSetBuffer(from triangulatedPaths: [TriangulatedLetterPath]) throws { 18 | finalVertices = [] 19 | for letter in triangulatedPaths { 20 | for portion in letter.glyphLines { 21 | finalVertices += portion 22 | } 23 | } 24 | if finalVertices.count == 0 { 25 | throw TextBufferCreationError.noVertices 26 | } 27 | var largestX: Double = 0 28 | var largestY: Double = 0 29 | for vert in finalVertices { 30 | if vert.x > largestX { 31 | largestX = vert.x 32 | } 33 | if vert.y < largestY { 34 | largestY = vert.y 35 | } 36 | } 37 | finalVerticesNormalized = finalVertices.map { simd_double3($0.x / largestX, $0.y / largestY, 0)} 38 | posBuffer = ShaderCore.device.makeBuffer( 39 | bytes: finalVertices.map { f3($0) }, 40 | length: finalVertices.count * f3.memorySize 41 | ) 42 | } 43 | public override init( 44 | text: String, 45 | fontName: String = "Avenir-BlackOblique", 46 | fontSize: Double = 10.0, 47 | bounds: CGSize = .zero, 48 | pivot: simd_double2 = .zero, 49 | textAlignment: CTTextAlignment = .natural, 50 | verticalAlignment: PathText.VerticalAlignment = .center, 51 | kern: Double = 0.0, 52 | lineSpacing: Double = 0.0, 53 | maxDepth: Int = 1 54 | ) { 55 | super.init( 56 | text: text, 57 | fontName: fontName, 58 | fontSize: fontSize, 59 | bounds: bounds, 60 | pivot: pivot, 61 | textAlignment: textAlignment, 62 | verticalAlignment: verticalAlignment, 63 | kern: kern, 64 | lineSpacing: lineSpacing, 65 | maxDepth: maxDepth 66 | ) 67 | let triangulatedPaths = GlyphUtil.MainFunctions.triangulate(self.calculatedPaths) 68 | try? createAndSetBuffer(from: triangulatedPaths) 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /Examples/ExampleMacOSApp/ExampleMacOSApp/ExampleMacOSApp.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ExampleMacOSApp.swift 3 | // ExampleMacOSApp 4 | // 5 | // Created by Yuki Kuwashima on 2022/12/15. 6 | // 7 | 8 | import SwiftUI 9 | import SwiftyCreatives 10 | 11 | @main 12 | struct ExampleMacOSApp: App { 13 | var body: some Scene { 14 | WindowGroup { 15 | NavigationSplitView { 16 | List { 17 | Section("Examples") { 18 | NavigationLink("1 Blooming Boxes") { 19 | Sample1View() 20 | } 21 | NavigationLink("2 Rectangle") { 22 | Sample2View() 23 | } 24 | NavigationLink("3 Box") { 25 | Sample3View() 26 | } 27 | NavigationLink("4 Colorful Boxes") { 28 | Sample4View() 29 | } 30 | NavigationLink("5 Tree (L-system)") { 31 | Sample5View() 32 | } 33 | NavigationLink("6 Fog") { 34 | Sample6View() 35 | } 36 | NavigationLink("7 Rect with hit test") { 37 | Sample7View() 38 | } 39 | NavigationLink("8 Box with hit test") { 40 | Sample8View() 41 | } 42 | NavigationLink("9 fov") { 43 | Sample9View() 44 | } 45 | NavigationLink("10 Text2D") { 46 | Sample10View() 47 | } 48 | } 49 | Section("Features") { 50 | NavigationLink("Feature1") { 51 | Feature1.VIEW() 52 | } 53 | NavigationLink("Feature2") { 54 | Feature2.VIEW() 55 | } 56 | NavigationLink("Feature3") { 57 | Feature3.VIEW() 58 | } 59 | } 60 | } 61 | } detail: { 62 | Text("Choose sample sketch from the sidebar") 63 | .foregroundStyle(.white) 64 | } 65 | .background(.black) 66 | } 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /Sources/SwiftyCreatives/Utils/BlendMode.swift: -------------------------------------------------------------------------------- 1 | // 2 | // BlendMode.swift 3 | // 4 | // 5 | // Created by Yuki Kuwashima on 2022/12/09. 6 | // 7 | 8 | #if os(visionOS) 9 | 10 | import CompositorServices 11 | 12 | public extension RendererBase { 13 | enum BlendMode { 14 | 15 | case normalBlend 16 | case add 17 | case alphaBlend 18 | 19 | public func getRenderer(sketch: Sketch, layerRenderer: LayerRenderer) -> RendererBase { 20 | switch self { 21 | case .normalBlend: 22 | return NormalBlendRendererVision(sketch: sketch, layerRenderer: layerRenderer) 23 | case .add: 24 | if ShaderCore.device.supportsFamily(.apple3) { 25 | return AddBlendRendererVision(sketch: sketch, layerRenderer: layerRenderer) 26 | } else { 27 | return NormalBlendRendererVision(sketch: sketch, layerRenderer: layerRenderer) 28 | } 29 | case .alphaBlend: 30 | if ShaderCore.device.supportsFamily(.apple4) { 31 | return TransparentRendererVision(sketch: sketch, layerRenderer: layerRenderer) 32 | } else { 33 | return NormalBlendRendererVision(sketch: sketch, layerRenderer: layerRenderer) 34 | } 35 | } 36 | } 37 | } 38 | } 39 | 40 | #else 41 | 42 | public extension RendererBase { 43 | 44 | @MainActor 45 | enum BlendMode { 46 | 47 | case normalBlend 48 | case add 49 | case alphaBlend 50 | 51 | public func getRenderer(sketch: Sketch, cameraConfig: CameraConfig, drawConfig: DrawConfig) -> RendererBase { 52 | switch self { 53 | case .normalBlend: 54 | return NormalBlendRenderer(sketch: sketch, cameraConfig: cameraConfig, drawConfig: drawConfig) 55 | case .add: 56 | if ShaderCore.device.supportsFamily(.apple3) { 57 | return AddRenderer(sketch: sketch, cameraConfig: cameraConfig, drawConfig: drawConfig) 58 | } else { 59 | return NormalBlendRenderer(sketch: sketch, cameraConfig: cameraConfig, drawConfig: drawConfig) 60 | } 61 | case .alphaBlend: 62 | if ShaderCore.device.supportsFamily(.apple4) { 63 | return TransparentRenderer(sketch: sketch, cameraConfig: cameraConfig, drawConfig: drawConfig) 64 | } else { 65 | return NormalBlendRenderer(sketch: sketch, cameraConfig: cameraConfig, drawConfig: drawConfig) 66 | } 67 | } 68 | } 69 | } 70 | } 71 | 72 | #endif 73 | -------------------------------------------------------------------------------- /Examples/ExampleMacOSApp/ExampleMacOSApp/FeatureExamples/Feature1.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Feature1.swift 3 | // ExampleMacOSApp 4 | // 5 | // Created by Yuki Kuwashima on 2024/08/03. 6 | // 7 | 8 | import SwiftyCreatives 9 | import SwiftUI 10 | 11 | final class Feature1: Sketch { 12 | 13 | enum StandardGeometry: Int { 14 | case rect 15 | case box 16 | case circle 17 | case triangle 18 | case line 19 | case boldline 20 | case image 21 | case svg 22 | case object3d 23 | mutating func toggleNext() { 24 | self = StandardGeometry(rawValue: self.rawValue + 1) ?? StandardGeometry(rawValue: 0)! 25 | } 26 | } 27 | 28 | var frameCount = 0 29 | var currentGeometry: StandardGeometry = StandardGeometry(rawValue: 0)! 30 | 31 | let imgObj = Img().load(name: "apple", bundle: nil).adjustScale(with: .basedOnWidth).multiplyScale(1.2) 32 | var svgObj: SVGObj? 33 | let modelObj = ModelObject().loadModel(name: "sphere", extensionName: "obj") 34 | 35 | override init() { 36 | super.init() 37 | DispatchQueue.main.async { 38 | Task { 39 | self.svgObj = await SVGObj(url: Bundle.main.url(forResource: "apple", withExtension: "svg")!)? 40 | .normalizeScale(imageAdjustOption: .basedOnWidth) 41 | .makeAnchorCenter() 42 | .changeColors { currentColors in 43 | return currentColors.map { _ in f4.randomPoint(0...1) } 44 | } 45 | } 46 | } 47 | } 48 | 49 | override func update(camera: MainCamera) { 50 | frameCount += 1 51 | if frameCount.isMultiple(of: 300) { 52 | currentGeometry.toggleNext() 53 | } 54 | } 55 | 56 | override func draw(encoder: SCEncoder, camera: MainCamera) { 57 | color(1, 1, 1, 1) 58 | 59 | switch currentGeometry { 60 | case .rect: 61 | rect(1) 62 | case .box: 63 | box(1) 64 | case .circle: 65 | circle(1) 66 | case .triangle: 67 | triangle(1) 68 | case .line: 69 | line(.zero, .one) 70 | case .boldline: 71 | boldline(.zero, .one, width: 0.1) 72 | case .image: 73 | img(imgObj) 74 | case .svg: 75 | if let svgObj { 76 | svg(svgObj) 77 | } 78 | case .object3d: 79 | model(modelObj) 80 | } 81 | } 82 | 83 | struct VIEW: View { 84 | var body: some View { 85 | VStack { 86 | Text("standard geometries") 87 | SketchView(Feature1()) 88 | } 89 | } 90 | } 91 | } 92 | -------------------------------------------------------------------------------- /Sources/SwiftyCreativesSound/Utils/NoiseExtractor.swift: -------------------------------------------------------------------------------- 1 | // 2 | // File.swift 3 | // 4 | // 5 | // Created by Yuki Kuwashima on 2023/07/04. 6 | // 7 | // original code from Apple, modified by Yuki Kuwashima 8 | // 9 | // Copyright © 2023 Apple Inc. 10 | // Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 11 | // 12 | // The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 13 | // 14 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 15 | 16 | import Accelerate 17 | 18 | public enum NoiseExtractor { 19 | public static func extractSignalFromNoise( 20 | sampleCount: Int, 21 | noisySignal: [Float], 22 | threshold: Double, 23 | timeDomainDestination: inout [Float], 24 | frequencyDomainDestination: inout [Float] 25 | ) { 26 | guard let forwardDCTSetup = vDSP.DCT( 27 | count: sampleCount, 28 | transformType: vDSP.DCTTransformType.II 29 | ) else { 30 | return 31 | } 32 | guard let inverseDCTSetup = vDSP.DCT( 33 | count: sampleCount, 34 | transformType: vDSP.DCTTransformType.III 35 | ) else { 36 | return 37 | } 38 | 39 | forwardDCTSetup.transform( 40 | noisySignal, 41 | result: &frequencyDomainDestination 42 | ) 43 | 44 | vDSP.threshold( 45 | frequencyDomainDestination, 46 | to: Float(threshold), 47 | with: .zeroFill, 48 | result: &frequencyDomainDestination 49 | ) 50 | 51 | inverseDCTSetup.transform( 52 | frequencyDomainDestination, 53 | result: &timeDomainDestination 54 | ) 55 | 56 | let divisor = Float(sampleCount / 2) 57 | 58 | vDSP.divide( 59 | timeDomainDestination, 60 | divisor, 61 | result: &timeDomainDestination 62 | ) 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /Tests/SwiftyCreativesTests/SnapshotTests/Functions/BoxTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // BoxTests.swift 3 | // 4 | // 5 | // Created by Yuki Kuwashima on 2023/03/28. 6 | // 7 | 8 | @testable import SwiftyCreatives 9 | import XCTest 10 | import SwiftUI 11 | import SnapshotTesting 12 | import MetalKit 13 | 14 | #if os(macOS) 15 | final class BoxTests: XCTestCase { 16 | 17 | @MainActor 18 | func testBoxIsDrawed() async throws { 19 | try SnapshotTestUtil.testGPU() 20 | class TestSketch: SketchForTest { 21 | override func draw(encoder: SCEncoder) { 22 | color(1) 23 | box(3) 24 | } 25 | } 26 | let expectation = XCTestExpectation() 27 | let sketch = TestSketch(expectation, testName: "testBoxIsDrawed") 28 | SnapshotTestUtil.render(sketch: sketch) 29 | await fulfillment(of: [expectation], timeout: 5.0) 30 | } 31 | 32 | @MainActor 33 | func testBoxColorWorking() async throws { 34 | try SnapshotTestUtil.testGPU() 35 | class TestSketch: SketchForTest { 36 | override func draw(encoder: SCEncoder) { 37 | color(1, 0.5, 0.2, 0.8) 38 | box(3) 39 | } 40 | } 41 | let expectation = XCTestExpectation() 42 | let sketch = TestSketch(expectation, testName: "testBoxColorWorking") 43 | SnapshotTestUtil.render(sketch: sketch) 44 | await fulfillment(of: [expectation], timeout: 5.0) 45 | } 46 | 47 | @MainActor 48 | func testBoxRotationWorking() async throws { 49 | try SnapshotTestUtil.testGPU() 50 | class TestSketch: SketchForTest { 51 | override func draw(encoder: SCEncoder) { 52 | color(1, 0.5, 0.2, 0.8) 53 | rotateX(0.5) 54 | rotateY(0.5) 55 | box(3) 56 | } 57 | } 58 | let expectation = XCTestExpectation() 59 | let sketch = TestSketch(expectation, testName: "testBoxRotationWorking") 60 | SnapshotTestUtil.render(sketch: sketch) 61 | await fulfillment(of: [expectation], timeout: 5.0) 62 | } 63 | 64 | @MainActor 65 | func testBoxVariations() async throws { 66 | try SnapshotTestUtil.testGPU() 67 | class TestSketch: SketchForTest { 68 | override func draw(encoder: SCEncoder) { 69 | color(1, 0.5, 0.2, 0.8) 70 | rotateX(0.5) 71 | rotateY(0.5) 72 | box(3, 5, 3, 1, 2, 3) 73 | } 74 | } 75 | let expectation = XCTestExpectation() 76 | let sketch = TestSketch(expectation, testName: "testBoxVariations") 77 | SnapshotTestUtil.render(sketch: sketch) 78 | await fulfillment(of: [expectation], timeout: 5.0) 79 | } 80 | } 81 | #endif 82 | -------------------------------------------------------------------------------- /Examples/ExampleiOSApp/ExampleiOSApp/TestView.xib: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | -------------------------------------------------------------------------------- /Sources/SwiftyCreatives/Primitives/PrimitiveUtils/Abstract/RectanglePlanePrimitive.swift: -------------------------------------------------------------------------------- 1 | // 2 | // RectanglePlanePrimitive.swift 3 | // 4 | // 5 | // Created by Yuki Kuwashima on 2023/02/19. 6 | // 7 | 8 | import simd 9 | 10 | open class RectanglePlanePrimitive: HitTestablePrimitive { 11 | override init() { super.init() } 12 | private func calculateHitTest(origin: f3, direction: f3, testDistance: Float) -> (globalPos: f3, localPos: f3)? { 13 | 14 | let model = f4x4.createIdentity() 15 | let customModel = cachedCustomMatrix.transpose 16 | 17 | 18 | var selfPos_f4 = f4(0, 0, 0, 1) * model 19 | selfPos_f4.w = 1 20 | selfPos_f4 = selfPos_f4 * customModel 21 | 22 | let selfPos = f3(selfPos_f4.x, selfPos_f4.y, selfPos_f4.z) 23 | 24 | var a = f4(0, 0, 1000, 1) * model 25 | a.w = 1 26 | a = a * customModel 27 | 28 | let A = origin 29 | let B = origin + direction * testDistance 30 | 31 | let n = simd_normalize(f3(a.x, a.y, a.z) - selfPos) 32 | 33 | let P = selfPos 34 | 35 | let PAdotN = simd_dot(A-P, n) 36 | let PBdotN = simd_dot(B-P, n) 37 | 38 | guard (PAdotN >= 0 && PBdotN <= 0) || (PAdotN <= 0 && PBdotN >= 0) else { 39 | return nil 40 | } 41 | 42 | let ttt = abs(PAdotN) / (abs(PAdotN)+abs(PBdotN)) 43 | let x = A + (B-A) * ( ttt ) 44 | 45 | let inverseModel = model.inverse 46 | let inverseCustomModel = customModel.inverse 47 | var processedLocalPos = f4(x.x, x.y, x.z, 1) * inverseCustomModel 48 | processedLocalPos.w = 1 49 | processedLocalPos = processedLocalPos * inverseModel 50 | 51 | guard abs(processedLocalPos.x) <= scale.x && abs(processedLocalPos.y) <= scale.y else { 52 | return nil 53 | } 54 | 55 | let processedLocalPos_f3 = f3(processedLocalPos.x, processedLocalPos.y, processedLocalPos.z) 56 | 57 | return (x, processedLocalPos_f3) 58 | } 59 | 60 | public func hitTestGetPos(origin: f3, direction: f3, testDistance: Float = 3000) -> f3? { 61 | if let result = calculateHitTest(origin: origin, direction: direction, testDistance: testDistance) { 62 | return result.globalPos 63 | } else { 64 | return nil 65 | } 66 | } 67 | 68 | public func hitTestGetNormalizedCoord(origin: f3, direction: f3, testDistance: Float = 3000) -> f2? { 69 | if let result = calculateHitTest(origin: origin, direction: direction, testDistance: testDistance) { 70 | let localPos = result.localPos 71 | return f2(localPos.x / scale.x, localPos.y / scale.y) 72 | } else { 73 | return nil 74 | } 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /Package.resolved: -------------------------------------------------------------------------------- 1 | { 2 | "pins" : [ 3 | { 4 | "identity" : "easymetalshader", 5 | "kind" : "remoteSourceControl", 6 | "location" : "https://github.com/yukiny0811/EasyMetalShader.git", 7 | "state" : { 8 | "revision" : "7f496fcd2b000262151b90a863228215bf101eda", 9 | "version" : "3.3.2" 10 | } 11 | }, 12 | { 13 | "identity" : "fontvertexbuilder", 14 | "kind" : "remoteSourceControl", 15 | "location" : "https://github.com/yukiny0811/FontVertexBuilder", 16 | "state" : { 17 | "revision" : "2dc3a96a7abfbb6678bd39a70fc0c0f2a80a7d35", 18 | "version" : "2.1.1" 19 | } 20 | }, 21 | { 22 | "identity" : "simplesimdswift", 23 | "kind" : "remoteSourceControl", 24 | "location" : "https://github.com/yukiny0811/SimpleSimdSwift", 25 | "state" : { 26 | "revision" : "4a0b195450e42f8a46b0a77f183b1ff116696697", 27 | "version" : "1.0.1" 28 | } 29 | }, 30 | { 31 | "identity" : "svgpath", 32 | "kind" : "remoteSourceControl", 33 | "location" : "https://github.com/yukiny0811/SVGPath", 34 | "state" : { 35 | "revision" : "e738fb6400d4e4d14e82c41c9d30c3908285eae9", 36 | "version" : "1.0.0" 37 | } 38 | }, 39 | { 40 | "identity" : "swift-custom-dump", 41 | "kind" : "remoteSourceControl", 42 | "location" : "https://github.com/pointfreeco/swift-custom-dump", 43 | "state" : { 44 | "revision" : "82645ec760917961cfa08c9c0c7104a57a0fa4b1", 45 | "version" : "1.3.3" 46 | } 47 | }, 48 | { 49 | "identity" : "swift-snapshot-testing", 50 | "kind" : "remoteSourceControl", 51 | "location" : "https://github.com/pointfreeco/swift-snapshot-testing", 52 | "state" : { 53 | "revision" : "a8b7c5e0ed33d8ab8887d1654d9b59f2cbad529b", 54 | "version" : "1.18.7" 55 | } 56 | }, 57 | { 58 | "identity" : "swift-syntax", 59 | "kind" : "remoteSourceControl", 60 | "location" : "https://github.com/apple/swift-syntax.git", 61 | "state" : { 62 | "revision" : "4799286537280063c85a32f09884cfbca301b1a1", 63 | "version" : "602.0.0" 64 | } 65 | }, 66 | { 67 | "identity" : "swiftycoretext", 68 | "kind" : "remoteSourceControl", 69 | "location" : "https://github.com/yukiny0811/SwiftyCoreText", 70 | "state" : { 71 | "revision" : "ed0583f3ecc5c4e4d3ec56c8a9752d6e9a1e9b86", 72 | "version" : "1.0.0" 73 | } 74 | }, 75 | { 76 | "identity" : "xctest-dynamic-overlay", 77 | "kind" : "remoteSourceControl", 78 | "location" : "https://github.com/pointfreeco/xctest-dynamic-overlay", 79 | "state" : { 80 | "revision" : "4c27acf5394b645b70d8ba19dc249c0472d5f618", 81 | "version" : "1.7.0" 82 | } 83 | } 84 | ], 85 | "version" : 2 86 | } 87 | -------------------------------------------------------------------------------- /Sources/SwiftyCreatives/Primitives/Texts/Factory/TextFactory.swift: -------------------------------------------------------------------------------- 1 | // 2 | // File.swift 3 | // 4 | // 5 | // Created by Yuki Kuwashima on 2024/02/03. 6 | // 7 | 8 | import Foundation 9 | import CoreText 10 | import CoreGraphics 11 | import Metal 12 | import SimpleSimdSwift 13 | import FontVertexBuilder 14 | 15 | public class TextFactory { 16 | 17 | private var fontName: String 18 | private var fontSize: Double 19 | private var bounds: CGSize 20 | private var pivot: simd_double2 21 | private var textAlignment: CTTextAlignment 22 | private var verticalAlignment: PathText.VerticalAlignment 23 | private var kern: Double 24 | private var lineSpacing: Double 25 | private var isClockwiseFont: Bool 26 | 27 | public var cached: [Character: LetterCache] = [:] 28 | 29 | public func cacheCharacter(char: Character) { 30 | if char == " " { return } 31 | let vectorText = PathText(text: String(char), fontName: fontName, fontSize: fontSize, bounds: bounds, pivot: pivot, textAlignment: textAlignment, verticalAlignment: verticalAlignment, kern: kern, lineSpacing: lineSpacing) 32 | let resultTuple = GlyphUtil.MainFunctions.triangulateWithoutLetterOffset(vectorText.calculatedPaths) 33 | let path = resultTuple.paths.first! 34 | guard let offset = resultTuple.letterOffsets.first else { 35 | print("failed to cache \(char)") 36 | return 37 | } 38 | 39 | let characterPath = path.glyphLines.flatMap { $0.map { $0 + path.offset } } 40 | let pathBuffer = ShaderCore.device.makeBuffer(bytes: characterPath, length: characterPath.count * f3.memorySize)! 41 | cached[char] = LetterCache( 42 | buffer: pathBuffer, 43 | verticeCount: characterPath.count, 44 | offset: simd_double2(offset.x, offset.y), 45 | size: simd_double2( 46 | characterPath.max(by: {$0.x > $1.x})!.x, 47 | characterPath.max(by: {$0.y > $1.y})!.y 48 | ) 49 | ) 50 | } 51 | 52 | public init(fontName: String = "Avenir-BlackOblique", 53 | fontSize: Double = 10.0, 54 | bounds: CGSize = .zero, 55 | pivot: simd_double2 = .zero, 56 | textAlignment: CTTextAlignment = .natural, 57 | verticalAlignment: PathText.VerticalAlignment = .center, 58 | kern: Double = 0.0, 59 | lineSpacing: Double = 0.0, 60 | isClockwiseFont: Bool = true 61 | ) { 62 | self.fontName = fontName 63 | self.fontSize = fontSize 64 | self.bounds = bounds 65 | self.pivot = pivot 66 | self.textAlignment = textAlignment 67 | self.verticalAlignment = verticalAlignment 68 | self.kern = kern 69 | self.lineSpacing = lineSpacing 70 | self.isClockwiseFont = isClockwiseFont 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /SwiftyCreatives.xcworkspace/xcshareddata/swiftpm/Package.resolved: -------------------------------------------------------------------------------- 1 | { 2 | "pins" : [ 3 | { 4 | "identity" : "easymetalshader", 5 | "kind" : "remoteSourceControl", 6 | "location" : "https://github.com/yukiny0811/EasyMetalShader.git", 7 | "state" : { 8 | "revision" : "7f496fcd2b000262151b90a863228215bf101eda", 9 | "version" : "3.3.2" 10 | } 11 | }, 12 | { 13 | "identity" : "fontvertexbuilder", 14 | "kind" : "remoteSourceControl", 15 | "location" : "https://github.com/yukiny0811/FontVertexBuilder", 16 | "state" : { 17 | "revision" : "2dc3a96a7abfbb6678bd39a70fc0c0f2a80a7d35", 18 | "version" : "2.1.1" 19 | } 20 | }, 21 | { 22 | "identity" : "simplesimdswift", 23 | "kind" : "remoteSourceControl", 24 | "location" : "https://github.com/yukiny0811/SimpleSimdSwift", 25 | "state" : { 26 | "revision" : "4a0b195450e42f8a46b0a77f183b1ff116696697", 27 | "version" : "1.0.1" 28 | } 29 | }, 30 | { 31 | "identity" : "svgpath", 32 | "kind" : "remoteSourceControl", 33 | "location" : "https://github.com/yukiny0811/SVGPath", 34 | "state" : { 35 | "revision" : "e738fb6400d4e4d14e82c41c9d30c3908285eae9", 36 | "version" : "1.0.0" 37 | } 38 | }, 39 | { 40 | "identity" : "swift-custom-dump", 41 | "kind" : "remoteSourceControl", 42 | "location" : "https://github.com/pointfreeco/swift-custom-dump", 43 | "state" : { 44 | "revision" : "82645ec760917961cfa08c9c0c7104a57a0fa4b1", 45 | "version" : "1.3.3" 46 | } 47 | }, 48 | { 49 | "identity" : "swift-snapshot-testing", 50 | "kind" : "remoteSourceControl", 51 | "location" : "https://github.com/pointfreeco/swift-snapshot-testing", 52 | "state" : { 53 | "revision" : "a8b7c5e0ed33d8ab8887d1654d9b59f2cbad529b", 54 | "version" : "1.18.7" 55 | } 56 | }, 57 | { 58 | "identity" : "swift-syntax", 59 | "kind" : "remoteSourceControl", 60 | "location" : "https://github.com/apple/swift-syntax.git", 61 | "state" : { 62 | "revision" : "4799286537280063c85a32f09884cfbca301b1a1", 63 | "version" : "602.0.0" 64 | } 65 | }, 66 | { 67 | "identity" : "swiftycoretext", 68 | "kind" : "remoteSourceControl", 69 | "location" : "https://github.com/yukiny0811/SwiftyCoreText", 70 | "state" : { 71 | "revision" : "ed0583f3ecc5c4e4d3ec56c8a9752d6e9a1e9b86", 72 | "version" : "1.0.0" 73 | } 74 | }, 75 | { 76 | "identity" : "xctest-dynamic-overlay", 77 | "kind" : "remoteSourceControl", 78 | "location" : "https://github.com/pointfreeco/xctest-dynamic-overlay", 79 | "state" : { 80 | "revision" : "4c27acf5394b645b70d8ba19dc249c0472d5f618", 81 | "version" : "1.7.0" 82 | } 83 | } 84 | ], 85 | "version" : 2 86 | } 87 | -------------------------------------------------------------------------------- /Examples/ExampleMacOSApp/ExampleMacOSApp/FeatureExamples/Feature2.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Feature2.swift 3 | // ExampleMacOSApp 4 | // 5 | // Created by Yuki Kuwashima on 2024/08/03. 6 | // 7 | 8 | import SwiftyCreatives 9 | import SwiftUI 10 | 11 | final class Feature2: Sketch { 12 | 13 | enum TextSamples: Int { 14 | case textFactory 15 | case text2d 16 | case text3d 17 | case text3dRaw 18 | case textFactoryRaw 19 | case text2dPath 20 | mutating func toggleNext() { 21 | self = Self(rawValue: self.rawValue + 1) ?? Self(rawValue: 0)! 22 | } 23 | } 24 | 25 | var frameCount = 0 26 | var currentGeometry: TextSamples = TextSamples(rawValue: 0)! 27 | 28 | let text2d = Text2D(text: "Test") 29 | let text3d = Text3D(text: "Test3D", extrudingValue: 1) 30 | let factory: TextFactory = { 31 | let f = TextFactory() 32 | for c in "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ!?.," { 33 | f.cacheCharacter(char: c) 34 | } 35 | return f 36 | }() 37 | 38 | let text3dRaw = Text3DRaw(text: "Test3D", extrudingValue: 1) 39 | let factoryRaw: RawTextFactory = { 40 | let f = RawTextFactory() 41 | for c in "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ!?.," { 42 | f.cacheCharacter(char: c) 43 | } 44 | return f 45 | }() 46 | 47 | override func update(camera: MainCamera) { 48 | frameCount += 1 49 | if frameCount.isMultiple(of: 300) { 50 | currentGeometry.toggleNext() 51 | } 52 | } 53 | 54 | override func draw(encoder: SCEncoder, camera: MainCamera) { 55 | color(1, 1, 1, 1) 56 | 57 | switch currentGeometry { 58 | case .textFactory: 59 | text("Hello, World!", factory: factory) 60 | case .text2d: 61 | text(text2d) 62 | case .text3d: 63 | text(text3d) 64 | case .text3dRaw: 65 | for blob in text3dRaw.chunkedBlobs { 66 | mesh(blob.map { $0 + f3.randomPoint(0...0.3) }, primitiveType: .lineStrip) 67 | } 68 | case .textFactoryRaw: 69 | if let vertices = factoryRaw.cached["H"]?.vertices.map({ f3($0) }) { 70 | mesh(vertices: vertices, colors: vertices.map { _ in f4.randomPoint(0.5...1) }, primitiveType: .triangle) 71 | } 72 | break 73 | case .text2dPath: 74 | let str = text2d.calculatedPaths.map { $0.glyphs } 75 | if let letter = str.first { 76 | for line in letter { 77 | mesh(line.map { f3(Float($0.x), Float($0.y), 0) }, primitiveType: .lineStrip) 78 | } 79 | } 80 | } 81 | } 82 | 83 | struct VIEW: View { 84 | var body: some View { 85 | VStack { 86 | Text("text geometries") 87 | SketchView(Feature2()) 88 | } 89 | } 90 | } 91 | } 92 | -------------------------------------------------------------------------------- /Sources/SwiftyCreatives/Primitives/HitTestables/UIViewObject.swift: -------------------------------------------------------------------------------- 1 | // 2 | // UIViewObject.swift 3 | // 4 | // 5 | // Created by Yuki Kuwashima on 2022/12/27. 6 | // 7 | 8 | #if os(iOS) 9 | 10 | import MetalKit 11 | import UIKit 12 | 13 | open class UIViewObject: RectanglePlanePrimitive { 14 | private(set) public var texture: MTLTexture? 15 | 16 | public var viewObj: UIView? 17 | 18 | public override init() { 19 | super.init() 20 | hasTexture = [true] 21 | } 22 | 23 | @discardableResult 24 | public func load(view: UIView) -> Self { 25 | 26 | self.viewObj = view 27 | 28 | let image = view.convertToImage().cgImage! 29 | 30 | let tex = try! ShaderCore.textureLoader.newTexture( 31 | cgImage: image, 32 | options: ShaderCore.defaultTextureLoaderOptions 33 | ) 34 | self.texture = tex 35 | let longer: Float = Float(max(image.width, image.height)) 36 | self.setScale( 37 | f3( 38 | Float(image.width) / longer, 39 | Float(image.height) / longer, 40 | 1 41 | ) 42 | ) 43 | return self 44 | } 45 | override public func draw(_ encoder: SCEncoder) { 46 | encoder.setVertexBytes(RectShapeInfo.vertices, length: RectShapeInfo.vertices.count * f3.memorySize, index: VertexBufferIndex.Position.rawValue) 47 | encoder.setVertexBytes(_mScale, length: f3.memorySize, index: VertexBufferIndex.ModelScale.rawValue) 48 | encoder.setVertexBytes(RectShapeInfo.uvs, length: RectShapeInfo.uvs.count * f2.memorySize, index: VertexBufferIndex.UV.rawValue) 49 | encoder.setVertexBytes(RectShapeInfo.normals, length: RectShapeInfo.normals.count * f3.memorySize, index: VertexBufferIndex.Normal.rawValue) 50 | encoder.setFragmentBytes(self.hasTexture, length: Bool.memorySize, index: FragmentBufferIndex.HasTexture.rawValue) 51 | encoder.setFragmentTexture(self.texture, index: FragmentTextureIndex.MainTexture.rawValue) 52 | encoder.drawPrimitives(type: RectShapeInfo.primitiveType, vertexStart: 0, vertexCount: RectShapeInfo.vertices.count) 53 | } 54 | 55 | public func buttonTest(origin: f3, direction: f3, testDistance: Float = 3000) { 56 | guard let coord = hitTestGetNormalizedCoord(origin: origin, direction: direction, testDistance: testDistance) else { 57 | return 58 | } 59 | let viewCoord = CGPoint( 60 | x: (CGFloat(coord.x)+1.0) / 2, 61 | y: 1.0 - (CGFloat(coord.y)+1.0) / 2 62 | ) 63 | let result = self.viewObj!.hitTest(CGPoint( 64 | x: viewCoord.x * self.viewObj!.bounds.width, 65 | y: viewCoord.y * self.viewObj!.bounds.height 66 | ), with: nil) 67 | guard let button = result as? UIButton else { 68 | return 69 | } 70 | button.sendActions(for: .touchUpInside) 71 | } 72 | } 73 | 74 | #endif 75 | -------------------------------------------------------------------------------- /Sources/SwiftyCreatives/Renderers/Base/RendererBase.swift: -------------------------------------------------------------------------------- 1 | // 2 | // RendererBase.swift 3 | // 4 | // 5 | // Created by Yuki Kuwashima on 2022/12/14. 6 | // 7 | 8 | import MetalKit 9 | 10 | #if os(visionOS) 11 | 12 | import CompositorServices 13 | import Spatial 14 | 15 | @MainActor 16 | public class RendererBase { 17 | 18 | public var drawProcess: Sketch 19 | 20 | let arSession: ARKitSession 21 | let worldTracking: WorldTrackingProvider 22 | let layerRenderer: LayerRenderer 23 | 24 | public init(sketch: Sketch, layerRenderer: LayerRenderer) { 25 | self.drawProcess = sketch 26 | self.layerRenderer = layerRenderer 27 | worldTracking = WorldTrackingProvider() 28 | arSession = ARKitSession() 29 | 30 | 31 | } 32 | 33 | public func startRenderLoop() { 34 | Task { 35 | do { 36 | try await arSession.run([worldTracking]) 37 | } catch { 38 | fatalError("Failed to initialize ARSession") 39 | } 40 | 41 | let renderThread = Thread { 42 | self.renderLoop() 43 | } 44 | renderThread.name = "Render Thread" 45 | renderThread.start() 46 | } 47 | } 48 | 49 | func renderLoop() { 50 | while true { 51 | if layerRenderer.state == .invalidated { 52 | print("Layer is invalidated") 53 | return 54 | } else if layerRenderer.state == .paused { 55 | layerRenderer.waitUntilRunning() 56 | continue 57 | } else { 58 | autoreleasepool { 59 | self.renderFrame() 60 | } 61 | } 62 | } 63 | } 64 | func renderFrame() { 65 | 66 | } 67 | } 68 | #else 69 | @MainActor 70 | public class RendererBase: NSObject, MTKViewDelegate { 71 | var camera: MainCamera 72 | public var drawProcess: Sketch 73 | var savedDate: Date 74 | var drawConfig: DrawConfig 75 | public var cachedTexture: MTLTexture? 76 | public init(drawProcess: Sketch, cameraConfig: CameraConfig, drawConfig: DrawConfig) { 77 | self.camera = MainCamera(config: cameraConfig) 78 | self.drawConfig = drawConfig 79 | self.drawProcess = drawProcess 80 | self.savedDate = Date() 81 | } 82 | public func mtkView(_ view: MTKView, drawableSizeWillChange size: CGSize) {} 83 | public func draw(in view: MTKView) { 84 | calculateDeltaTime() 85 | view.drawableSize = CGSize( 86 | width: view.frame.size.width * CGFloat(drawConfig.contentScaleFactor), 87 | height: view.frame.size.height * CGFloat(drawConfig.contentScaleFactor) 88 | ) 89 | camera.setFrame( 90 | width: Float(view.frame.size.width) * Float(drawConfig.contentScaleFactor), 91 | height: Float(view.frame.size.height) * Float(drawConfig.contentScaleFactor) 92 | ) 93 | } 94 | } 95 | #endif 96 | -------------------------------------------------------------------------------- /Tests/SwiftyCreativesTests/SnapshotTests/Functions/BoldLineTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // BoldLineTests.swift 3 | // 4 | // 5 | // Created by Yuki Kuwashima on 2023/03/28. 6 | // 7 | 8 | @testable import SwiftyCreatives 9 | import XCTest 10 | import SwiftUI 11 | import SnapshotTesting 12 | import MetalKit 13 | 14 | #if os(macOS) 15 | final class BoldLineTests: XCTestCase { 16 | 17 | @MainActor 18 | func testBoldLineIsDrawed() async throws { 19 | try SnapshotTestUtil.testGPU() 20 | class TestSketch: SketchForTest { 21 | override func draw(encoder: SCEncoder) { 22 | color(1) 23 | boldline(0, 0, 0, 10, 10, 10, width: 5) 24 | } 25 | } 26 | let expectation = XCTestExpectation() 27 | let sketch = TestSketch(expectation, testName: "testBoldLineIsDrawed") 28 | SnapshotTestUtil.render(sketch: sketch) 29 | await fulfillment(of: [expectation], timeout: 5.0) 30 | } 31 | 32 | @MainActor 33 | func testBoldLineColorWorking() async throws { 34 | try SnapshotTestUtil.testGPU() 35 | class TestSketch: SketchForTest { 36 | override func draw(encoder: SCEncoder) { 37 | color(1, 0.5, 0.2, 0.8) 38 | boldline(0, 0, 0, 10, 10, 10, width: 5) 39 | } 40 | } 41 | let expectation = XCTestExpectation() 42 | let sketch = TestSketch(expectation, testName: "testBoldLineColorWorking") 43 | SnapshotTestUtil.render(sketch: sketch) 44 | await fulfillment(of: [expectation], timeout: 5.0) 45 | } 46 | 47 | @MainActor 48 | func testBoldLineVariations() async throws { 49 | try SnapshotTestUtil.testGPU() 50 | class TestSketch: SketchForTest { 51 | override func draw(encoder: SCEncoder) { 52 | color(1, 0.5, 0.2, 0.8) 53 | boldline(0, 0, 0, 10, 10, 0, width: 2) 54 | boldline(0, 0, 0, 10, 0, 0, width: 2) 55 | boldline(-3, -4, 0, -3, 1, 0, width: 2) 56 | boldline(-5, -3, 0, 2, -5, 0, width: 2) 57 | } 58 | } 59 | let expectation = XCTestExpectation() 60 | let sketch = TestSketch(expectation, testName: "testBoldLineVariations") 61 | SnapshotTestUtil.render(sketch: sketch) 62 | await fulfillment(of: [expectation], timeout: 5.0) 63 | } 64 | 65 | @MainActor 66 | func testBoldLineWithVertexColor() async throws { 67 | try SnapshotTestUtil.testGPU() 68 | class TestSketch: SketchForTest { 69 | override func draw(encoder: SCEncoder) { 70 | color(1, 0.5, 0.2, 0.8) 71 | boldline(0, 0, 0, 10, 10, 0, width: 2, color1: f4(1, 0, 0, 1), color2: f4(0, 0, 1, 1)) 72 | } 73 | } 74 | let expectation = XCTestExpectation() 75 | let sketch = TestSketch(expectation, testName: "testBoldLineWithVertexColor") 76 | SnapshotTestUtil.render(sketch: sketch) 77 | await fulfillment(of: [expectation], timeout: 5.0) 78 | } 79 | } 80 | #endif 81 | -------------------------------------------------------------------------------- /Sources/SwiftyCreatives/Primitives/PrimitiveUtils/Abstract/ImageLoadable.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ImageLoadable.swift 3 | // 4 | // 5 | // Created by Yuki Kuwashima on 2023/03/03. 6 | // 7 | 8 | import Metal 9 | import CoreGraphics 10 | 11 | public protocol ImageLoadable: AnyObject, ScaleSettable { 12 | var texture: MTLTexture? { get set } 13 | } 14 | 15 | // MARK: Util Functions 16 | public extension ImageLoadable { 17 | @discardableResult 18 | func adjustScale(with option: ImageAdjustOption) -> Self { 19 | let width = Float(texture!.width) 20 | let height = Float(texture!.height) 21 | switch option { 22 | case .basedOnWidth: 23 | self.setScale(f3(1, height / width, 1)) 24 | case .basedOnHeight: 25 | self.setScale(f3(width / height, 1, 1)) 26 | case .basedOnLonger: 27 | let longer = max(width, height) 28 | self.setScale(f3(width / longer, height / longer, 1)) 29 | } 30 | return self 31 | } 32 | } 33 | 34 | // MARK: Load Functions 35 | public extension ImageLoadable { 36 | 37 | @discardableResult 38 | func load(name: String, bundle: Bundle?) -> Self { 39 | self.texture = try! ShaderCore.textureLoader.newTexture( 40 | name: name, 41 | scaleFactor: 3, 42 | bundle: bundle, 43 | options: ShaderCore.defaultTextureLoaderOptions 44 | ) 45 | return self 46 | } 47 | 48 | @discardableResult 49 | func load(image: CGImage) -> Self { 50 | self.texture = try! ShaderCore.textureLoader.newTexture( 51 | cgImage: image, 52 | options: ShaderCore.defaultTextureLoaderOptions 53 | ) 54 | return self 55 | } 56 | 57 | @discardableResult 58 | func load(data: Data) -> Self { 59 | self.texture = try! ShaderCore.textureLoader.newTexture( 60 | data: data, 61 | options: ShaderCore.defaultTextureLoaderOptions 62 | ) 63 | return self 64 | } 65 | } 66 | 67 | // MARK: Async Load Functions 68 | public extension ImageLoadable { 69 | 70 | @discardableResult 71 | func load(url: URL) async -> Self { 72 | self.texture = try! await ShaderCore.textureLoader.newTexture( 73 | URL: url, 74 | options: ShaderCore.defaultTextureLoaderOptions 75 | ) 76 | return self 77 | } 78 | 79 | @discardableResult 80 | func load(name: String, bundle: Bundle?) async -> Self { 81 | self.texture = try! await ShaderCore.textureLoader.newTexture( 82 | name: name, 83 | scaleFactor: 3, 84 | bundle: bundle, 85 | options: ShaderCore.defaultTextureLoaderOptions 86 | ) 87 | return self 88 | } 89 | 90 | @discardableResult 91 | func load(data: Data) async -> Self { 92 | self.texture = try! await ShaderCore.textureLoader.newTexture( 93 | data: data, 94 | options: ShaderCore.defaultTextureLoaderOptions 95 | ) 96 | return self 97 | } 98 | } 99 | -------------------------------------------------------------------------------- /.swiftpm/xcode/xcshareddata/xcschemes/SwiftyCreatives.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 33 | 39 | 40 | 41 | 42 | 43 | 53 | 54 | 60 | 61 | 67 | 68 | 69 | 70 | 72 | 73 | 76 | 77 | 78 | -------------------------------------------------------------------------------- /Tests/SwiftyCreativesTests/SnapshotTests/Functions/SVGTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // File.swift 3 | // 4 | // 5 | // Created by Yuki Kuwashima on 2024/02/06. 6 | // 7 | 8 | @testable import SwiftyCreatives 9 | import XCTest 10 | import SwiftUI 11 | import SnapshotTesting 12 | import MetalKit 13 | 14 | #if os(macOS) 15 | final class SVGTests: XCTestCase { 16 | 17 | @MainActor 18 | func testSVGIsDrawed() async throws { 19 | try SnapshotTestUtil.testGPU() 20 | let svgObject = await SVGObj(url: Bundle.module.url(forResource: "sampleSvg", withExtension: "svg")!)! 21 | class TestSketch: SketchForTest { 22 | let svgObject: SVGObj 23 | init(svgObject: SVGObj, _ expectation: XCTestExpectation, testName: String) { 24 | self.svgObject = svgObject 25 | super.init(expectation, testName: testName) 26 | } 27 | override func draw(encoder: SCEncoder) { 28 | color(0.3, 0.6, 1.0, 1.0) 29 | scale(0.05) 30 | let count = svgObject.triangulated.reduce([], +).count 31 | var colors: [f4] = [] 32 | for i in 0..