vizcore 1.1.0 → 1.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (78) hide show
  1. checksums.yaml +4 -4
  2. data/frontend/index.html +24 -2
  3. data/frontend/src/audio-inspector.js +9 -0
  4. data/frontend/src/live-controls.js +219 -7
  5. data/frontend/src/main.js +447 -57
  6. data/frontend/src/midi-learn.js +22 -2
  7. data/frontend/src/performance-monitor.js +137 -1
  8. data/frontend/src/renderer/engine.js +391 -10
  9. data/frontend/src/renderer/layer-manager.js +472 -71
  10. data/frontend/src/runtime-control-preset.js +44 -0
  11. data/frontend/src/scene-patches.js +159 -0
  12. data/frontend/src/shader-error-overlay.js +1 -0
  13. data/frontend/src/visuals/image-renderer.js +19 -0
  14. data/frontend/src/visuals/particle-system.js +10 -0
  15. data/frontend/src/visuals/shape-renderer.js +13 -0
  16. data/frontend/src/visuals/spectrogram-renderer.js +14 -0
  17. data/frontend/src/visuals/text-renderer.js +13 -0
  18. data/frontend/src/websocket-client.js +6 -0
  19. data/lib/vizcore/analysis/adaptive_normalizer.rb +20 -2
  20. data/lib/vizcore/analysis/bpm_estimator.rb +18 -8
  21. data/lib/vizcore/analysis/feature_recorder.rb +117 -7
  22. data/lib/vizcore/analysis/feature_replay.rb +48 -9
  23. data/lib/vizcore/analysis/pipeline.rb +258 -9
  24. data/lib/vizcore/analysis/tap_tempo.rb +17 -2
  25. data/lib/vizcore/audio/calibration.rb +156 -0
  26. data/lib/vizcore/audio/file_input.rb +28 -0
  27. data/lib/vizcore/audio/input_manager.rb +36 -1
  28. data/lib/vizcore/audio/midi_input.rb +5 -0
  29. data/lib/vizcore/audio/ring_buffer.rb +22 -0
  30. data/lib/vizcore/audio.rb +1 -0
  31. data/lib/vizcore/cli/dsl_reference.rb +64 -8
  32. data/lib/vizcore/cli/plugin_checker.rb +93 -0
  33. data/lib/vizcore/cli/scene_diagnostics.rb +2 -2
  34. data/lib/vizcore/cli/scene_inspector.rb +35 -1
  35. data/lib/vizcore/cli/scene_validator.rb +487 -39
  36. data/lib/vizcore/cli/shader_template.rb +7 -2
  37. data/lib/vizcore/cli/shader_uniform_docs.rb +11 -0
  38. data/lib/vizcore/cli.rb +268 -15
  39. data/lib/vizcore/config.rb +40 -3
  40. data/lib/vizcore/control_preset.rb +29 -0
  41. data/lib/vizcore/deep_copy.rb +21 -0
  42. data/lib/vizcore/dsl/color_helpers.rb +155 -0
  43. data/lib/vizcore/dsl/engine.rb +219 -23
  44. data/lib/vizcore/dsl/layer_builder.rb +278 -15
  45. data/lib/vizcore/dsl/layer_group_builder.rb +10 -12
  46. data/lib/vizcore/dsl/layout_helpers.rb +290 -0
  47. data/lib/vizcore/dsl/mapping_preset_builder.rb +41 -0
  48. data/lib/vizcore/dsl/mapping_resolver.rb +404 -22
  49. data/lib/vizcore/dsl/mapping_transform_builder.rb +50 -0
  50. data/lib/vizcore/dsl/midi_map_executor.rb +219 -23
  51. data/lib/vizcore/dsl/reaction_builder.rb +1 -0
  52. data/lib/vizcore/dsl/scene_builder.rb +83 -13
  53. data/lib/vizcore/dsl/shader_source_resolver.rb +1 -10
  54. data/lib/vizcore/dsl/style_builder.rb +3 -0
  55. data/lib/vizcore/dsl/timeline_builder.rb +91 -8
  56. data/lib/vizcore/dsl/transition_controller.rb +157 -18
  57. data/lib/vizcore/dsl.rb +2 -0
  58. data/lib/vizcore/layer_catalog.rb +1 -0
  59. data/lib/vizcore/plugin_asset_policy.rb +55 -0
  60. data/lib/vizcore/project_manifest.rb +12 -2
  61. data/lib/vizcore/renderer/render_sequence.rb +104 -13
  62. data/lib/vizcore/renderer/scene_frame_source.rb +179 -14
  63. data/lib/vizcore/renderer/scene_serializer.rb +38 -0
  64. data/lib/vizcore/renderer/snapshot.rb +4 -3
  65. data/lib/vizcore/renderer/snapshot_renderer.rb +134 -8
  66. data/lib/vizcore/scene_trust.rb +31 -0
  67. data/lib/vizcore/server/frame_broadcaster.rb +469 -23
  68. data/lib/vizcore/server/rack_app.rb +151 -4
  69. data/lib/vizcore/server/runner.rb +676 -82
  70. data/lib/vizcore/server/websocket_handler.rb +236 -14
  71. data/lib/vizcore/server.rb +21 -0
  72. data/lib/vizcore/shape.rb +39 -16
  73. data/lib/vizcore/sync/osc_message.rb +66 -9
  74. data/lib/vizcore/version.rb +1 -1
  75. data/lib/vizcore.rb +33 -0
  76. data/scripts/browser_capture.mjs +31 -2
  77. data/sig/vizcore.rbs +55 -4
  78. metadata +18 -3
@@ -2,7 +2,11 @@
2
2
 
3
3
  require_relative "mapping_transform_builder"
4
4
  require_relative "reaction_builder"
5
+ require_relative "../deep_copy"
6
+ require_relative "../layer_catalog"
5
7
  require_relative "../shape"
8
+ require_relative "color_helpers"
9
+ require_relative "layout_helpers"
6
10
 
7
11
  module Vizcore
8
12
  module DSL
@@ -11,7 +15,9 @@ module Vizcore
11
15
  NO_ARGUMENT = Object.new.freeze
12
16
  SHAPE_SCHEMA_VERSION = 2
13
17
  MAPPING_SOURCE_KINDS = %i[
14
- amplitude frequency_band fft_spectrum onset kick snare hihat beat beat_confidence beat_pulse beat_count bpm
18
+ amplitude peak frequency_band frequency_band_peak fft_spectrum onset kick snare hihat beat beat_confidence beat_pulse beat_count bpm
19
+ beat_phase beat_2 beat_4 beat_8 beat_triplet triplet bar_phase bar_count phrase_count bpm_confidence
20
+ spectral_centroid spectral_rolloff spectral_flatness spectral_flux zero_crossing_rate global lfo adsr envelope
15
21
  ].freeze
16
22
  PATH_DEFAULT_DETAIL = 32
17
23
  PATH_MIN_DETAIL = 4
@@ -30,6 +36,9 @@ module Vizcore
30
36
  }.freeze
31
37
  SHAPE_STYLE_KEYS = Vizcore::Shape::STYLE_KEYS
32
38
  SHAPE_TRANSFORM_KEYS = %i[translate rotate rotation scale origin].freeze
39
+ STRICT_PARAM_ALLOWLIST = %i[
40
+ custom_shape_controls custom_shapes glsl_source group origin rotate scale translate
41
+ ].freeze
33
42
 
34
43
  # Reference to an already declared shape, used by `map ... to: shape(:id).radius`.
35
44
  class ShapeReference
@@ -52,9 +61,13 @@ module Vizcore
52
61
  # @param name [Symbol, String] layer identifier
53
62
  # @param styles [Hash] reusable layer parameter styles
54
63
  # @param defaults [Hash] default params applied before layer-specific values
55
- def initialize(name:, styles: {}, defaults: {})
64
+ # @param strict [Boolean] true when unknown layer params should fail
65
+ # @param mapping_presets [Hash] reusable mapping presets
66
+ def initialize(name:, styles: {}, defaults: {}, mapping_presets: {}, strict: false)
56
67
  @name = name.to_sym
57
68
  @styles = styles
69
+ @mapping_presets = mapping_presets
70
+ @strict = !!strict
58
71
  @type = nil
59
72
  @shader = nil
60
73
  @glsl = nil
@@ -65,6 +78,9 @@ module Vizcore
65
78
  @shape_group_stack = [{}]
66
79
  end
67
80
 
81
+ include ColorHelpers
82
+ include LayoutHelpers
83
+
68
84
  # Evaluate a layer block.
69
85
  #
70
86
  # @yield Layer DSL methods
@@ -522,7 +538,12 @@ module Vizcore
522
538
  # @return [ShapeReference]
523
539
  def shape(id)
524
540
  key = id.to_sym
525
- index = @shape_index_by_id.fetch(key) { raise ArgumentError, "unknown shape id: #{key.inspect}" }
541
+ index = @shape_index_by_id.fetch(key) do
542
+ suggestion_message = shape_id_suggestions(key)
543
+ message = "unknown shape id: #{key.inspect}"
544
+ message = "#{message}. Did you mean: #{suggestion_message}" unless suggestion_message.empty?
545
+ raise ArgumentError, message
546
+ end
526
547
  ShapeReference.new("shapes.#{index}")
527
548
  end
528
549
 
@@ -567,6 +588,17 @@ module Vizcore
567
588
  @params[:palette] = normalize_palette(colors)
568
589
  end
569
590
 
591
+ # Append one post effect to this layer's post effect chain.
592
+ #
593
+ # @param name [Symbol, String] effect key
594
+ # @return [Symbol]
595
+ def post(name)
596
+ raise ArgumentError, "post expects a symbol or string" unless name
597
+
598
+ @params[:post_effects] ||= []
599
+ @params[:post_effects] << name.to_sym
600
+ end
601
+
570
602
  # Apply a named style by merging its params into this layer.
571
603
  #
572
604
  # @param name [Symbol, String] style identifier
@@ -636,6 +668,17 @@ module Vizcore
636
668
  end
637
669
  end
638
670
 
671
+ # Apply a named mapping preset to this layer.
672
+ #
673
+ # @param name [Symbol, String] mapping preset identifier
674
+ # @raise [ArgumentError] when the preset is unknown
675
+ # @return [void]
676
+ def use_mapping(name)
677
+ preset_name = name.to_sym
678
+ preset = @mapping_presets.fetch(preset_name) { raise ArgumentError, "unknown mapping preset: #{preset_name}" }
679
+ preset.each { |mapping| @mappings << deep_dup(mapping) }
680
+ end
681
+
639
682
  # High-level mapping DSL for describing audio reactions inside a layer.
640
683
  #
641
684
  # @param source_value [Hash, Symbol, String] analysis source descriptor
@@ -663,42 +706,82 @@ module Vizcore
663
706
  mapping_source(:amplitude)
664
707
  end
665
708
 
709
+ # @return [Hash] source descriptor for absolute sample peak level
710
+ def peak
711
+ mapping_source(:peak)
712
+ end
713
+
666
714
  # @param name [Symbol, String] band key (`sub`, `low`, `mid`, `high`)
667
715
  # @return [Hash] source descriptor for a frequency band
668
716
  def frequency_band(name)
669
717
  mapping_source(:frequency_band, band: name.to_sym)
670
718
  end
671
719
 
720
+ # @return [Hash] source descriptor for a held frequency-band peak
721
+ def frequency_band_peak(name)
722
+ mapping_source(:frequency_band_peak, band: name.to_sym)
723
+ end
724
+
672
725
  # @return [Hash] source descriptor for the sub-bass frequency band
673
726
  def sub
674
727
  frequency_band(:sub)
675
728
  end
676
729
 
730
+ # @return [Hash] source descriptor for the held sub-bass peak
731
+ def sub_peak
732
+ frequency_band_peak(:sub)
733
+ end
734
+
677
735
  # @return [Hash] source descriptor for the low/bass frequency band
678
736
  def low
679
737
  frequency_band(:low)
680
738
  end
681
739
 
740
+ # @return [Hash] source descriptor for the held low/bass peak
741
+ def low_peak
742
+ frequency_band_peak(:low)
743
+ end
744
+
682
745
  # @return [Hash] source descriptor for the low/bass frequency band
683
746
  def bass
684
747
  frequency_band(:low)
685
748
  end
686
749
 
750
+ # @return [Hash] source descriptor for the held low/bass peak
751
+ def bass_peak
752
+ frequency_band_peak(:low)
753
+ end
754
+
687
755
  # @return [Hash] source descriptor for the mid frequency band
688
756
  def mid
689
757
  frequency_band(:mid)
690
758
  end
691
759
 
760
+ # @return [Hash] source descriptor for the held mid peak
761
+ def mid_peak
762
+ frequency_band_peak(:mid)
763
+ end
764
+
692
765
  # @return [Hash] source descriptor for the high frequency band
693
766
  def high
694
767
  frequency_band(:high)
695
768
  end
696
769
 
770
+ # @return [Hash] source descriptor for the held high peak
771
+ def high_peak
772
+ frequency_band_peak(:high)
773
+ end
774
+
697
775
  # @return [Hash] source descriptor for the high/treble frequency band
698
776
  def treble
699
777
  frequency_band(:high)
700
778
  end
701
779
 
780
+ # @return [Hash] source descriptor for the held high/treble peak
781
+ def treble_peak
782
+ frequency_band_peak(:high)
783
+ end
784
+
702
785
  # @return [Hash] source descriptor for FFT spectrum array
703
786
  def fft_spectrum
704
787
  mapping_source(:fft_spectrum)
@@ -757,13 +840,138 @@ module Vizcore
757
840
  mapping_source(:beat_count)
758
841
  end
759
842
 
843
+ # @return [Hash] source descriptor for 0.0..1.0 phase within the current beat
844
+ def beat_phase
845
+ mapping_source(:beat_phase)
846
+ end
847
+
848
+ # @return [Hash] source descriptor for half-beat subdivision pulses
849
+ def beat_2
850
+ mapping_source(:beat_2)
851
+ end
852
+
853
+ # @return [Hash] source descriptor for quarter-beat subdivision pulses
854
+ def beat_4
855
+ mapping_source(:beat_4)
856
+ end
857
+
858
+ # @return [Hash] source descriptor for eighth-beat subdivision pulses
859
+ def beat_8
860
+ mapping_source(:beat_8)
861
+ end
862
+
863
+ # @return [Hash] source descriptor for triplet subdivision pulses
864
+ def beat_triplet
865
+ mapping_source(:beat_triplet)
866
+ end
867
+
868
+ # @return [Hash] source descriptor for triplet subdivision pulses
869
+ def triplet
870
+ mapping_source(:beat_triplet)
871
+ end
872
+
873
+ # @return [Hash] source descriptor for 0.0..1.0 phase within the current 4-beat bar
874
+ def bar_phase
875
+ mapping_source(:bar_phase)
876
+ end
877
+
878
+ # @return [Hash] source descriptor for completed 4-beat bars
879
+ def bar_count(value = NO_ARGUMENT)
880
+ return @params[:bar_count] = Integer(value) unless value.equal?(NO_ARGUMENT)
881
+
882
+ mapping_source(:bar_count)
883
+ end
884
+
885
+ # @return [Hash] source descriptor for completed 8-bar phrases
886
+ def phrase_count
887
+ mapping_source(:phrase_count)
888
+ end
889
+
760
890
  # @return [Hash] source descriptor for estimated BPM
761
891
  def bpm
762
892
  mapping_source(:bpm)
763
893
  end
764
894
 
895
+ # @return [Hash] source descriptor for tempo estimator confidence
896
+ def bpm_confidence
897
+ mapping_source(:bpm_confidence)
898
+ end
899
+
900
+ # @return [Hash] source descriptor for spectral centroid in Hz
901
+ def spectral_centroid
902
+ mapping_source(:spectral_centroid)
903
+ end
904
+
905
+ # @return [Hash] source descriptor for spectral rolloff in Hz
906
+ def spectral_rolloff
907
+ mapping_source(:spectral_rolloff)
908
+ end
909
+
910
+ # @return [Hash] source descriptor for spectral flatness
911
+ def spectral_flatness
912
+ mapping_source(:spectral_flatness)
913
+ end
914
+
915
+ # @return [Hash] source descriptor for positive spectrum delta
916
+ def spectral_flux
917
+ mapping_source(:spectral_flux)
918
+ end
919
+
920
+ # @return [Hash] source descriptor for time-domain zero crossing rate
921
+ def zero_crossing_rate
922
+ mapping_source(:zero_crossing_rate)
923
+ end
924
+
925
+ # @param name [Symbol, String] runtime global value name
926
+ # @return [Hash] source descriptor for mutable runtime globals
927
+ def global(name)
928
+ mapping_source(:global, name: name.to_sym)
929
+ end
930
+
931
+ # @param wave [Symbol, String] one of `sine`, `triangle`, `saw`, `square`
932
+ # @param rate [Numeric] cycles per second
933
+ # @param phase [Numeric] phase offset in cycles
934
+ # @return [Hash] source descriptor for a time-based low-frequency oscillator
935
+ def lfo(wave = :sine, rate: 1.0, phase: 0.0)
936
+ mapping_source(:lfo, wave: wave.to_sym, rate: Float(rate), phase: Float(phase))
937
+ rescue ArgumentError, TypeError
938
+ raise ArgumentError, "lfo rate and phase must be numeric"
939
+ end
940
+
941
+ # @param source [Symbol, Hash] source to trigger the envelope
942
+ # @param attack [Numeric] seconds from 0.0 to peak
943
+ # @param decay [Numeric] seconds from peak to sustain
944
+ # @param sustain [Numeric] sustain gain once decay is complete (0.0..1.0)
945
+ # @param release [Numeric] seconds from sustain to 0.0
946
+ # @param threshold [Numeric] value threshold that starts attack
947
+ # @param peak [Numeric] peak gain multiplier
948
+ # @return [Hash] source descriptor for an ADSR envelope
949
+ def adsr(source = :kick, attack: 0.02, decay: 0.08, sustain: 0.7, release: 0.16, threshold: 0.0, peak: 1.0)
950
+ source_value = source.nil? ? { kind: :kick } : normalize_source(source)
951
+ mapping_source(
952
+ :adsr,
953
+ source: source_value,
954
+ attack: normalize_non_negative_float(attack, :attack),
955
+ decay: normalize_non_negative_float(decay, :decay),
956
+ sustain: clamp(normalize_float(sustain, :sustain), 0.0, 1.0),
957
+ release: normalize_non_negative_float(release, :release),
958
+ threshold: normalize_float(threshold, :threshold),
959
+ peak: normalize_float(peak, :peak)
960
+ )
961
+ end
962
+
963
+ # Alias for ADSR envelope mapping source.
964
+ #
965
+ # @param source [Symbol, Hash] source to trigger the envelope
966
+ # @param options [Numeric] envelope timing and shaping values
967
+ # @return [Hash] source descriptor for a general envelope
968
+ def envelope(source = :kick, **options)
969
+ adsr(source, **options)
970
+ end
971
+
765
972
  # @return [Hash] serialized layer payload
766
973
  def to_h
974
+ validate_strict_params! if @strict
767
975
  layer = {
768
976
  name: @name,
769
977
  type: resolved_type,
@@ -1322,16 +1530,7 @@ module Vizcore
1322
1530
  end
1323
1531
 
1324
1532
  def deep_dup(value)
1325
- case value
1326
- when Hash
1327
- value.each_with_object({}) do |(key, entry), output|
1328
- output[key] = deep_dup(entry)
1329
- end
1330
- when Array
1331
- value.map { |entry| deep_dup(entry) }
1332
- else
1333
- value
1334
- end
1533
+ Vizcore::DeepCopy.copy(value)
1335
1534
  end
1336
1535
 
1337
1536
  def evaluate_transform_block(initial_options, &block)
@@ -1365,19 +1564,26 @@ module Vizcore
1365
1564
  raise ArgumentError, "param min must be less than or equal to max"
1366
1565
  end
1367
1566
 
1368
- def normalize_transform(gain: nil, range: nil, min: nil, max: nil, curve: nil, attack: nil, release: nil, deadzone: nil)
1567
+ def normalize_transform(gain: nil, range: nil, min: nil, max: nil, curve: nil, attack: nil, release: nil, deadzone: nil, threshold: nil, hysteresis: nil, hold: nil, decay: nil, cooldown: nil, one_shot: nil, as: nil)
1369
1568
  range_min, range_max = normalize_range(range, context: "mapping")
1370
1569
  min = range_min if min.nil?
1371
1570
  max = range_max if max.nil?
1372
1571
 
1373
1572
  output = {}
1374
1573
  output[:deadzone] = normalize_non_negative_float(deadzone, :deadzone) unless deadzone.nil?
1574
+ output[:as] = normalize_mapping_mode(as) unless as.nil?
1575
+ output[:threshold] = normalize_float(threshold, :threshold) unless threshold.nil?
1576
+ output[:hysteresis] = normalize_non_negative_float(hysteresis, :hysteresis) unless hysteresis.nil?
1375
1577
  output[:gain] = normalize_float(gain, :gain) unless gain.nil?
1376
1578
  output[:min] = normalize_float(min, :min) unless min.nil?
1377
1579
  output[:max] = normalize_float(max, :max) unless max.nil?
1378
1580
  output[:curve] = normalize_curve(curve) unless curve.nil?
1379
1581
  output[:attack] = clamp(normalize_float(attack, :attack), 0.0, 1.0) unless attack.nil?
1380
1582
  output[:release] = clamp(normalize_float(release, :release), 0.0, 1.0) unless release.nil?
1583
+ output[:hold] = normalize_non_negative_float(hold, :hold) unless hold.nil?
1584
+ output[:decay] = clamp(normalize_float(decay, :decay), 0.0, 1.0) unless decay.nil?
1585
+ output[:cooldown] = normalize_non_negative_float(cooldown, :cooldown) unless cooldown.nil?
1586
+ output[:one_shot] = !!one_shot unless one_shot.nil?
1381
1587
  output
1382
1588
  end
1383
1589
 
@@ -1419,11 +1625,30 @@ module Vizcore
1419
1625
 
1420
1626
  def normalize_curve(value)
1421
1627
  curve = value.to_sym
1422
- return curve if %i[linear sqrt square ease_out].include?(curve)
1628
+ return curve if %i[linear sqrt square ease_out ease_in ease_in_out smoothstep exp log step].include?(curve)
1423
1629
 
1424
1630
  raise ArgumentError, "unsupported mapping curve: #{value.inspect}"
1425
1631
  end
1426
1632
 
1633
+ def normalize_mapping_mode(value)
1634
+ mode = value.to_sym
1635
+ return mode if %i[continuous trigger].include?(mode)
1636
+
1637
+ raise ArgumentError, "unsupported mapping mode: #{value.inspect}"
1638
+ end
1639
+
1640
+ def validate_strict_params!
1641
+ unknown = @params.keys.map(&:to_sym) - strict_allowed_params
1642
+ return if unknown.empty?
1643
+
1644
+ raise ArgumentError, "layer #{@name} has unknown params in strict mode: #{unknown.sort.join(', ')}"
1645
+ end
1646
+
1647
+ def strict_allowed_params
1648
+ catalog_params = Vizcore::LayerCatalog.params_for(resolved_type).keys
1649
+ (catalog_params + @param_schema.keys + STRICT_PARAM_ALLOWLIST).map(&:to_sym).uniq
1650
+ end
1651
+
1427
1652
  def clamp(value, min, max)
1428
1653
  [[value, min].max, max].min
1429
1654
  end
@@ -1434,6 +1659,44 @@ module Vizcore
1434
1659
  **options
1435
1660
  }
1436
1661
  end
1662
+
1663
+ def shape_id_suggestions(key)
1664
+ return "" if @shape_index_by_id.empty?
1665
+
1666
+ candidates = @shape_index_by_id.keys
1667
+ .map do |shape_id|
1668
+ [shape_id, levenshtein_distance(shape_id.to_s, key.to_s)]
1669
+ end
1670
+ .select { |_, distance| distance <= 3 }
1671
+ .sort_by { |shape_id, distance| [distance, shape_id.to_s] }
1672
+ .first(3)
1673
+
1674
+ return "" if candidates.empty?
1675
+
1676
+ candidates.map { |shape_id, _| shape_id.inspect }.join(", ")
1677
+ end
1678
+
1679
+ def levenshtein_distance(a, b)
1680
+ prev = (0..b.length).to_a
1681
+ b_chars = b.bytes
1682
+ a_bytes = a.bytes
1683
+
1684
+ a_bytes.each_with_index do |codepoint_a, index_a|
1685
+ current = [index_a + 1]
1686
+ b_chars.each_with_index do |codepoint_b, index_b|
1687
+ cost = codepoint_a == codepoint_b ? 0 : 1
1688
+ current << [
1689
+ current[index_b] + 1,
1690
+ prev[index_b + 1] + 1,
1691
+ prev[index_b] + cost
1692
+ ].min
1693
+ end
1694
+
1695
+ prev = current
1696
+ end
1697
+
1698
+ prev[b.length]
1699
+ end
1437
1700
  end
1438
1701
  end
1439
1702
  end
@@ -1,17 +1,24 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require_relative "layer_builder"
4
+ require_relative "color_helpers"
4
5
 
5
6
  module Vizcore
6
7
  module DSL
7
8
  # Collects related layers and applies shared layer parameters.
8
9
  class LayerGroupBuilder
10
+ include ColorHelpers
11
+
9
12
  # @param name [Symbol, String] group identifier stored on nested layer params
10
13
  # @param styles [Hash] reusable layer parameter styles
14
+ # @param mapping_presets [Hash] reusable layer mapping presets
11
15
  # @param defaults [Hash] scene defaults already applied before group params
12
- def initialize(name:, styles: {}, defaults: {})
16
+ # @param strict [Boolean] true when unknown layer params should fail
17
+ def initialize(name:, styles: {}, defaults: {}, mapping_presets: {}, strict: false)
13
18
  @name = name.to_sym
14
19
  @styles = styles
20
+ @mapping_presets = mapping_presets
21
+ @strict = !!strict
15
22
  @params = deep_dup(defaults)
16
23
  @layers = []
17
24
  end
@@ -31,7 +38,7 @@ module Vizcore
31
38
  # @yield Layer definition block
32
39
  # @return [void]
33
40
  def layer(name, &block)
34
- builder = LayerBuilder.new(name: name, styles: @styles, defaults: layer_defaults)
41
+ builder = LayerBuilder.new(name: name, styles: @styles, mapping_presets: @mapping_presets, defaults: layer_defaults, strict: @strict)
35
42
  builder.evaluate(&block)
36
43
  @layers << builder.to_h
37
44
  end
@@ -96,16 +103,7 @@ module Vizcore
96
103
  end
97
104
 
98
105
  def deep_dup(value)
99
- case value
100
- when Hash
101
- value.each_with_object({}) do |(key, entry), output|
102
- output[key] = deep_dup(entry)
103
- end
104
- when Array
105
- value.map { |entry| deep_dup(entry) }
106
- else
107
- value
108
- end
106
+ Vizcore::DeepCopy.copy(value)
109
107
  end
110
108
  end
111
109
  end