M Physarum.xcodeproj/project.pbxproj => Physarum.xcodeproj/project.pbxproj +43 -7
@@ 14,9 14,13 @@
B98501882597943D00A03727 /* PhysarumTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = B98501872597943D00A03727 /* PhysarumTests.swift */; };
B98501932597943D00A03727 /* PhysarumUITests.swift in Sources */ = {isa = PBXBuildFile; fileRef = B98501922597943D00A03727 /* PhysarumUITests.swift */; };
B98501AA259ADBF100A03727 /* Simulation.swift in Sources */ = {isa = PBXBuildFile; fileRef = B98501A9259ADBF100A03727 /* Simulation.swift */; };
- B98501B9259AE7A100A03727 /* Shaders.metal in Sources */ = {isa = PBXBuildFile; fileRef = B98501B8259AE7A100A03727 /* Shaders.metal */; };
B98501ED25A0D6EB00A03727 /* MTLTexture+Z.swift in Sources */ = {isa = PBXBuildFile; fileRef = B98501EC25A0D6EA00A03727 /* MTLTexture+Z.swift */; };
B985020725A2376F00A03727 /* Simulation+SaveFrames.swift in Sources */ = {isa = PBXBuildFile; fileRef = B985020625A2376F00A03727 /* Simulation+SaveFrames.swift */; };
+ B9E6211425C10AF500A5683C /* SimulationKernel.metal in Sources */ = {isa = PBXBuildFile; fileRef = B9E6211325C10AF500A5683C /* SimulationKernel.metal */; };
+ B9E6211925C10B4900A5683C /* mod.metal in Sources */ = {isa = PBXBuildFile; fileRef = B9E6211825C10B4900A5683C /* mod.metal */; };
+ B9E6212525C10C9500A5683C /* noise.metal in Sources */ = {isa = PBXBuildFile; fileRef = B9E6212425C10C9500A5683C /* noise.metal */; };
+ B9E6212A25C10D3900A5683C /* TextureKernel.metal in Sources */ = {isa = PBXBuildFile; fileRef = B9E6212925C10D3900A5683C /* TextureKernel.metal */; };
+ B9F00E7225C11227009B4E61 /* Shaders.metal in Sources */ = {isa = PBXBuildFile; fileRef = B9F00E7125C11227009B4E61 /* Shaders.metal */; };
/* End PBXBuildFile section */
/* Begin PBXContainerItemProxy section */
@@ 52,12 56,18 @@
B98501922597943D00A03727 /* PhysarumUITests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PhysarumUITests.swift; sourceTree = "<group>"; };
B98501942597943D00A03727 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
B98501A9259ADBF100A03727 /* Simulation.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Simulation.swift; sourceTree = "<group>"; };
- B98501B8259AE7A100A03727 /* Shaders.metal */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.metal; path = Shaders.metal; sourceTree = "<group>"; };
B98501C0259B814F00A03727 /* Particle.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = Particle.h; sourceTree = "<group>"; };
B98501C2259B822100A03727 /* Physarum-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "Physarum-Bridging-Header.h"; sourceTree = "<group>"; };
B98501DD259E2ACE00A03727 /* SimParameters.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = SimParameters.h; sourceTree = "<group>"; };
B98501EC25A0D6EA00A03727 /* MTLTexture+Z.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "MTLTexture+Z.swift"; sourceTree = "<group>"; };
B985020625A2376F00A03727 /* Simulation+SaveFrames.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Simulation+SaveFrames.swift"; sourceTree = "<group>"; };
+ B9E6211325C10AF500A5683C /* SimulationKernel.metal */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.metal; path = SimulationKernel.metal; sourceTree = "<group>"; };
+ B9E6211825C10B4900A5683C /* mod.metal */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.metal; path = mod.metal; sourceTree = "<group>"; };
+ B9E6211D25C10B5D00A5683C /* mod.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = mod.h; sourceTree = "<group>"; };
+ B9E6212325C10C8B00A5683C /* noise.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = noise.h; sourceTree = "<group>"; };
+ B9E6212425C10C9500A5683C /* noise.metal */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.metal; path = noise.metal; sourceTree = "<group>"; };
+ B9E6212925C10D3900A5683C /* TextureKernel.metal */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.metal; path = TextureKernel.metal; sourceTree = "<group>"; };
+ B9F00E7125C11227009B4E61 /* Shaders.metal */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.metal; path = Shaders.metal; sourceTree = "<group>"; };
/* End PBXFileReference section */
/* Begin PBXFrameworksBuildPhase section */
@@ 113,11 123,8 @@
B98501762597943A00A03727 /* ViewController.swift */,
B98501A9259ADBF100A03727 /* Simulation.swift */,
B985020625A2376F00A03727 /* Simulation+SaveFrames.swift */,
- B98501DD259E2ACE00A03727 /* SimParameters.h */,
- B90CC8D425A904E90079ED0E /* TexturePassParameters.h */,
- B98501C0259B814F00A03727 /* Particle.h */,
B98501C2259B822100A03727 /* Physarum-Bridging-Header.h */,
- B98501B8259AE7A100A03727 /* Shaders.metal */,
+ B9E6211E25C10C6300A5683C /* Metal */,
B98501782597943D00A03727 /* Assets.xcassets */,
B985017A2597943D00A03727 /* Main.storyboard */,
B985017D2597943D00A03727 /* Info.plist */,
@@ 144,6 151,31 @@
path = PhysarumUITests;
sourceTree = "<group>";
};
+ B9E6211E25C10C6300A5683C /* Metal */ = {
+ isa = PBXGroup;
+ children = (
+ B9F00E7125C11227009B4E61 /* Shaders.metal */,
+ B9E6212925C10D3900A5683C /* TextureKernel.metal */,
+ B9E6211325C10AF500A5683C /* SimulationKernel.metal */,
+ B9E6211F25C10C6C00A5683C /* Includes */,
+ );
+ path = Metal;
+ sourceTree = "<group>";
+ };
+ B9E6211F25C10C6C00A5683C /* Includes */ = {
+ isa = PBXGroup;
+ children = (
+ B98501DD259E2ACE00A03727 /* SimParameters.h */,
+ B98501C0259B814F00A03727 /* Particle.h */,
+ B90CC8D425A904E90079ED0E /* TexturePassParameters.h */,
+ B9E6211825C10B4900A5683C /* mod.metal */,
+ B9E6211D25C10B5D00A5683C /* mod.h */,
+ B9E6212325C10C8B00A5683C /* noise.h */,
+ B9E6212425C10C9500A5683C /* noise.metal */,
+ );
+ path = Includes;
+ sourceTree = "<group>";
+ };
/* End PBXGroup section */
/* Begin PBXNativeTarget section */
@@ 273,11 305,15 @@
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
- B98501B9259AE7A100A03727 /* Shaders.metal in Sources */,
+ B9E6212A25C10D3900A5683C /* TextureKernel.metal in Sources */,
+ B9E6211425C10AF500A5683C /* SimulationKernel.metal in Sources */,
+ B9E6211925C10B4900A5683C /* mod.metal in Sources */,
B985020725A2376F00A03727 /* Simulation+SaveFrames.swift in Sources */,
+ B9F00E7225C11227009B4E61 /* Shaders.metal in Sources */,
B98501ED25A0D6EB00A03727 /* MTLTexture+Z.swift in Sources */,
B98501772597943A00A03727 /* ViewController.swift in Sources */,
B98501AA259ADBF100A03727 /* Simulation.swift in Sources */,
+ B9E6212525C10C9500A5683C /* noise.metal in Sources */,
B98501752597943A00A03727 /* AppDelegate.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
M Physarum.xcodeproj/project.xcworkspace/xcuserdata/connorbell.xcuserdatad/UserInterfaceState.xcuserstate => Physarum.xcodeproj/project.xcworkspace/xcuserdata/connorbell.xcuserdatad/UserInterfaceState.xcuserstate +0 -0
M Physarum/Base.lproj/Main.storyboard => Physarum/Base.lproj/Main.storyboard +3 -3
@@ 707,11 707,11 @@
<objects>
<viewController id="XfG-lQ-9wD" customClass="ViewController" customModule="Physarum" customModuleProvider="target" sceneMemberID="viewController">
<view key="view" id="m2S-Jp-Qdl">
- <rect key="frame" x="0.0" y="0.0" width="750" height="750"/>
+ <rect key="frame" x="0.0" y="0.0" width="500" height="500"/>
<autoresizingMask key="autoresizingMask"/>
<subviews>
<button verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="Kk9-jN-RMZ">
- <rect key="frame" x="697" y="19" width="33" height="23"/>
+ <rect key="frame" x="447" y="19" width="33" height="23"/>
<autoresizingMask key="autoresizingMask" flexibleMinX="YES" flexibleMaxY="YES"/>
<buttonCell key="cell" type="roundTextured" title="🛠" bezelStyle="texturedRounded" alignment="center" lineBreakMode="truncatingTail" state="on" borderStyle="border" imageScaling="proportionallyDown" inset="2" id="ZGR-XZ-fxz">
<behavior key="behavior" pushIn="YES" lightByBackground="YES" lightByGray="YES"/>
@@ 719,7 719,7 @@
</buttonCell>
</button>
<scrollView fixedFrame="YES" horizontalLineScroll="10" horizontalPageScroll="10" verticalLineScroll="10" verticalPageScroll="10" usesPredominantAxisScrolling="NO" translatesAutoresizingMaskIntoConstraints="NO" id="iLG-dn-t8y">
- <rect key="frame" x="448" y="20" width="282" height="172"/>
+ <rect key="frame" x="198" y="20" width="282" height="172"/>
<autoresizingMask key="autoresizingMask" flexibleMinX="YES" flexibleMaxY="YES"/>
<clipView key="contentView" id="uUa-9T-fcw">
<rect key="frame" x="1" y="1" width="280" height="170"/>
R Physarum/Particle.h => Physarum/Metal/Includes/Particle.h +1 -0
@@ 7,6 7,7 @@ typedef struct {
float sensorHeading;
vector_float2 position;
int active;
+ vector_float3 color;
} Particle;
#endif
R Physarum/SimParameters.h => Physarum/Metal/Includes/SimParameters.h +0 -0
R Physarum/TexturePassParameters.h => Physarum/Metal/Includes/TexturePassParameters.h +0 -0
A Physarum/Metal/Includes/mod.h => Physarum/Metal/Includes/mod.h +16 -0
@@ 0,0 1,16 @@
+//
+// mod.h
+// Physarum
+//
+// Created by Connor Bell on 2021-01-26.
+//
+
+#ifndef mod_h
+#define mod_h
+
+float mod(float x, float y);
+float2 mod(float2 x, float2 y);
+float3 mod(float3 x, float3 y);
+float4 mod(float4 x, float4 y);
+
+#endif /* mod_h */
A Physarum/Metal/Includes/mod.metal => Physarum/Metal/Includes/mod.metal +30 -0
@@ 0,0 1,30 @@
+//
+// mod.metal
+// Physarum
+//
+// Created by Connor Bell on 2021-01-26.
+//
+
+#include <metal_stdlib>
+using namespace metal;
+
+// glsl mod <3
+float2 mod(float2 x, float2 y)
+{
+ return x - y * floor(x/y);
+}
+
+float mod(float x, float y)
+{
+ return x - y * floor(x/y);
+}
+
+float3 mod(float3 x, float3 y)
+{
+ return x - y * floor(x/y);
+}
+
+float4 mod(float4 x, float4 y)
+{
+ return x - y * floor(x/y);
+}
A Physarum/Metal/Includes/noise.h => Physarum/Metal/Includes/noise.h +13 -0
@@ 0,0 1,13 @@
+//
+// noise.h
+// Physarum
+//
+// Created by Connor Bell on 2021-01-26.
+//
+
+#ifndef noise_h
+#define noise_h
+
+float3 curl(float3 pos);
+
+#endif /* noise_h */
R Physarum/Shaders.metal => Physarum/Metal/Includes/noise.metal +3 -208
@@ 1,48 1,15 @@
//
-// Shaders.metal
+// noise.metal
// Physarum
//
-// Created by Connor Bell on 2020-12-28.
+// Created by Connor Bell on 2021-01-26.
//
#include <metal_stdlib>
-#include "Particle.h"
-#include "SimParameters.h"
-#include "TexturePassParameters.h"
+#include "mod.h"
using namespace metal;
-float random(float n) {
- return fract(sin(n) * 43758.5453123);
-}
-
-// glsl mod <3
-float2 mod(float2 x, float2 y)
-{
- return x - y * floor(x/y);
-}
-
-float mod(float x, float y)
-{
- return x - y * floor(x/y);
-}
-
-float3 mod(float3 x, float3 y)
-{
- return x - y * floor(x/y);
-}
-
-float4 mod(float4 x, float4 y)
-{
- return x - y * floor(x/y);
-}
-
-float3 hueShift(float3 color, float hue) {
- const float3 k = float3(0.57735, 0.57735, 0.57735);
- float cosAngle = cos(hue);
- return float3(color * cosAngle + cross(k, color) * sin(hue) + k * dot(k, color) * (1.0 - cosAngle));
-}
-
float4 permute(float4 x){return mod(((x*34.0)+1.0)*x, 289.0);}
float4 taylorInvSqrt(float4 r){return 1.79284291400159 - 0.85373472095314 * r;}
@@ 144,175 111,3 @@ float3 curl(float3 pos) {
return res;
}
-
-float3 hsv2rgb(float3 c)
-{
- const float4 K = float4(1.0, 0.66667, 0.33333, 3.0);
- float3 p = abs(fract(c.xxx + K.xyz) * 6.0 - K.www);
- return c.z * mix(K.xxx, clamp(p - K.xxx, 0.0, 1.0), c.y);
-}
-
-kernel void setupParticles(device Particle *particles [[buffer(0)]],
- device const int &width [[ buffer(1) ]],
- device const int &height [[ buffer(2) ]],
- uint id [[ thread_position_in_grid ]])
-
-{
- Particle p = particles[id];
- p.active = 0;
- p.position = float2(width/2.0,height/2.0) + (float2(cos(id*0.00235), sin(id*0.0059022))*(250+100));
-
- p.sensorHeading = random(id*0.034) * 6.28318;
-
- particles[id] = p;
-}
-
-kernel void addParticles(device Particle *particles [[buffer(0)]],
- device const int &width [[ buffer(1) ]],
- device const int &height [[ buffer(2) ]],
- device const float &startupProgress [[ buffer(3) ]],
- device const int &offset [[ buffer(4) ]],
- uint id [[ thread_position_in_grid ]])
-{
- uint index = id + offset;
- Particle particle = particles[index];
- particle.active = 1;
- float halfWidth = width / 2.0;
- float startX = width/4.0;
- float2 pos = float2(halfWidth + cos(-.5+startupProgress * 3.14159 * 3.5) * halfWidth * (1.-startupProgress) * 0.75,
- height/2.0 + sin(-.5+startupProgress * 3.14159 * 3.5) * halfWidth* (1.-startupProgress) * 0.75);
-
- particle.position = pos;
- particle.sensorHeading = -.5 + 3.14159 + startupProgress*3.14159*3.5 + random(id*0.104) ;
- particles[index] = particle;
-}
-
-
-kernel void updateParticles(device Particle *particles [[buffer(0)]],
- device const float &time [[ buffer(1) ]],
- device SimParameters *parameters [[buffer(2)]],
- texture2d<float, access::read> inTexture [[texture(0)]],
- texture2d<float, access::write> outTexture [[texture(1)]],
- texture2d<float, access::sample> inTrailMapTexture [[texture(2)]],
- texture2d<float, access::write> outTrailMapTexture [[texture(3)]],
- uint id [[ thread_position_in_grid ]])
-{
- float2 viewSize = float2(inTexture.get_width(), inTexture.get_height());
-
- // Create a copy of the current particle
- Particle p = particles[id];
-
- if (p.active == 0) return;
-
- uint2 texCoord = uint2(p.position);
-
- float sensorAngle = parameters->sensorAngle;
- // Create coordinates for the sensors to sample the trail map
- float2 coordForward = mod(p.position.xy + float2(cos(p.sensorHeading), sin(p.sensorHeading))*parameters->sensorDistance, viewSize);
- float cA = p.sensorHeading + sensorAngle;
- float2 coordClockwise = mod(p.position.xy + float2(cos(cA), sin(cA))*parameters->sensorDistance, viewSize);
- float ccA = p.sensorHeading - sensorAngle;
- float2 coordCounterClockwise = mod(p.position.xy + float2(cos(ccA), sin(ccA))*parameters->sensorDistance, viewSize);
-
- float trailMapSampleForward = inTrailMapTexture.read((uint2)round(coordForward)).r;
- float trailMapSampleClockwise = inTrailMapTexture.read((uint2)round(coordClockwise)).r;
- float trailMapSampleCounterClockwise = inTrailMapTexture.read((uint2)round(coordCounterClockwise)).r;
- float4 color = mix(float4(0., 0., 0., 1.0), float4(1., 1., 1., 1.0), saturate(time*0.1));
-
- // Do nothing and move forward if most concentrated forward
- if (trailMapSampleForward > trailMapSampleClockwise && trailMapSampleForward > trailMapSampleCounterClockwise) {
-
- }
- // Rotate Randomly if not conentrated in center
- else if (trailMapSampleClockwise > trailMapSampleForward && trailMapSampleCounterClockwise > trailMapSampleForward) {
- p.sensorHeading += random(id + time) > 0.5 ? parameters->rotationAngle : -parameters->rotationAngle;
- }
- // Rotate clockwise
- else if (trailMapSampleCounterClockwise < trailMapSampleClockwise) {
- p.sensorHeading += parameters->rotationAngle;
- }
- // Rotate counter clockwise
- else if (trailMapSampleCounterClockwise > trailMapSampleClockwise) {
- p.sensorHeading -= parameters->rotationAngle;
- }
-
- // Write particle color
- outTexture.write(color, texCoord);
- outTexture.write(color, clamp((uint2)(int2(texCoord) + int2(0,1)), uint2(0.), uint2(viewSize)));
- outTexture.write(color, clamp((uint2)(int2(texCoord) + int2(1,1)), uint2(0.), uint2(viewSize)));
- outTexture.write(color, clamp((uint2)(int2(texCoord) + int2(1,0)), uint2(0.), uint2(viewSize)));
- outTexture.write(color, clamp((uint2)(int2(texCoord) + int2(1,-1)), uint2(0.), uint2(viewSize)));
- outTexture.write(color, clamp((uint2)(int2(texCoord) + int2(0,-1)), uint2(0.), uint2(viewSize)));
- outTexture.write(color, clamp((uint2)(int2(texCoord) + int2(-1,-1)), uint2(0.), uint2(viewSize)));
- outTexture.write(color, clamp((uint2)(int2(texCoord) + int2(-1,0)), uint2(0.), uint2(viewSize)));
- outTexture.write(color, clamp((uint2)(int2(texCoord) + int2(-1,1)), uint2(0.), uint2(viewSize)));
-
- // Deposit
- float trailMapSample = inTrailMapTexture.read(texCoord).r;
- trailMapSample = 1.0;
- outTrailMapTexture.write(float4(trailMapSample, 1., 0., 1.0), texCoord);
-
- // Move
- p.position.xy = mod(p.position.xy + float2(cos(p.sensorHeading), sin(p.sensorHeading))*(parameters->movementOffset + random(id*520.421235) * parameters->movementOffsetRandomness), viewSize);
-
- particles[id] = p;
-}
-
-// Perform a box blur on both the trail and color texture
-kernel void texturePass(texture2d<float, access::sample> inTexture [[texture(0)]],
- texture2d<float, access::write> outTexture [[texture(1)]],
- texture2d<float, access::sample> inTrailTexture [[texture(2)]],
- texture2d<float, access::write> outTrailTexture [[texture(3)]],
- device TexturePassParameters *parameters [[buffer(0)]],
- device const float &time [[buffer(1)]],
- uint2 gid [[ thread_position_in_grid ]]) {
-
- constexpr sampler colorSampler(coord::normalized,
- address::repeat,
- filter::linear);
-
- // Compute weighted sum of surrounding box
- float3 col = float3(0.);
- float3 colTrail = float3(0.);
- float2 size = float2(inTexture.get_width(), inTexture.get_height());
- float2 texel = 1. / size;
- float2 uv = (float2)gid / size;
-
- // Non uniform weights to created colored feedback.
- float3 weight = float3(0.07, 0.1111111111, 0.1);
-
- for (int i = -1; i <= 1; i++) {
- for (int j = -1; j <= 1; j++) {
- float3 val = inTexture.read((uint2)(int2(gid) + int2(i,j))).rgb;
- col += val * weight;
-
- val = inTrailTexture.read((uint2)(int2(gid) + int2(i,j))).rgb;
- colTrail += val * 0.111111;
- }
- }
-
- // Add trail for mouse position
- if (length(uv - parameters->mousePosition) < 0.05) {
- colTrail = float3(1.0, 0.0, 0.0);
- }
-
- // Dampen
- col.rgb *= parameters->additiveBlendFrameFactor;
- colTrail *= parameters->additiveBlendTrailFactor;
-
- col.rgb = hueShift(col.rgb, 0.175);
- colTrail = clamp(colTrail, float3(0.01), float3(1.0));
-
- outTexture.write(float4(col, 1.0), gid);
- outTrailTexture.write(float4(colTrail, 1.0), gid);
-}
-
-
-kernel void postFx(texture2d<float, access::sample> inTexture [[texture(0)]],
- texture2d<float, access::write> outTexture [[texture(1)]],
- uint2 gid [[ thread_position_in_grid ]]) {
-
- float4 col = inTexture.read(gid);
- col.rgb = 1.0 - col.rgb;
- outTexture.write(col, gid);
-}
A Physarum/Metal/Shaders.metal => Physarum/Metal/Shaders.metal +72 -0
@@ 0,0 1,72 @@
+//
+// Shaders.metal
+// Physarum
+//
+// Created by Connor Bell on 2020-12-28.
+//
+
+#include <metal_stdlib>
+#include "Includes/Particle.h"
+#include "Includes/SimParameters.h"
+#include "Includes/TexturePassParameters.h"
+
+using namespace metal;
+
+float randomOk(float n) {
+ return fract(sin(n) * 43758.5453123);
+}
+
+float3 hueShift(float3 color, float hue) {
+ const float3 k = float3(0.57735, 0.57735, 0.57735);
+ float cosAngle = cos(hue);
+ return float3(color * cosAngle + cross(k, color) * sin(hue) + k * dot(k, color) * (1.0 - cosAngle));
+}
+
+kernel void setupParticles(device Particle *particles [[buffer(0)]],
+ device const int &width [[ buffer(1) ]],
+ device const int &height [[ buffer(2) ]],
+ uint id [[ thread_position_in_grid ]])
+
+{
+ Particle p = particles[id];
+ p.active = 0;
+ p.position = float2(width/2.0,height/2.0) + (float2(cos(id*0.00235), sin(id*0.0059022))*(250+100));
+
+ p.sensorHeading = randomOk(id*0.034) * 6.28318;
+
+ particles[id] = p;
+}
+
+kernel void addParticles(device Particle *particles [[buffer(0)]],
+ device const int &width [[ buffer(1) ]],
+ device const int &height [[ buffer(2) ]],
+ device const float &startupProgress [[ buffer(3) ]],
+ device const int &offset [[ buffer(4) ]],
+ uint id [[ thread_position_in_grid ]])
+{
+ uint index = id + offset;
+ Particle particle = particles[index];
+ particle.active = 1;
+ float halfWidth = width / 2.0;
+ float halfHeight = height / 2.0;
+ float startX = width/4.0;
+ float angle = startupProgress*3.14159*2;
+ float2 pos = float2(halfWidth + cos(angle)*5*startupProgress, halfHeight + sin(angle)*5*startupProgress);
+
+ if (index % 2 == 0) {
+ pos.y = - pos.y;
+ }
+ particle.position = pos;
+
+ particle.sensorHeading = -1.5 + 3.14159 + startupProgress*3.14159*3.5 + randomOk(id*0.104) ;
+ particles[index] = particle;
+}
+
+kernel void postFx(texture2d<float, access::sample> inTexture [[texture(0)]],
+ texture2d<float, access::write> outTexture [[texture(1)]],
+ uint2 gid [[ thread_position_in_grid ]]) {
+
+ float4 col = inTexture.read(gid);
+ col.rgb = 1.-col.rgb;
+ outTexture.write(col, gid);
+}
A Physarum/Metal/SimulationKernel.metal => Physarum/Metal/SimulationKernel.metal +93 -0
@@ 0,0 1,93 @@
+//
+// SimulationKernel.metal
+// Physarum
+//
+// Created by Connor Bell on 2021-01-26.
+//
+
+#include <metal_stdlib>
+
+#include "Includes/Particle.h"
+#include "Includes/SimParameters.h"
+#include "Includes/mod.h"
+
+using namespace metal;
+
+float random(float n) {
+ return fract(sin(n) * 3629135.423193);
+}
+
+// Write a 9x9 pixel area around the grid id with color, blended with the previous color
+void writePixelWithBlendedColor(float4 color,
+ uint2 gid,
+ texture2d<float, access::read> inTexture,
+ texture2d<float, access::write> outTexture,
+ uint2 viewSize) {
+
+ for (int i = -1; i <= 1; i++) {
+ for (int j = -1; j <= 1; j++) {
+ outTexture.write(color, clamp((uint2)(int2(gid) + int2(0,1)), uint2(0.), viewSize));
+ }
+ }
+}
+
+kernel void updateParticles(device Particle *particles [[buffer(0)]],
+ device const float &time [[ buffer(1) ]],
+ device SimParameters *parameters [[buffer(2)]],
+ texture2d<float, access::read> inTexture [[texture(0)]],
+ texture2d<float, access::write> outTexture [[texture(1)]],
+ texture2d<float, access::sample> inTrailMapTexture [[texture(2)]],
+ texture2d<float, access::write> outTrailMapTexture [[texture(3)]],
+ uint id [[ thread_position_in_grid ]])
+{
+ float2 viewSize = float2(inTexture.get_width(), inTexture.get_height());
+
+ // Create a copy of the current particle
+ Particle p = particles[id];
+
+ if (p.active == 0) return;
+
+ uint2 texCoord = uint2(p.position);
+
+ float sensorAngle = parameters->sensorAngle;
+ // Create coordinates for the sensors to sample the trail map
+ float2 coordForward = mod(p.position.xy + float2(cos(p.sensorHeading), sin(p.sensorHeading))*parameters->sensorDistance, viewSize);
+ float cA = p.sensorHeading + sensorAngle;
+ float2 coordClockwise = mod(p.position.xy + float2(cos(cA), sin(cA))*parameters->sensorDistance, viewSize);
+ float ccA = p.sensorHeading - sensorAngle;
+ float2 coordCounterClockwise = mod(p.position.xy + float2(cos(ccA), sin(ccA))*parameters->sensorDistance, viewSize);
+
+ float trailMapSampleForward = inTrailMapTexture.read((uint2)round(coordForward)).r;
+ float trailMapSampleClockwise = inTrailMapTexture.read((uint2)round(coordClockwise)).r;
+ float trailMapSampleCounterClockwise = inTrailMapTexture.read((uint2)round(coordCounterClockwise)).r;
+ float4 color = mix(float4(0., 0., 0., 1.0), float4(1.0), saturate(time*0.1));
+
+ // Do nothing and move forward if most concentrated forward
+ if (trailMapSampleForward > trailMapSampleClockwise && trailMapSampleForward > trailMapSampleCounterClockwise) {
+
+ }
+ // Rotate Randomly if not conentrated in center
+ else if (trailMapSampleClockwise > trailMapSampleForward && trailMapSampleCounterClockwise > trailMapSampleForward) {
+ p.sensorHeading += random(id + time) > 0.5 ? parameters->rotationAngle : -parameters->rotationAngle;
+ }
+ // Rotate clockwise
+ else if (trailMapSampleCounterClockwise < trailMapSampleClockwise) {
+ p.sensorHeading += parameters->rotationAngle;
+ }
+ // Rotate counter clockwise
+ else if (trailMapSampleCounterClockwise > trailMapSampleClockwise) {
+ p.sensorHeading -= parameters->rotationAngle;
+ }
+
+ writePixelWithBlendedColor(color, texCoord, inTexture, outTexture, uint2(viewSize));
+
+ // Deposit
+ float trailMapSample = inTrailMapTexture.read(texCoord).r;
+ trailMapSample = 1.0;
+ outTrailMapTexture.write(float4(trailMapSample, 1., 0., 1.0), texCoord);
+
+ // Move
+ p.position.xy = mod(p.position.xy + float2(cos(p.sensorHeading), sin(p.sensorHeading))*(parameters->movementOffset + random(id*520.421235) * parameters->movementOffsetRandomness), viewSize);
+
+ particles[id] = p;
+}
A Physarum/Metal/TextureKernel.metal => Physarum/Metal/TextureKernel.metal +81 -0
@@ 0,0 1,81 @@
+//
+// TextureKernel.metal
+// Physarum
+//
+// Created by Connor Bell on 2021-01-26.
+//
+
+#include <metal_stdlib>
+
+#include "Includes/TexturePassParameters.h"
+#include "Includes/noise.h"
+
+using namespace metal;
+
+// via Inigo Quilez https://iquilezles.org/www/articles/distfunctions2d/distfunctions2d.htm
+float sdEquilateralTriangle( float2 p, float r )
+{
+ const float k = sqrt(3.0);
+ p.y = - p.y;
+ p.x = abs(p.x) - r;
+ p.y = p.y + r/k;
+ if( p.x+k*p.y>0.0 ) p=float2(p.x-k*p.y,-k*p.x-p.y)/2.0;
+ p.x -= clamp( p.x, -2.0*r, 0.0 );
+ return -length(p)*sign(p.y);
+}
+
+// Perform a box blur on both the trail and color texture
+kernel void texturePass(texture2d<float, access::sample> inTexture [[texture(0)]],
+ texture2d<float, access::write> outTexture [[texture(1)]],
+ texture2d<float, access::sample> inTrailTexture [[texture(2)]],
+ texture2d<float, access::write> outTrailTexture [[texture(3)]],
+ device TexturePassParameters *parameters [[buffer(0)]],
+ device const float &time [[buffer(1)]],
+ uint2 gid [[ thread_position_in_grid ]]) {
+
+ constexpr sampler colorSampler(coord::normalized,
+ address::repeat,
+ filter::linear);
+
+ // Compute weighted sum of surrounding box
+ float3 col = float3(0.);
+ float3 colTrail = float3(0.);
+ float2 size = float2(inTexture.get_width(), inTexture.get_height());
+ float2 texel = 1. / size;
+ float2 uv = (float2)gid / size;
+
+ // Non uniform weights to created colored feedback.
+ float3 weight = float3(0.95, 0.05, 0.1111111111);
+
+ for (int i = -1; i <= 1; i++) {
+ for (int j = -1; j <= 1; j++) {
+ float3 val = inTexture.read((uint2)(int2(gid) + int2(i,j))).rgb;
+ col += val * 0.1111111;
+
+ val = inTrailTexture.read((uint2)(int2(gid) + int2(i,j))).rgb;
+ colTrail += val * 0.111111;
+ }
+ }
+ col *= float3(0.775, 0.95, 1.0);
+ // Add trail for mouse position
+ if (length(uv - parameters->mousePosition) < 0.05) {
+ colTrail = float3(1.0, 0.0, 0.0);
+ }
+
+ // Dampen
+ col.rgb *= parameters->additiveBlendFrameFactor;
+ colTrail *= parameters->additiveBlendTrailFactor;
+
+ float3 n = curl(float3(uv*15., time*0.05));
+ float tri = sdEquilateralTriangle(uv + (n.xy-0.5)*0.015 - float2(0.5, 0.6), 0.4);
+ tri = smoothstep(0.035, .0, abs(tri)) * (abs(n.z)*0.25);
+
+ //col.rgb = hueShift(col.rgb, 0.15);
+ colTrail = clamp(colTrail + tri, float3(0.0), float3(1.0));
+
+ //outTexture.write(float4(float3(tri), 1.0), gid);
+ outTexture.write(float4(col, 1.0), gid);
+
+ outTrailTexture.write(float4(colTrail, 1.0), gid);
+}
+
M Physarum/Simulation.swift => Physarum/Simulation.swift +8 -11
@@ 15,7 15,7 @@ class Simulation: MTKView {
// Parameters
var simulationParameters: SimParameters
var texturePassParameters: TexturePassParameters
- var particleCount = 1024*64
+ var particleCount = 1024*4
var activeParticles = 0
var particlesPerFrame = 1024/4
@@ 51,11 51,12 @@ class Simulation: MTKView {
private var mouseDown = false
// Render PNG properties
- private var numRenderFrames: Int = 1250
+ private var numRenderFrames: Int = 850
private var currentRenderFrame = 0
var renderFrames: Bool = false
init(width: Int, height: Int) {
+
self.metalDevice = MTLCreateSystemDefaultDevice()!
self.commandQueue = metalDevice.makeCommandQueue()!
self.metalLibrary = metalDevice.makeDefaultLibrary()!
@@ 82,15 83,15 @@ class Simulation: MTKView {
fatalError("newComputePipelineStateWithFunction failed ")
}
let rect = CGRect(x: 0, y: 0, width: width, height: height)
-
- self.simulationParameters = SimParameters(sensorAngle: 0.15,
+
+ self.simulationParameters = SimParameters(sensorAngle: 0.125,
sensorDistance: 8.0,
movementOffset: 1.1,
movementOffsetRandomness: 1.0,
rotationAngle: 0.5)
- self.texturePassParameters = TexturePassParameters(additiveBlendFrameFactor: 0.975,
- additiveBlendTrailFactor: 0.9, mousePosition: vector2(-1.0, -1.0))
+ self.texturePassParameters = TexturePassParameters(additiveBlendFrameFactor: 0.985,
+ additiveBlendTrailFactor: 0.925, mousePosition: vector2(-1.0, -1.0))
super.init(frame: rect,
device: self.metalDevice)
@@ 163,12 164,8 @@ extension Simulation {
if activeParticles < particleCount {
activeParticles += addParticles(commandEncoder: commandEncoder)
- if activeParticles >= particleCount {
- self.simulationParameters.movementOffset += 0.5;
- self.simulationParameters.sensorAngle -= 0.035;
- }
}
-
+
simulate(commandEncoder: commandEncoder)
dampenAndDecayTextures(drawableIn: renderTextureSwap,