Make Tile swap slow at a low Swap rate, on a curved slider
A tile has to come back to the cut it started with for the clip to loop, so every tile swapped at least twice a clip, and below that rounding left every tile still: the bottom of the slider was either frozen or busy. Swap rate is now the average per tile: below two swaps a clip only a share of the tiles swap (twice each), fewer the lower it goes, and above it each tile rounds up or down by its own draw. ParamSpec::slider() takes an optional curve: the scale runs over the slider's travel and sliderValue/sliderPosition map it onto the number as position^curve. Swap rate uses a curve of 3, so the low end gets most of the slider; the command line and project file are unchanged. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
+30
-18
@@ -26,10 +26,11 @@ const std::vector<ParamSpec>& TileSwapEffect::params() const {
|
||||
ParamSpec::makeBool("tileswap_keep_order", "Cycle images in order", false,
|
||||
"Take tiles from the images in order instead of picking randomly."),
|
||||
ParamSpec::makeDouble("tileswap_rate", "Swap rate", 1.0, 0.0, 5.0,
|
||||
"Swaps per second of each tile, rounded so that the clip loops "
|
||||
"without a jump; 0 for none.",
|
||||
"Swaps per second of each tile, on average; 0 for none. Below two "
|
||||
"swaps in the clip only some of the tiles swap, fewer the lower it "
|
||||
"goes, so that the clip still loops without a jump.",
|
||||
0.01, 2)
|
||||
.slider(),
|
||||
.slider(3.0),
|
||||
};
|
||||
return specs;
|
||||
}
|
||||
@@ -57,8 +58,8 @@ struct Settings {
|
||||
|
||||
double unit(Rng& rng) { return static_cast<double>(rng.next() >> 11) * 0x1.0p-53; }
|
||||
|
||||
// Where a tile sits for the whole clip, when in its swaps it turns over, and
|
||||
// the seed its cuts come from.
|
||||
// Where a tile sits for the whole clip, when in its swaps it turns over, the
|
||||
// seed its cuts come from, and the draw that settles how often it swaps.
|
||||
struct Cell {
|
||||
int x;
|
||||
int y;
|
||||
@@ -66,6 +67,7 @@ struct Cell {
|
||||
int h;
|
||||
double phase;
|
||||
std::uint64_t seed;
|
||||
double share;
|
||||
};
|
||||
|
||||
} // namespace
|
||||
@@ -81,7 +83,7 @@ Image TileSwapEffect::compose(const std::vector<Image>& sources, int width, int
|
||||
Rng steady = at.steady();
|
||||
std::vector<Cell> cells;
|
||||
auto addCell = [&](int x, int y, int w, int h) {
|
||||
cells.push_back(Cell{x, y, w, h, 0.0, 0});
|
||||
cells.push_back(Cell{x, y, w, h, 0.0, 0, 0.0});
|
||||
};
|
||||
if (scatter) {
|
||||
const Image& src = sources[steady.below(sources.size() - 1)];
|
||||
@@ -120,27 +122,37 @@ Image TileSwapEffect::compose(const std::vector<Image>& sources, int width, int
|
||||
for (Cell& cell : cells) {
|
||||
cell.phase = unit(steady);
|
||||
cell.seed = steady.next();
|
||||
cell.share = unit(steady);
|
||||
}
|
||||
|
||||
// Where this frame is in time: each tile has swapped a whole number of
|
||||
// times, counted round the clip so that the frame after the last shows
|
||||
// what the first does.
|
||||
long swaps = 0;
|
||||
double perSecond = 0.0;
|
||||
if (settings.rate > 0.0) {
|
||||
perSecond = at.loopRate(settings.rate);
|
||||
swaps = std::max(1L, std::lround(perSecond * at.length()));
|
||||
}
|
||||
// How often each tile swaps in one pass of the clip, `each` on average. A
|
||||
// tile has to come back to the cut it started with, so one that swaps at
|
||||
// all swaps at least twice; below that a share of the tiles swap twice and
|
||||
// the rest hold still, so a low rate thins out the tiles that move instead
|
||||
// of moving every one of them. Above it, each tile rounds `each` up or
|
||||
// down by its own draw.
|
||||
const double length = at.length();
|
||||
const double each = settings.rate * length;
|
||||
auto swapsOf = [&](const Cell& cell) -> long {
|
||||
if (each <= 0.0) return 0;
|
||||
if (each < 2.0) return cell.share < each / 2.0 ? 2 : 0;
|
||||
const double whole = std::floor(each);
|
||||
return static_cast<long>(whole) + (cell.share < each - whole ? 1 : 0);
|
||||
};
|
||||
|
||||
// What a tile shows depends only on the tile and its swap, so it holds
|
||||
// still between swaps and any frame can be drawn on its own.
|
||||
// What a tile shows depends only on the tile and how many times it has
|
||||
// swapped, counted round the clip so that the frame after the last shows
|
||||
// what the first does; so it holds still between swaps and any frame can
|
||||
// be drawn on its own.
|
||||
std::vector<Tile> plan;
|
||||
plan.reserve(cells.size());
|
||||
for (std::size_t i = 0; i < cells.size(); ++i) {
|
||||
const Cell& cell = cells[i];
|
||||
const long swaps = swapsOf(cell);
|
||||
std::uint64_t turn = 0;
|
||||
if (swaps > 0) {
|
||||
const auto passed = static_cast<long>(std::floor(at.seconds() * perSecond + cell.phase));
|
||||
const auto passed =
|
||||
static_cast<long>(std::floor(at.seconds() * swaps / length + cell.phase));
|
||||
turn = static_cast<std::uint64_t>(passed % swaps);
|
||||
}
|
||||
Rng cut(cell.seed + turn * 0x9e3779b97f4a7c15ULL);
|
||||
|
||||
Reference in New Issue
Block a user