1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
use std::cell::RefCell;
use std::f64::consts::PI as π;
use std::rc::Rc;
const R: usize = 0;
const G: usize = 1;
const B: usize = 2;
const A: usize = 3;
fn mod_(n: i16, m: i16) -> i16 {
((n % m) + m) % m
}
use crate::engine::{SNES_HEIGHT, SNES_WIDTH};
use crate::rom::distortion_effect::{
DistortionEffect, HORIZONTAL, HORIZONTAL_INTERLACED, VERTICAL,
};
#[allow(non_snake_case)]
#[derive(Debug)]
pub struct Distorter {
bitmap: Rc<RefCell<Vec<i16>>>,
pub effect: Rc<RefCell<DistortionEffect>>,
C1: f64,
C2: f64,
C3: f64,
amplitude: f64,
frequency: f64,
compression: i16,
speed: f64,
}
impl Distorter {
pub fn new(bitmap: Rc<RefCell<Vec<i16>>>) -> Self {
// There is some redundancy here: 'effect' is currently what is used
// in computing frames, although really there should be a list of
// four different effects ('dist') which are used in sequence.
Distorter {
bitmap,
effect: Rc::new(RefCell::new(DistortionEffect::new(0))),
// NOTE: Another discrepancy from Java: These values should be "short" and must have a specific precision. This seems to affect backgrounds with distortEffect === HORIZONTAL
C1: 1. / 512.,
C2: 8. * π / (1024. * 256.),
C3: π / 60.,
amplitude: 0.,
compression: 0,
frequency: 0.,
speed: 0.,
}
}
fn set_offset_constants(&mut self, ticks: u64, effect: &DistortionEffect) {
let amplitude = effect.amplitude();
let amplitude_acceleration = effect.amplitude_acceleration();
let compression = effect.compression();
let compression_acceleration = effect.compression_acceleration();
let frequency = effect.frequency();
let frequency_acceleration = effect.frequency_acceleration();
let speed = effect.speed();
// Compute "current" values of amplitude, frequency and compression.
let t2 = ticks * 2;
self.amplitude = self.C1 * f64::from(amplitude + amplitude_acceleration * t2 as i16);
self.frequency = self.C2 * f64::from(frequency + frequency_acceleration * t2 as i16);
self.compression = 1 + (compression + compression_acceleration * t2 as i16) / 256;
self.speed = self.C3 * f64::from(speed) * ticks as f64;
}
#[allow(non_snake_case)]
fn S(&self, y: i16) -> i16 {
(f64::from(self.amplitude)
* (f64::from(self.frequency) * f64::from(y) + f64::from(self.speed)).sin())
.round() as i16
}
pub fn overlay_frame(
&mut self,
dst: &mut [u8],
letterbox: u8,
ticks: u64,
alpha: f32,
erase: bool,
) {
let bitmap = self.bitmap.clone();
self.compute_frame(
dst,
&bitmap.borrow(),
letterbox,
ticks,
alpha,
erase,
self.effect.clone(),
);
}
/// Evaluates the distortion effect at the given destination line and
/// time value and returns the computed offset value.
/// If the distortion mode is horizontal, this offset should be interpreted
/// as the number of pixels to offset the given line's starting x position.
/// If the distortion mode is vertical, this offset should be interpreted as
/// the y-coordinate of the line from the source bitmap to draw at the given
/// y-coordinate in the destination bitmap.
/// @param y
/// The y-coordinate of the destination line to evaluate for
/// @param t
/// The number of ticks since beginning animation
/// @return
/// The distortion offset for the given (y, t) coordinates
fn get_applied_offset(&self, y: i16, distortion_effect: u8) -> i16 {
let s = self.S(y);
match distortion_effect {
HORIZONTAL => s,
HORIZONTAL_INTERLACED => {
if y % 2 == 0 {
-s
} else {
s
}
}
VERTICAL => {
// Compute L
mod_(s + y * self.compression, 256)
}
_ => s,
}
}
fn compute_frame(
&mut self,
destination_bitmap: &mut [u8],
source_bitmap: &[i16],
letterbox: u8,
ticks: u64,
alpha: f32,
erase: bool,
effect: Rc<RefCell<DistortionEffect>>,
) {
let distortion_effect = effect.borrow().type_();
let new_bitmap = destination_bitmap;
let old_bitmap = source_bitmap;
// TODO: Hardcoding is bad
let dst_stride = 1024;
let src_stride = 1024;
// Given the list of 4 distortions and the tick count, decide which
// effect to use:
// Basically, we have 4 effects, each possibly with a duration.
// Evaluation order is: 1, 2, 3, 0
// If the first effect is null, control transitions to the second effect.
// If the first and second effects are null, no effect occurs.
// If any other effect is null, the sequence is truncated.
// If a non-null effect has a zero duration, it will not be switched
// away from.
// Essentially, this configuration sets up a precise and repeating
// sequence of between 0 and 4 different distortion effects. Once we
// compute the sequence, computing the particular frame of which distortion
// to use becomes easy; simply mod the tick count by the total duration
// of the effects that are used in the sequence, then check the remainder
// against the cumulative durations of each effect.
// I guess the trick is to be sure that my description above is correct.
// Heh.
let mut b_pos: usize;
let mut s_pos: usize;
let mut dx;
self.set_offset_constants(ticks, &effect.borrow());
for y in 0..usize::from(SNES_HEIGHT) {
let offset = self.get_applied_offset(y as i16, distortion_effect);
#[allow(non_snake_case)]
let L = if distortion_effect == VERTICAL {
offset
} else {
y as i16
};
for x in 0..usize::from(SNES_WIDTH) {
b_pos = x * 4 + y * dst_stride;
if y < letterbox.into() || y > usize::from(SNES_HEIGHT) - usize::from(letterbox) {
new_bitmap[b_pos + R] = 0;
new_bitmap[b_pos + G] = 0;
new_bitmap[b_pos + B] = 0;
new_bitmap[b_pos + A] = 255;
continue;
}
dx = x;
if distortion_effect == HORIZONTAL || distortion_effect == HORIZONTAL_INTERLACED {
dx = mod_(x as i16 + offset, SNES_WIDTH as i16) as usize;
}
s_pos = dx * 4 + L as usize * src_stride;
// Either copy or add to the destination bitmap.
if erase {
new_bitmap[b_pos + R] = (alpha * f32::from(old_bitmap[s_pos + R])) as u8;
new_bitmap[b_pos + G] = (alpha * f32::from(old_bitmap[s_pos + G])) as u8;
new_bitmap[b_pos + B] = (alpha * f32::from(old_bitmap[s_pos + B])) as u8;
new_bitmap[b_pos + A] = 255;
} else {
new_bitmap[b_pos + R] += (alpha * f32::from(old_bitmap[s_pos + R])) as u8;
new_bitmap[b_pos + G] += (alpha * f32::from(old_bitmap[s_pos + G])) as u8;
new_bitmap[b_pos + B] += (alpha * f32::from(old_bitmap[s_pos + B])) as u8;
new_bitmap[b_pos + A] = 255;
}
}
}
}
}