vizcore 1.0.0 → 1.1.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.
- checksums.yaml +4 -4
- data/README.md +66 -648
- data/docs/assets/playground-worker.js +373 -0
- data/docs/assets/playground.css +440 -0
- data/docs/assets/playground.js +652 -0
- data/docs/index.html +2 -1
- data/docs/playground.html +81 -0
- data/docs/shape_dsl.md +269 -0
- data/frontend/index.html +26 -0
- data/frontend/src/custom-shape-param-controls.js +106 -0
- data/frontend/src/main.js +268 -0
- data/frontend/src/mapping-target-selector.js +109 -0
- data/frontend/src/renderer/engine.js +10 -1
- data/frontend/src/renderer/layer-manager.js +18 -4
- data/frontend/src/shape-editor-controls.js +157 -0
- data/frontend/src/visuals/geometry.js +425 -27
- data/frontend/src/visuals/shape-renderer.js +475 -0
- data/frontend/src/visuals/svg-arc.js +104 -0
- data/lib/vizcore/cli/dsl_reference.rb +1 -1
- data/lib/vizcore/cli/scene_validator.rb +92 -0
- data/lib/vizcore/dsl/layer_builder.rb +795 -7
- data/lib/vizcore/dsl/mapping_resolver.rb +158 -4
- data/lib/vizcore/layer_catalog.rb +4 -2
- data/lib/vizcore/renderer/scene_frame_source.rb +14 -1
- data/lib/vizcore/renderer/snapshot_renderer.rb +507 -15
- data/lib/vizcore/server/frame_broadcaster.rb +53 -4
- data/lib/vizcore/server/runner.rb +21 -0
- data/lib/vizcore/shape.rb +719 -0
- data/lib/vizcore/version.rb +1 -1
- data/lib/vizcore.rb +1 -0
- data/sig/vizcore.rbs +100 -1
- metadata +12 -1
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
+
require_relative "../shape"
|
|
4
|
+
|
|
3
5
|
module Vizcore
|
|
4
6
|
module DSL
|
|
5
7
|
# Resolves `map` definitions into concrete per-layer parameter values.
|
|
@@ -11,17 +13,19 @@ module Vizcore
|
|
|
11
13
|
# @param scene_layers [Array<Hash>]
|
|
12
14
|
# @param audio [Hash]
|
|
13
15
|
# @return [Array<Hash>] normalized layer payloads with resolved params
|
|
14
|
-
def resolve_layers(scene_layers:, audio:)
|
|
16
|
+
def resolve_layers(scene_layers:, audio:, time: 0.0, frame: 0, resolution: [1280, 720], globals: {}, custom_shape_overrides: {})
|
|
15
17
|
normalize_scene_layers(scene_layers).map do |layer|
|
|
16
|
-
resolve_layer(layer, audio)
|
|
18
|
+
resolve_layer(layer, audio, time: time, frame: frame, resolution: resolution, globals: globals, custom_shape_overrides: custom_shape_overrides)
|
|
17
19
|
end
|
|
18
20
|
end
|
|
19
21
|
|
|
20
22
|
private
|
|
21
23
|
|
|
22
|
-
def resolve_layer(layer, audio)
|
|
23
|
-
params = (layer[:params] || {})
|
|
24
|
+
def resolve_layer(layer, audio, time:, frame:, resolution:, globals:, custom_shape_overrides:)
|
|
25
|
+
params = deep_dup(layer[:params] || {})
|
|
26
|
+
apply_custom_shape_overrides!(params, layer_name: layer[:name], custom_shape_overrides: custom_shape_overrides)
|
|
24
27
|
merge_resolved_mappings!(params, resolve_mappings(layer[:mappings], audio, layer_name: layer[:name]))
|
|
28
|
+
expand_dynamic_custom_shapes!(params, layer: layer, audio: audio, time: time, frame: frame, resolution: resolution, globals: globals)
|
|
25
29
|
|
|
26
30
|
output = {
|
|
27
31
|
name: layer.fetch(:name).to_s,
|
|
@@ -57,6 +61,95 @@ module Vizcore
|
|
|
57
61
|
end
|
|
58
62
|
end
|
|
59
63
|
|
|
64
|
+
def expand_dynamic_custom_shapes!(params, layer:, audio:, time:, frame:, resolution:, globals:)
|
|
65
|
+
descriptors = Array(params.delete(:custom_shapes) || params.delete("custom_shapes"))
|
|
66
|
+
return if descriptors.empty?
|
|
67
|
+
|
|
68
|
+
params[:shapes] = Array(params[:shapes])
|
|
69
|
+
controls = []
|
|
70
|
+
descriptors.each_with_index do |descriptor, index|
|
|
71
|
+
start_index = params[:shapes].length
|
|
72
|
+
expanded = expand_dynamic_custom_shape(descriptor, layer: layer, palette: params[:palette], audio: audio, time: time, frame: frame, resolution: resolution, globals: globals)
|
|
73
|
+
params[:shapes].concat(expanded)
|
|
74
|
+
controls << custom_shape_control_descriptor(descriptor, index: index, start_index: start_index, count: expanded.length)
|
|
75
|
+
end
|
|
76
|
+
params[:custom_shape_controls] = controls unless controls.empty?
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
def expand_dynamic_custom_shape(descriptor, layer:, palette:, audio:, time:, frame:, resolution:, globals:)
|
|
80
|
+
values = Hash(descriptor)
|
|
81
|
+
renderer = values.fetch(:renderer)
|
|
82
|
+
shape_name = values[:name] || renderer
|
|
83
|
+
primitives = Vizcore::Shape.expand_custom_shape(
|
|
84
|
+
renderer,
|
|
85
|
+
params: Hash(values[:params] || {}),
|
|
86
|
+
shape_id: values[:shape_id],
|
|
87
|
+
layer_name: layer[:name],
|
|
88
|
+
palette: Array(palette),
|
|
89
|
+
audio: audio,
|
|
90
|
+
time: time,
|
|
91
|
+
frame: frame,
|
|
92
|
+
resolution: resolution,
|
|
93
|
+
globals: globals,
|
|
94
|
+
shape_name: shape_name
|
|
95
|
+
)
|
|
96
|
+
primitives.each { |primitive| apply_custom_shape_attributes!(primitive, values) }
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
def custom_shape_control_descriptor(descriptor, index:, start_index:, count:)
|
|
100
|
+
values = Hash(descriptor)
|
|
101
|
+
{
|
|
102
|
+
index: index,
|
|
103
|
+
name: (values[:name] || values["name"] || "custom_shape").to_s,
|
|
104
|
+
params: deep_dup(Hash(values[:params] || values["params"] || {})),
|
|
105
|
+
param_schema: Array(values[:param_schema] || values["param_schema"]).map { |entry| deep_dup(entry) },
|
|
106
|
+
shape_indices: (start_index...(start_index + count)).to_a
|
|
107
|
+
}
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
def apply_custom_shape_overrides!(params, layer_name:, custom_shape_overrides:)
|
|
111
|
+
layer_overrides = custom_shape_layer_overrides(custom_shape_overrides, layer_name)
|
|
112
|
+
return if layer_overrides.empty?
|
|
113
|
+
|
|
114
|
+
descriptors = Array(params[:custom_shapes] || params["custom_shapes"])
|
|
115
|
+
layer_overrides.each do |index, values|
|
|
116
|
+
descriptor = descriptors[Integer(index)]
|
|
117
|
+
next unless descriptor && values.is_a?(Hash)
|
|
118
|
+
|
|
119
|
+
descriptor[:params] ||= {}
|
|
120
|
+
values.each do |param_name, value|
|
|
121
|
+
key = param_name.to_sym
|
|
122
|
+
descriptor[:params][key] = value
|
|
123
|
+
end
|
|
124
|
+
rescue ArgumentError, TypeError
|
|
125
|
+
next
|
|
126
|
+
end
|
|
127
|
+
end
|
|
128
|
+
|
|
129
|
+
def custom_shape_layer_overrides(overrides, layer_name)
|
|
130
|
+
values = Hash(overrides)
|
|
131
|
+
name = layer_name.to_s
|
|
132
|
+
Hash(values[name] || values[layer_name.to_sym] || {})
|
|
133
|
+
rescue TypeError
|
|
134
|
+
{}
|
|
135
|
+
end
|
|
136
|
+
|
|
137
|
+
def apply_custom_shape_attributes!(primitive, descriptor)
|
|
138
|
+
style = Hash(descriptor[:style] || {})
|
|
139
|
+
style.each do |key, value|
|
|
140
|
+
symbol_key = key.to_sym
|
|
141
|
+
if symbol_key == :opacity && primitive.key?(:opacity)
|
|
142
|
+
primitive[:opacity] = numeric(style[:opacity] || style["opacity"], :opacity) * numeric(primitive[:opacity], :opacity)
|
|
143
|
+
else
|
|
144
|
+
primitive[symbol_key] = deep_dup(value) unless primitive.key?(symbol_key)
|
|
145
|
+
end
|
|
146
|
+
end
|
|
147
|
+
|
|
148
|
+
transform = Hash(descriptor[:transform] || {})
|
|
149
|
+
primitive[:transform] = compose_shape_transform(transform, primitive[:transform]) unless transform.empty?
|
|
150
|
+
primitive
|
|
151
|
+
end
|
|
152
|
+
|
|
60
153
|
def assign_nested_param(container, path, value)
|
|
61
154
|
key = path.shift
|
|
62
155
|
if path.empty?
|
|
@@ -65,6 +158,7 @@ module Vizcore
|
|
|
65
158
|
end
|
|
66
159
|
|
|
67
160
|
next_container = nested_value(container, key)
|
|
161
|
+
next_container = create_nested_container(container, key, path.first) if next_container.nil?
|
|
68
162
|
return unless next_container
|
|
69
163
|
|
|
70
164
|
assign_nested_param(next_container, path, value)
|
|
@@ -77,6 +171,13 @@ module Vizcore
|
|
|
77
171
|
nil
|
|
78
172
|
end
|
|
79
173
|
|
|
174
|
+
def create_nested_container(container, key, next_key)
|
|
175
|
+
return unless container.is_a?(Hash)
|
|
176
|
+
|
|
177
|
+
value = integer_key?(next_key) ? [] : {}
|
|
178
|
+
container[key.to_sym] = value
|
|
179
|
+
end
|
|
180
|
+
|
|
80
181
|
def assign_nested_value(container, key, value)
|
|
81
182
|
if container.is_a?(Array) && integer_key?(key)
|
|
82
183
|
container[key.to_i] = value
|
|
@@ -89,6 +190,46 @@ module Vizcore
|
|
|
89
190
|
value.match?(/\A\d+\z/)
|
|
90
191
|
end
|
|
91
192
|
|
|
193
|
+
def compose_shape_transform(parent, child)
|
|
194
|
+
return deep_dup(child || {}) unless parent
|
|
195
|
+
|
|
196
|
+
child ||= {}
|
|
197
|
+
output = deep_dup(parent)
|
|
198
|
+
output[:translate] = add_shape_xy(parent[:translate], child[:translate]) if child.key?(:translate)
|
|
199
|
+
output[:origin] = child[:origin] if child.key?(:origin)
|
|
200
|
+
output[:rotate] = numeric(parent[:rotate] || 0, :rotate) + numeric(child[:rotate] || 0, :rotate) if child.key?(:rotate)
|
|
201
|
+
output[:scale] = multiply_shape_scale(parent[:scale], child[:scale]) if child.key?(:scale)
|
|
202
|
+
output
|
|
203
|
+
end
|
|
204
|
+
|
|
205
|
+
def add_shape_xy(parent, child)
|
|
206
|
+
parent ||= {}
|
|
207
|
+
child ||= {}
|
|
208
|
+
{
|
|
209
|
+
x: numeric(parent[:x] || parent["x"] || 0, :"translate.x") + numeric(child[:x] || child["x"] || 0, :"translate.x"),
|
|
210
|
+
y: numeric(parent[:y] || parent["y"] || 0, :"translate.y") + numeric(child[:y] || child["y"] || 0, :"translate.y")
|
|
211
|
+
}
|
|
212
|
+
end
|
|
213
|
+
|
|
214
|
+
def multiply_shape_scale(parent, child)
|
|
215
|
+
parent = shape_scale_pair(parent)
|
|
216
|
+
child = shape_scale_pair(child)
|
|
217
|
+
{ x: parent[:x] * child[:x], y: parent[:y] * child[:y] }
|
|
218
|
+
end
|
|
219
|
+
|
|
220
|
+
def shape_scale_pair(value)
|
|
221
|
+
return { x: numeric(value[:x] || value["x"] || 1, :"scale.x"), y: numeric(value[:y] || value["y"] || 1, :"scale.y") } if value.is_a?(Hash)
|
|
222
|
+
|
|
223
|
+
scale = numeric(value || 1, :scale)
|
|
224
|
+
{ x: scale, y: scale }
|
|
225
|
+
end
|
|
226
|
+
|
|
227
|
+
def numeric(value, name)
|
|
228
|
+
Float(value)
|
|
229
|
+
rescue ArgumentError, TypeError
|
|
230
|
+
raise ArgumentError, "param #{name} must be numeric"
|
|
231
|
+
end
|
|
232
|
+
|
|
92
233
|
def resolve_source_value(source, audio)
|
|
93
234
|
case source[:kind]&.to_sym
|
|
94
235
|
when :amplitude
|
|
@@ -193,6 +334,19 @@ module Vizcore
|
|
|
193
334
|
Array(scene_layers).map { |layer| deep_symbolize(layer) }
|
|
194
335
|
end
|
|
195
336
|
|
|
337
|
+
def deep_dup(value)
|
|
338
|
+
case value
|
|
339
|
+
when Hash
|
|
340
|
+
value.each_with_object({}) do |(key, entry), output|
|
|
341
|
+
output[key] = deep_dup(entry)
|
|
342
|
+
end
|
|
343
|
+
when Array
|
|
344
|
+
value.map { |entry| deep_dup(entry) }
|
|
345
|
+
else
|
|
346
|
+
value
|
|
347
|
+
end
|
|
348
|
+
end
|
|
349
|
+
|
|
196
350
|
def deep_symbolize(value)
|
|
197
351
|
case value
|
|
198
352
|
when Hash
|
|
@@ -151,10 +151,12 @@ module Vizcore
|
|
|
151
151
|
aliases: %i[shapes shape_layer],
|
|
152
152
|
params: COMMON_PARAMS.merge(
|
|
153
153
|
shapes: "Array<Hash>",
|
|
154
|
+
shape_schema_version: "Integer",
|
|
155
|
+
units: "Symbol",
|
|
154
156
|
color_shift: "Float"
|
|
155
157
|
),
|
|
156
|
-
mappable_params: %i[color_shift opacity],
|
|
157
|
-
description: "Declarative 2D circle and
|
|
158
|
+
mappable_params: %i[color_shift opacity shapes],
|
|
159
|
+
description: "Declarative and Ruby-generated 2D circle, line, rect, polygon, polyline, path, and star primitives rendered by the browser."
|
|
158
160
|
),
|
|
159
161
|
Capability.new(
|
|
160
162
|
type: :mesh,
|
|
@@ -22,6 +22,7 @@ module Vizcore
|
|
|
22
22
|
@input_manager.start
|
|
23
23
|
@capture_size = capture_size
|
|
24
24
|
@pipeline = build_pipeline
|
|
25
|
+
@frame_count = 0
|
|
25
26
|
self
|
|
26
27
|
end
|
|
27
28
|
|
|
@@ -30,7 +31,13 @@ module Vizcore
|
|
|
30
31
|
ensure_started!
|
|
31
32
|
|
|
32
33
|
audio = @pipeline.call(@input_manager.capture_frame(@capture_size))
|
|
33
|
-
|
|
34
|
+
@frame_count += 1
|
|
35
|
+
layers = Vizcore::DSL::MappingResolver.new.resolve_layers(
|
|
36
|
+
scene_layers: @scene[:layers],
|
|
37
|
+
audio: audio,
|
|
38
|
+
time: frame_time,
|
|
39
|
+
frame: @frame_count
|
|
40
|
+
)
|
|
34
41
|
|
|
35
42
|
{
|
|
36
43
|
scene: { name: @scene[:name], layers: layers },
|
|
@@ -82,6 +89,12 @@ module Vizcore
|
|
|
82
89
|
)
|
|
83
90
|
end
|
|
84
91
|
|
|
92
|
+
def frame_time
|
|
93
|
+
return 0.0 unless @frame_rate
|
|
94
|
+
|
|
95
|
+
(@frame_count - 1).fdiv(@frame_rate)
|
|
96
|
+
end
|
|
97
|
+
|
|
85
98
|
def audio_normalize_settings
|
|
86
99
|
Hash(@definition[:analysis] || {})[:audio_normalize]
|
|
87
100
|
rescue StandardError
|