diff --git a/MetaballsKit/Metaballs.swift b/MetaballsKit/Metaballs.swift index 4cdb5eb..6ce2bb2 100644 --- a/MetaballsKit/Metaballs.swift +++ b/MetaballsKit/Metaballs.swift @@ -293,6 +293,17 @@ public class Field { didChange = true } + if let target = userInfo["target"] as? Float { + parameters.target = target + defaults.target = target + didChange = true + } + if let feather = userInfo["feather"] as? Float { + parameters.feather = feather + defaults.feather = feather + didChange = true + } + if didChange { populateParametersBuffer() } diff --git a/MetaballsKit/Preferences.swift b/MetaballsKit/Preferences.swift index 24a90d6..4ac90cd 100644 --- a/MetaballsKit/Preferences.swift +++ b/MetaballsKit/Preferences.swift @@ -9,6 +9,36 @@ import Cocoa extension UserDefaults { + public var target: Float { + get { + if let obj = object(forKey: "target") as? NSNumber { + return obj.floatValue + } else { + let defaultValue: Float = 1.0 + set(defaultValue, forKey: "target") + return defaultValue + } + } + set { + set(newValue, forKey: "target") + } + } + + public var feather: Float { + get { + if let obj = object(forKey: "feather") as? NSNumber { + return obj.floatValue + } else { + let defaultValue: Float = 0.25 + set(defaultValue, forKey: "target") + return defaultValue + } + } + set { + set(newValue, forKey: "feather") + } + } + public var colorStyle: ColorStyle? { get { let value = integer(forKey: "colorStyle") diff --git a/MetaballsKit/PreferencesViewController.swift b/MetaballsKit/PreferencesViewController.swift index 1d536f4..c4c1c36 100644 --- a/MetaballsKit/PreferencesViewController.swift +++ b/MetaballsKit/PreferencesViewController.swift @@ -39,6 +39,34 @@ public class PreferencesViewController: NSViewController { private var colorStackView = NSStackView() private var colorViews = [ColorView]() + private lazy var targetSlider: SliderView = { + let targetSlider = SliderView(label: NSLocalizedString("Target", comment: "name of the target slider")) + targetSlider.slider.tag = Slider.target.rawValue + if #available(OSX 10.12.2, *) { + targetSlider.slider.trackFillColor = nil + } + targetSlider.slider.minValue = 0 + targetSlider.slider.maxValue = 1 + targetSlider.slider.target = self + targetSlider.slider.action = #selector(PreferencesViewController.sliderDidUpdate(sender:)) + targetSlider.slider.floatValue = self.defaults.target + return targetSlider + }() + + private lazy var featherSlider: SliderView = { + let featherSlider = SliderView(label: NSLocalizedString("Feather", comment: "name of the feather slider")) + featherSlider.slider.tag = Slider.feather.rawValue + if #available(OSX 10.12.2, *) { + featherSlider.slider.trackFillColor = nil + } + featherSlider.slider.minValue = 0 + featherSlider.slider.maxValue = 1 + featherSlider.slider.target = self + featherSlider.slider.action = #selector(PreferencesViewController.sliderDidUpdate(sender:)) + featherSlider.slider.floatValue = self.defaults.feather + return featherSlider + }() + private lazy var styleMenu: NSPopUpButton = { let button = NSPopUpButton() button.translatesAutoresizingMaskIntoConstraints = false @@ -100,13 +128,15 @@ public class PreferencesViewController: NSViewController { colorStackView.addArrangedSubview(styleMenu) for i in 0..<4 { - let colorView = ColorView() + let colorView = ColorView(label: "Color \(i+1)") colorView.translatesAutoresizingMaskIntoConstraints = false - colorView.label.stringValue = "Color \(i+1)" colorStackView.addArrangedSubview(colorView) colorViews.append(colorView) } + colorStackView.addArrangedSubview(targetSlider) + colorStackView.addArrangedSubview(featherSlider) + showCloseButtonIfNeeded() self.view = view @@ -163,6 +193,10 @@ public class PreferencesViewController: NSViewController { postColorNotification() } + func sliderDidUpdate(sender: NSSlider) { + postColorNotification() + } + func closeWindow() { self.view.window?.close() } @@ -191,23 +225,30 @@ public class PreferencesViewController: NSViewController { for (idx, cv) in colorViews.enumerated() { info["color\(idx)"] = cv.colorWell.color } + info["target"] = targetSlider.slider.floatValue + info["feather"] = featherSlider.slider.floatValue NotificationCenter.default.post(name: PreferencesDidChange_Color, object: nil, userInfo: info) } } -class ColorView: NSView { +class ParameterView: NSView { private let stackView = NSStackView() - internal let colorWell = NSColorWell() - internal let label = NSTextField(labelWithString: "Hello") + internal let control: NSControl + internal let label: NSTextField - override init(frame frameRect: NSRect) { - super.init(frame: frameRect) + init(frame f: NSRect, control c: NSControl, label: String = "Hello") { + control = c + self.label = NSTextField(labelWithString: label) + super.init(frame: f) commonInit() } + convenience init(control c: NSControl) { + self.init(frame: NSRect(), control: c) + } + required init?(coder: NSCoder) { - super.init(coder: coder) - commonInit() + fatalError("init(coder:) has not been implemented") } private func commonInit() { @@ -217,9 +258,9 @@ class ColorView: NSView { stackView.alignment = .centerY stackView.distribution = .equalSpacing - colorWell.translatesAutoresizingMaskIntoConstraints = false - colorWell.setContentHuggingPriority(251, for: .horizontal) - stackView.addArrangedSubview(colorWell) + control.translatesAutoresizingMaskIntoConstraints = false + control.setContentHuggingPriority(251, for: .horizontal) + stackView.addArrangedSubview(control) label.translatesAutoresizingMaskIntoConstraints = false stackView.addArrangedSubview(label) @@ -233,3 +274,31 @@ class ColorView: NSView { ]) } } + +class ColorView: ParameterView { + var colorWell: NSColorWell { + return control as! NSColorWell + } + + init(label: String) { + super.init(frame: NSRect(), control: NSColorWell(), label: label) + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } +} + +class SliderView: ParameterView { + var slider: NSSlider { + return control as! NSSlider + } + + init(label: String) { + super.init(frame: NSRect(), control: NSSlider(), label: label) + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } +} diff --git a/MetaballsKit/Shaders.metal b/MetaballsKit/Shaders.metal index 3294bda..a49bbcc 100644 --- a/MetaballsKit/Shaders.metal +++ b/MetaballsKit/Shaders.metal @@ -64,8 +64,8 @@ passthroughVertexShader(uint vid [[vertex_id]], float sampleAtPoint(float2, constant Ball*, int); // Color samplers -float4 singleColor(float, float, float4); -float4 gradient2(float, float, float, float4, float4); +float4 singleColor(float, float, float, float4); +float4 gradient2(float, float, float, float, float4, float4); // Helpers float mapValueFromRangeOntoRange(float, float, float, float, float); @@ -76,17 +76,18 @@ sampleToColorShader(RasterizerData in [[stage_in]], constant Parameters& parameters [[buffer(0)]], constant Ball* balls [[buffer(1)]]) { - const float target = 1.0; + const float target = parameters.target; + const float feather = parameters.feather; const float sample = sampleAtPoint(in.position.xy, balls, parameters.numberOfBalls); const float blend = in.position.x / parameters.size.x; float4 out; switch (parameters.colorStyle) { case SingleColor: - out = singleColor(sample, target, parameters.colors[0]); + out = singleColor(sample, target, feather, parameters.colors[0]); break; case Gradient2Horizontal: - out = gradient2(sample, target, blend, parameters.colors[0], parameters.colors[1]); + out = gradient2(sample, target, feather, blend, parameters.colors[0], parameters.colors[1]); break; } @@ -112,15 +113,17 @@ sampleAtPoint(float2 point, float4 singleColor(float sample, float target, + float feather, float4 color) { float4 out; - if (sample > 1.0) { + if (sample > target) { out = color; } // Feather the alpha value. - const float a = clamp(mapValueFromRangeOntoRange(sample, 0.75 * target, target, 0, 1), 0.0, 1.0); + const float mappedAlpha = mapValueFromRangeOntoRange(sample, (1.0 - feather) * target, target, 0, 1); + const float a = clamp(mappedAlpha, 0.0, 1.0); out = float4(out.xyz, a); return out; @@ -129,12 +132,13 @@ singleColor(float sample, float4 gradient2(float sample, float target, + float feather, float normalizedBlend, float4 fromColor, float4 toColor) { float4 blendedColor = averageTwoColors(normalizedBlend, fromColor, toColor); - float4 out = singleColor(sample, target, blendedColor); + float4 out = singleColor(sample, target, feather, blendedColor); return out; }