summaryrefslogtreecommitdiff
path: root/src/code/z_rumble.c
blob: fcc39adee8b249551f970faa7d6ed7990add13ca (plain)
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
/**
 * @file z_rumble.c
 *
 * This file implements an interface for the game state to set up, manage and request use of the rumble pak. Despite
 * some parts of the system appearing to accommodate all four controller ports, only controller 1 can be instructed
 * to rumble.
 * This file is half of the system that runs on the graph thread alongside the game state. The rest of the system that
 * processes the requests runs on the padmgr thread and is implemented in `sys_rumble.c`.
 *
 * @see sys_rumble.c
 *
 * @note Original filename is likely z_vibrate.c or similar as it is ordered after z_ss_sram.c and before z_view.c
 */
#include "rumble.h"
#include "padmgr.h"
#include "z_math.h"

static s32 sUnused[4];
RumbleMgr sRumbleMgr;

/**
 * Padmgr callback to update the state of rumble on Vertical Retrace.
 *
 * Unlike every other function in this file, this runs on the padmgr thread.
 */
void Rumble_Update(PadMgr* padMgr, void* arg) {
    RumbleMgr_Update(&sRumbleMgr);
    PadMgr_RumbleSet(padMgr, sRumbleMgr.rumbleEnable);
}

/**
 * Forces the rumble state to use the supplied parameters.
 * The parameters are the same as in `Rumble_Request`.
 *
 * @see Rumble_Request
 */
void Rumble_Override(f32 distSq, u8 sourceStrength, u8 duration, u8 decreaseRate) {
    s32 dist;
    s32 strength;

    if (distSq > SQ(1000)) {
        dist = 1000;
    } else {
        dist = sqrtf(distSq);
    }

    if (dist < 1000 && sourceStrength != 0 && decreaseRate != 0) {
        // Decrease the strength linearly with distance
        strength = sourceStrength - (dist * 255) / 1000;

        if (strength > 0) {
            // Note: sRumbleMgr is a shared resource between the graph and padmgr threads, no locking is done
            // to ensure that the entire request is written before it is possibly used.
            sRumbleMgr.overrideStrength = strength;
            sRumbleMgr.overrideDuration = duration;
            sRumbleMgr.overrideDecreaseRate = decreaseRate;
        }
    }
}

/**
 * Submits a request to the rumble manager with the properties given in the arguments. If there is no free request slot
 * the request is silently dropped.
 *
 * @param distSq
 *     Squared distance, usually taken to be from an apparent source to the player in world coordinates.
 * @param sourceStrength
 *     The strength of the rumble at 0 distance from the source.
 *     The rumble source strength decreases linearly with distance, a distance of 0 results in the full source strength
 *     while a distance of 1000 or greater is discarded. A source strength of 0 is discarded. A minimum source strength
 *     of 1 drops to 0 at 3 units of distance from the source. A maximum source strength of 255 drops to 0 at 1000
 *     units of distance from the source.
 *     Note that, once the request has been submitted, if the distance to the source changes in subsequent frames while
 *     the rumble request is still running, the request will not be updated with the new distance.
 * @param duration
 *     The duration for which the rumble will sustain full strength. It is measured in Vertical Retraces rather than
 *     game frames. There are ~60 Retraces/s on NTSC and 50 Retraces/s on PAL.
 * @param decreaseRate
 *     The amount by which to lower the strength every Vertical Retrace once duration has hit 0.
 */
void Rumble_Request(f32 distSq, u8 sourceStrength, u8 duration, u8 decreaseRate) {
    s32 dist;
    s32 strength;
    s32 i;

    if (distSq > SQ(1000)) {
        dist = 1000;
    } else {
        dist = sqrtf(distSq);
    }

    if (dist < 1000 && sourceStrength != 0 && decreaseRate != 0) {
        // Decrease the strength linearly with distance
        strength = sourceStrength - (dist * 255) / 1000;

        for (i = 0; i < RUMBLE_MAX_REQUESTS; i++) {
            // Search for an empty slot
            if (sRumbleMgr.reqStrengths[i] == 0) {
                if (strength > 0) {
                    // Note: sRumbleMgr is a shared resource between the graph and padmgr threads, no locking is done
                    // to ensure that the entire request is written before it is possibly used.
                    sRumbleMgr.reqStrengths[i] = strength;
                    sRumbleMgr.reqDurations[i] = duration;
                    sRumbleMgr.reqDecreaseRates[i] = decreaseRate;
                }
                break;
            }
        }
    }
}

void Rumble_Init(void) {
    RumbleMgr_Init(&sRumbleMgr);
    PADMGR_SET_RETRACE_CALLACK(&gPadMgr, Rumble_Update, NULL);
}

void Rumble_Destroy(void) {
    PadMgr* padmgr = &gPadMgr;

    PADMGR_UNSET_RETRACE_CALLACK(padmgr, Rumble_Update, NULL);
    RumbleMgr_Destroy(&sRumbleMgr);
}

s32 Rumble_Controller1HasRumblePak(void) {
    return gPadMgr.pakType[0] == CONT_PAK_RUMBLE;
}

void Rumble_Reset(void) {
    sRumbleMgr.state = RUMBLE_STATE_RESET;
}

void Rumble_ClearRequests(void) {
    sRumbleMgr.state = RUMBLE_STATE_CLEAR;
}

void Rumble_SetUpdateEnabled(u32 enable) {
    sRumbleMgr.updateEnabled = !!enable;
}