Add SMXPanelAnimation.
This commit is contained in:
parent
303283624a
commit
2628c078fa
14
sdk/SMX.h
14
sdk/SMX.h
@ -135,6 +135,13 @@ enum SMXUpdateCallbackReason {
|
|||||||
SMXUpdateCallback_FactoryResetCommandComplete
|
SMXUpdateCallback_FactoryResetCommandComplete
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Bits for SMXConfig::flags.
|
||||||
|
enum SMXConfigFlags {
|
||||||
|
// This is used to store whether SMXConfig is in GIF animation mode or
|
||||||
|
// not. If set, SMXConfig will use animations.
|
||||||
|
PlatformFlags_AutoLightingUsePressedAnimations = 1 << 0,
|
||||||
|
};
|
||||||
|
|
||||||
// The configuration for a connected controller. This can be retrieved with SMX_GetConfig
|
// The configuration for a connected controller. This can be retrieved with SMX_GetConfig
|
||||||
// and modified with SMX_SetConfig.
|
// and modified with SMX_SetConfig.
|
||||||
//
|
//
|
||||||
@ -227,12 +234,15 @@ struct SMXConfig
|
|||||||
// This is disabled by default.
|
// This is disabled by default.
|
||||||
uint16_t debounceDelayMs = 0;
|
uint16_t debounceDelayMs = 0;
|
||||||
|
|
||||||
|
// Packed flags (currently only used by SMXConfig).
|
||||||
|
uint8_t flags = 0;
|
||||||
|
|
||||||
// Pad the struct to 250 bytes. This keeps this struct size from changing
|
// Pad the struct to 250 bytes. This keeps this struct size from changing
|
||||||
// as we add fields, so the ABI doesn't change. Applications should leave
|
// as we add fields, so the ABI doesn't change. Applications should leave
|
||||||
// any data in here unchanged when calling SMX_SetConfig.
|
// any data in here unchanged when calling SMX_SetConfig.
|
||||||
uint8_t padding[164];
|
uint8_t padding[163];
|
||||||
};
|
};
|
||||||
static_assert(offsetof(SMXConfig, padding) == 86, "Expected 86 bytes"); // includes one padding byte
|
static_assert(offsetof(SMXConfig, padding) == 87, "Expected 87 bytes"); // includes one padding byte
|
||||||
static_assert(sizeof(SMXConfig) == 250, "Expected 250 bytes");
|
static_assert(sizeof(SMXConfig) == 250, "Expected 250 bytes");
|
||||||
|
|
||||||
// The values (except for Off) correspond with the protocol and must not be changed.
|
// The values (except for Off) correspond with the protocol and must not be changed.
|
||||||
|
@ -7,6 +7,7 @@
|
|||||||
#include "SMXManager.h"
|
#include "SMXManager.h"
|
||||||
#include "SMXDevice.h"
|
#include "SMXDevice.h"
|
||||||
#include "SMXBuildVersion.h"
|
#include "SMXBuildVersion.h"
|
||||||
|
#include "SMXPanelAnimation.h" // for SMX_LightsAnimation_SetAuto
|
||||||
using namespace std;
|
using namespace std;
|
||||||
using namespace SMX;
|
using namespace SMX;
|
||||||
|
|
||||||
@ -41,6 +42,9 @@ SMX_API void SMX_Start(SMXUpdateCallback callback, void *pUser)
|
|||||||
|
|
||||||
SMX_API void SMX_Stop()
|
SMX_API void SMX_Stop()
|
||||||
{
|
{
|
||||||
|
// If lights animation is running, shut it down first.
|
||||||
|
SMX_LightsAnimation_SetAuto(false);
|
||||||
|
|
||||||
SMXManager::g_pSMX.reset();
|
SMXManager::g_pSMX.reset();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -22,6 +22,7 @@
|
|||||||
<ClInclude Include="SMXHelperThread.h" />
|
<ClInclude Include="SMXHelperThread.h" />
|
||||||
<ClInclude Include="SMXManager.h" />
|
<ClInclude Include="SMXManager.h" />
|
||||||
<ClInclude Include="SMXThread.h" />
|
<ClInclude Include="SMXThread.h" />
|
||||||
|
<ClInclude Include="SMXPanelAnimation.h" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<ClCompile Include="Helpers.cpp" />
|
<ClCompile Include="Helpers.cpp" />
|
||||||
@ -34,6 +35,7 @@
|
|||||||
<ClCompile Include="SMXHelperThread.cpp" />
|
<ClCompile Include="SMXHelperThread.cpp" />
|
||||||
<ClCompile Include="SMXManager.cpp" />
|
<ClCompile Include="SMXManager.cpp" />
|
||||||
<ClCompile Include="SMXThread.cpp" />
|
<ClCompile Include="SMXThread.cpp" />
|
||||||
|
<ClCompile Include="SMXPanelAnimation.cpp" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<PropertyGroup Label="Globals">
|
<PropertyGroup Label="Globals">
|
||||||
<ProjectGuid>{C5FC0823-9896-4B7C-BFE1-B60DB671A462}</ProjectGuid>
|
<ProjectGuid>{C5FC0823-9896-4B7C-BFE1-B60DB671A462}</ProjectGuid>
|
||||||
|
@ -40,6 +40,9 @@
|
|||||||
<ClInclude Include="SMXGif.h">
|
<ClInclude Include="SMXGif.h">
|
||||||
<Filter>Source Files</Filter>
|
<Filter>Source Files</Filter>
|
||||||
</ClInclude>
|
</ClInclude>
|
||||||
|
<ClInclude Include="SMXPanelAnimation.h">
|
||||||
|
<Filter>Source Files</Filter>
|
||||||
|
</ClInclude>
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<ClCompile Include="SMX.cpp">
|
<ClCompile Include="SMX.cpp">
|
||||||
@ -72,5 +75,8 @@
|
|||||||
<ClCompile Include="SMXGif.cpp">
|
<ClCompile Include="SMXGif.cpp">
|
||||||
<Filter>Source Files</Filter>
|
<Filter>Source Files</Filter>
|
||||||
</ClCompile>
|
</ClCompile>
|
||||||
|
<ClCompile Include="SMXPanelAnimation.cpp">
|
||||||
|
<Filter>Source Files</Filter>
|
||||||
|
</ClCompile>
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
</Project>
|
</Project>
|
||||||
|
416
sdk/Windows/SMXPanelAnimation.cpp
Normal file
416
sdk/Windows/SMXPanelAnimation.cpp
Normal file
@ -0,0 +1,416 @@
|
|||||||
|
#include "SMXPanelAnimation.h"
|
||||||
|
#include "SMXManager.h"
|
||||||
|
#include "SMXDevice.h"
|
||||||
|
#include "SMXThread.h"
|
||||||
|
using namespace std;
|
||||||
|
using namespace SMX;
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
Mutex g_Lock;
|
||||||
|
}
|
||||||
|
|
||||||
|
#define LIGHTS_PER_PANEL 16
|
||||||
|
|
||||||
|
// XXX: go to sleep if there are no pads connected
|
||||||
|
|
||||||
|
struct AnimationState
|
||||||
|
{
|
||||||
|
SMXPanelAnimation animation;
|
||||||
|
|
||||||
|
// Seconds into the animation:
|
||||||
|
float fTime = 0;
|
||||||
|
|
||||||
|
// The currently displayed frame:
|
||||||
|
int iCurrentFrame = 0;
|
||||||
|
|
||||||
|
bool bPlaying = false;
|
||||||
|
|
||||||
|
double m_fLastUpdateTime = -1;
|
||||||
|
|
||||||
|
// Return the current animation frame.
|
||||||
|
const vector<SMXGif::Color> &GetAnimationFrame() const
|
||||||
|
{
|
||||||
|
// If we're not playing, return an empty array. As a sanity check, do this
|
||||||
|
// if the frame is out of bounds too.
|
||||||
|
if(!bPlaying || iCurrentFrame >= animation.m_aPanelGraphics.size())
|
||||||
|
{
|
||||||
|
static vector<SMXGif::Color> dummy;
|
||||||
|
return dummy;
|
||||||
|
}
|
||||||
|
|
||||||
|
return animation.m_aPanelGraphics[iCurrentFrame];
|
||||||
|
}
|
||||||
|
|
||||||
|
// Start the animation if it's not playing.
|
||||||
|
void Play()
|
||||||
|
{
|
||||||
|
bPlaying = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Stop and disable the animation.
|
||||||
|
void Stop()
|
||||||
|
{
|
||||||
|
bPlaying = false;
|
||||||
|
Rewind();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Reset to the first frame.
|
||||||
|
void Rewind()
|
||||||
|
{
|
||||||
|
fTime = 0;
|
||||||
|
iCurrentFrame = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Advance the animation by fSeconds.
|
||||||
|
void Update()
|
||||||
|
{
|
||||||
|
// fSeconds is the time since the last update:
|
||||||
|
double fNow = SMX::GetMonotonicTime();
|
||||||
|
double fSeconds = m_fLastUpdateTime == -1? 0: (fNow - m_fLastUpdateTime);
|
||||||
|
m_fLastUpdateTime = fNow;
|
||||||
|
|
||||||
|
if(!bPlaying || animation.m_aPanelGraphics.empty())
|
||||||
|
return;
|
||||||
|
|
||||||
|
// If the current frame is past the end, a new animation was probably
|
||||||
|
// loaded.
|
||||||
|
if(iCurrentFrame >= animation.m_aPanelGraphics.size())
|
||||||
|
Rewind();
|
||||||
|
|
||||||
|
// Advance time.
|
||||||
|
fTime += fSeconds;
|
||||||
|
|
||||||
|
// If we're still on this frame, we're done.
|
||||||
|
float fFrameDuration = animation.m_iFrameDurations[iCurrentFrame];
|
||||||
|
if(fTime - 0.00001f < fFrameDuration)
|
||||||
|
return;
|
||||||
|
|
||||||
|
// If we've passed the end of the frame, move to the next frame. Don't
|
||||||
|
// skip frames if we're updating too quickly.
|
||||||
|
fTime -= fFrameDuration;
|
||||||
|
if(fTime > 0)
|
||||||
|
fTime = 0;
|
||||||
|
|
||||||
|
// Advance the frame.
|
||||||
|
iCurrentFrame++;
|
||||||
|
|
||||||
|
// If we're at the end of the frame, rewind to the loop frame.
|
||||||
|
if(iCurrentFrame == animation.m_aPanelGraphics.size())
|
||||||
|
iCurrentFrame = animation.m_iLoopFrame;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
struct AnimationStateForPad
|
||||||
|
{
|
||||||
|
// asLightsData is an array of lights data to send to the pad and graphic
|
||||||
|
// is an animation graphic. Overlay graphic on top of the lights.
|
||||||
|
void OverlayLights(char *asLightsData, const vector<SMXGif::Color> &graphic) const
|
||||||
|
{
|
||||||
|
// Stop if this graphic isn't loaded or is paused.
|
||||||
|
if(graphic.empty())
|
||||||
|
return;
|
||||||
|
|
||||||
|
for(int i = 0; i < graphic.size(); ++i)
|
||||||
|
{
|
||||||
|
if(i >= LIGHTS_PER_PANEL)
|
||||||
|
return;
|
||||||
|
|
||||||
|
// If this color is transparent, leave the released animation alone.
|
||||||
|
if(graphic[i].color[3] == 0)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
asLightsData[i*3+0] = graphic[i].color[0];
|
||||||
|
asLightsData[i*3+1] = graphic[i].color[1];
|
||||||
|
asLightsData[i*3+2] = graphic[i].color[2];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Return the command to set the current animation state as pad lights.
|
||||||
|
string GetLightsCommand(int iPadState, const SMXConfig &config) const
|
||||||
|
{
|
||||||
|
g_Lock.AssertLockedByCurrentThread();
|
||||||
|
|
||||||
|
// If AutoLightingUsePressedAnimations is set, use lights animations.
|
||||||
|
// If it's not (the config tool is set to step color), mimic the built-in
|
||||||
|
// step color behavior instead of using pressed animations. Any released
|
||||||
|
// animation will always be used.
|
||||||
|
bool bUsePressedAnimations = config.flags & PlatformFlags_AutoLightingUsePressedAnimations;
|
||||||
|
|
||||||
|
const int iBytesPerPanel = LIGHTS_PER_PANEL*3;
|
||||||
|
const int iTotalLights = 9*iBytesPerPanel;
|
||||||
|
string result(iTotalLights, 0);
|
||||||
|
|
||||||
|
for(int panel = 0; panel < 9; ++panel)
|
||||||
|
{
|
||||||
|
// The portion of lights data for this panel:
|
||||||
|
char *out = &result[panel*iBytesPerPanel];
|
||||||
|
|
||||||
|
// Add the released animation, then overlay the pressed animation if we're pressed.
|
||||||
|
OverlayLights(out, animations[panel][SMX_LightsType_Released].GetAnimationFrame());
|
||||||
|
bool bPressed = bool(iPadState & (1 << panel));
|
||||||
|
if(bPressed && bUsePressedAnimations)
|
||||||
|
OverlayLights(out, animations[panel][SMX_LightsType_Pressed].GetAnimationFrame());
|
||||||
|
else if(bPressed && !bUsePressedAnimations)
|
||||||
|
{
|
||||||
|
// Light all LEDs on this panel using stepColor.
|
||||||
|
double LightsScaleFactor = 0.666666f;
|
||||||
|
const uint8_t *color = &config.stepColor[panel*3];
|
||||||
|
|
||||||
|
for(int light = 0; light < LIGHTS_PER_PANEL; ++light)
|
||||||
|
{
|
||||||
|
for(int i = 0; i < 3; ++i)
|
||||||
|
{
|
||||||
|
// stepColor is scaled to the 0-170 range. Scale it back to the 0-255 range.
|
||||||
|
// User applications don't need to worry about this since they normally don't
|
||||||
|
// need to care about stepColor.
|
||||||
|
uint8_t c = color[i];
|
||||||
|
c = (uint8_t) lrintf(min(255.0f, c / LightsScaleFactor));
|
||||||
|
out[light*3+i] = c;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
// State for both animations on each panel:
|
||||||
|
AnimationState animations[9][NUM_SMX_LightsType];
|
||||||
|
};
|
||||||
|
|
||||||
|
namespace
|
||||||
|
{
|
||||||
|
// Animations and animation states for both pads.
|
||||||
|
AnimationStateForPad pad_states[2];
|
||||||
|
}
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
// Given a 23x24 graphic frame and a panel number, return an array of 25 colors, containing
|
||||||
|
// each light in the order it's sent to the master controller.
|
||||||
|
void ConvertToPanelGraphic(const SMXGif::GIFImage &src, vector<SMXGif::Color> &dst, int panel)
|
||||||
|
{
|
||||||
|
vector<pair<int,int>> graphic_positions = {
|
||||||
|
{ 0,0 },
|
||||||
|
{ 1,0 },
|
||||||
|
{ 2,0 },
|
||||||
|
{ 0,1 },
|
||||||
|
{ 1,1 },
|
||||||
|
{ 2,1 },
|
||||||
|
{ 0,2 },
|
||||||
|
{ 1,2 },
|
||||||
|
{ 2,2 },
|
||||||
|
};
|
||||||
|
|
||||||
|
dst.clear();
|
||||||
|
|
||||||
|
// The top-left corner for this panel:
|
||||||
|
int x = graphic_positions[panel].first * 8;
|
||||||
|
int y = graphic_positions[panel].second * 8;
|
||||||
|
|
||||||
|
// Add the 4x4 grid first.
|
||||||
|
for(int dy = 0; dy < 4; ++dy)
|
||||||
|
for(int dx = 0; dx < 4; ++dx)
|
||||||
|
dst.push_back(src.get(x+dx*2, y+dy*2));
|
||||||
|
|
||||||
|
// Add the 3x3 grid.
|
||||||
|
for(int dy = 0; dy < 3; ++dy)
|
||||||
|
for(int dx = 0; dx < 3; ++dx)
|
||||||
|
dst.push_back(src.get(x+dx*2+1, y+dy*2+1));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Return the SMXPanelAnimation. The rest of the animation state is internal.
|
||||||
|
SMXPanelAnimation SMXPanelAnimation::GetLoadedAnimation(int pad, int panel, SMX_LightsType type)
|
||||||
|
{
|
||||||
|
g_Lock.AssertNotLockedByCurrentThread();
|
||||||
|
LockMutex L(g_Lock);
|
||||||
|
return pad_states[pad].animations[panel][type].animation;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Load an array of animation frames as a panel animation. Each frame must
|
||||||
|
// be 14x15.
|
||||||
|
void SMXPanelAnimation::Load(const vector<SMXGif::SMXGifFrame> &frames, int panel)
|
||||||
|
{
|
||||||
|
m_aPanelGraphics.clear();
|
||||||
|
m_iFrameDurations.clear();
|
||||||
|
m_iLoopFrame = -1;
|
||||||
|
|
||||||
|
for(int frame_no = 0; frame_no < frames.size(); ++frame_no)
|
||||||
|
{
|
||||||
|
const SMXGif::SMXGifFrame &gif_frame = frames[frame_no];
|
||||||
|
|
||||||
|
// If the bottom-left pixel is opaque, this is the loop frame, which marks the
|
||||||
|
// frame the animation should start at after a loop. This is global to the
|
||||||
|
// animation, not specific to each panel.
|
||||||
|
if(gif_frame.frame.get(0, gif_frame.frame.height-1).color[3] != 0)
|
||||||
|
{
|
||||||
|
// We shouldn't see more than one of these. If we do, use the first.
|
||||||
|
if(m_iLoopFrame != -1)
|
||||||
|
m_iLoopFrame = frame_no;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Extract this frame.
|
||||||
|
vector<SMXGif::Color> panel_graphic;
|
||||||
|
ConvertToPanelGraphic(gif_frame.frame, panel_graphic, panel);
|
||||||
|
m_aPanelGraphics.push_back(panel_graphic);
|
||||||
|
|
||||||
|
// GIFs have a very low-resolution duration field, with 10ms units.
|
||||||
|
// The panels run at 30 FPS internally, or 33 1/3 ms, but GIF can only
|
||||||
|
// represent 30ms or 40ms. Most applications will probably output 30,
|
||||||
|
// but snap both 30ms and 40ms to exactly 30 FPS to make sure animations
|
||||||
|
// that are meant to run at native framerate do.
|
||||||
|
float seconds;
|
||||||
|
if(gif_frame.milliseconds == 30 || gif_frame.milliseconds == 40)
|
||||||
|
seconds = 1 / 30.0f;
|
||||||
|
else
|
||||||
|
seconds = gif_frame.milliseconds / 1000.0;
|
||||||
|
|
||||||
|
m_iFrameDurations.push_back(seconds);
|
||||||
|
}
|
||||||
|
|
||||||
|
// By default, loop back to the first frame.
|
||||||
|
if(m_iLoopFrame == -1)
|
||||||
|
m_iLoopFrame = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Load a GIF into SMXLoadedPanelAnimations::animations.
|
||||||
|
bool SMX_LightsAnimation_Load(const char *gif, int size, int pad, SMX_LightsType type, const char **error)
|
||||||
|
{
|
||||||
|
// Parse the GIF.
|
||||||
|
string buf(gif, size);
|
||||||
|
vector<SMXGif::SMXGifFrame> frames;
|
||||||
|
if(!SMXGif::DecodeGIF(buf, frames) || frames.empty())
|
||||||
|
{
|
||||||
|
*error = "The GIF couldn't be read.";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check the dimensions of the image. We only need to check the first, the
|
||||||
|
// others will always have the same size.
|
||||||
|
if(frames[0].width != 14 || frames[0].height != 15)
|
||||||
|
{
|
||||||
|
*error = "The GIF must be 14x15.";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Lock while we access pad_states.
|
||||||
|
g_Lock.AssertNotLockedByCurrentThread();
|
||||||
|
LockMutex L(g_Lock);
|
||||||
|
|
||||||
|
// Load the animation for each panel into SMXPanelAnimations.
|
||||||
|
for(int panel = 0; panel < 9; ++panel)
|
||||||
|
{
|
||||||
|
SMXPanelAnimation &animation = pad_states[pad].animations[panel][type].animation;
|
||||||
|
animation.Load(frames, panel);
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// A thread to handle setting light animations. We do this in a separate
|
||||||
|
// thread rather than in the SMXManager thread so this can be treated as
|
||||||
|
// if it's external application thread, and it's making normal threaded
|
||||||
|
// calls to SetLights.
|
||||||
|
class PanelAnimationThread: public SMXThread
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
static shared_ptr<PanelAnimationThread> g_pSingleton;
|
||||||
|
PanelAnimationThread():
|
||||||
|
SMXThread(g_Lock)
|
||||||
|
{
|
||||||
|
Start("SMX light animations");
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
void ThreadMain()
|
||||||
|
{
|
||||||
|
m_Lock.Lock();
|
||||||
|
|
||||||
|
// Update lights at 30 FPS.
|
||||||
|
const int iDelayMS = 33;
|
||||||
|
|
||||||
|
while(!m_bShutdown)
|
||||||
|
{
|
||||||
|
// Run a single panel lights update.
|
||||||
|
UpdateLights();
|
||||||
|
|
||||||
|
// Wait up to 30 FPS, or until we're signalled. We can only be signalled
|
||||||
|
// if we're shutting down, so we don't need to worry about partial frame
|
||||||
|
// delays.
|
||||||
|
m_Event.Wait(iDelayMS);
|
||||||
|
}
|
||||||
|
|
||||||
|
m_Lock.Unlock();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Return lights for the given pad and pad state, using the loaded panel animations.
|
||||||
|
void GetCurrentLights(string &asLightsDataOut, int pad, int iPadState)
|
||||||
|
{
|
||||||
|
m_Lock.AssertLockedByCurrentThread();
|
||||||
|
|
||||||
|
// Get this pad's configuration.
|
||||||
|
SMXConfig config;
|
||||||
|
if(!SMXManager::g_pSMX->GetDevice(pad)->GetConfig(config))
|
||||||
|
return;
|
||||||
|
|
||||||
|
AnimationStateForPad &pad_state = pad_states[pad];
|
||||||
|
|
||||||
|
// Make sure the correct animations are playing.
|
||||||
|
for(int panel = 0; panel < 9; ++panel)
|
||||||
|
{
|
||||||
|
// The released animation is always playing.
|
||||||
|
pad_state.animations[panel][SMX_LightsType_Released].Play();
|
||||||
|
|
||||||
|
// The pressed animation only plays while the button is pressed,
|
||||||
|
// and rewind when it's released.
|
||||||
|
bool bPressed = iPadState & (1 << panel);
|
||||||
|
if(bPressed)
|
||||||
|
pad_state.animations[panel][SMX_LightsType_Pressed].Play();
|
||||||
|
else
|
||||||
|
pad_state.animations[panel][SMX_LightsType_Pressed].Stop();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set the current state.
|
||||||
|
asLightsDataOut = pad_state.GetLightsCommand(iPadState, config);
|
||||||
|
|
||||||
|
// Advance animations.
|
||||||
|
for(int panel = 0; panel < 9; ++panel)
|
||||||
|
{
|
||||||
|
for(auto &animation_state: pad_state.animations[panel])
|
||||||
|
animation_state.Update();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Run a single light animation update.
|
||||||
|
void UpdateLights()
|
||||||
|
{
|
||||||
|
string asLightsData[2];
|
||||||
|
for(int pad = 0; pad < 2; pad++)
|
||||||
|
{
|
||||||
|
int iPadState = SMXManager::g_pSMX->GetDevice(pad)->GetInputState();
|
||||||
|
GetCurrentLights(asLightsData[pad], pad, iPadState);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update lights.
|
||||||
|
SMXManager::g_pSMX->SetLights(asLightsData);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
void SMX_LightsAnimation_SetAuto(bool enable)
|
||||||
|
{
|
||||||
|
if(!enable)
|
||||||
|
{
|
||||||
|
// If we're turning off, shut down the thread if it's running.
|
||||||
|
if(PanelAnimationThread::g_pSingleton)
|
||||||
|
PanelAnimationThread::g_pSingleton->Shutdown();
|
||||||
|
PanelAnimationThread::g_pSingleton.reset();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create the animation thread if it's not already running.
|
||||||
|
if(PanelAnimationThread::g_pSingleton)
|
||||||
|
return;
|
||||||
|
PanelAnimationThread::g_pSingleton.reset(new PanelAnimationThread());
|
||||||
|
}
|
||||||
|
|
||||||
|
shared_ptr<PanelAnimationThread> PanelAnimationThread::g_pSingleton;
|
50
sdk/Windows/SMXPanelAnimation.h
Normal file
50
sdk/Windows/SMXPanelAnimation.h
Normal file
@ -0,0 +1,50 @@
|
|||||||
|
#ifndef SMXPanelAnimation_h
|
||||||
|
#define SMXPanelAnimation_h
|
||||||
|
|
||||||
|
#include <vector>
|
||||||
|
#include "SMXGif.h"
|
||||||
|
|
||||||
|
enum SMX_LightsType
|
||||||
|
{
|
||||||
|
SMX_LightsType_Released, // animation while panels are released
|
||||||
|
SMX_LightsType_Pressed, // animation while panel is pressed
|
||||||
|
NUM_SMX_LightsType,
|
||||||
|
};
|
||||||
|
|
||||||
|
// SMXPanelAnimation holds an animation, with graphics for a single panel.
|
||||||
|
class SMXPanelAnimation
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
// Return the animation loaded by SMX_LightsAnimation_Load.
|
||||||
|
static SMXPanelAnimation GetLoadedAnimation(int pad, int panel, SMX_LightsType type);
|
||||||
|
|
||||||
|
void Load(const std::vector<SMXGif::SMXGifFrame> &frames, int panel);
|
||||||
|
|
||||||
|
// The high-level animated GIF frames:
|
||||||
|
std::vector<std::vector<SMXGif::Color>> m_aPanelGraphics;
|
||||||
|
|
||||||
|
// The animation starts on frame 0. When it reaches the end, it loops
|
||||||
|
// back to this frame.
|
||||||
|
int m_iLoopFrame = 0;
|
||||||
|
|
||||||
|
// The duration of each frame in seconds.
|
||||||
|
std::vector<float> m_iFrameDurations;
|
||||||
|
};
|
||||||
|
|
||||||
|
// For SMX_API:
|
||||||
|
#include "../SMX.h"
|
||||||
|
|
||||||
|
// High-level interface for C# bindings:
|
||||||
|
//
|
||||||
|
// Load an animated GIF as a panel animation. pad is the pad this animation is for (0 or 1),
|
||||||
|
// and type is which animation this is for. Any previously loaded animation will be replaced.
|
||||||
|
// On error, false is returned and error is set to a plain-text error message which is valid
|
||||||
|
// until the next call.
|
||||||
|
SMX_API bool SMX_LightsAnimation_Load(const char *gif, int size, int pad, SMX_LightsType type, const char **error);
|
||||||
|
|
||||||
|
// Enable or disable automatically handling lights animations. If enabled, any animations
|
||||||
|
// loaded with SMX_LightsAnimation_Load will run automatically as long as the SDK is loaded.
|
||||||
|
// XXX: should we automatically disable SMX_SetLights when this is enabled?
|
||||||
|
SMX_API void SMX_LightsAnimation_SetAuto(bool enable);
|
||||||
|
|
||||||
|
#endif
|
@ -21,6 +21,11 @@ namespace SMX
|
|||||||
private Byte dummy;
|
private Byte dummy;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Bits for SMXConfig::flags.
|
||||||
|
public enum SMXConfigFlags {
|
||||||
|
SMXConfigFlags_AutoLightingUsePressedAnimations = 1 << 0,
|
||||||
|
};
|
||||||
|
|
||||||
[StructLayout(LayoutKind.Sequential, Pack=1)]
|
[StructLayout(LayoutKind.Sequential, Pack=1)]
|
||||||
public struct SMXConfig {
|
public struct SMXConfig {
|
||||||
public Byte unused1, unused2;
|
public Byte unused1, unused2;
|
||||||
@ -60,8 +65,29 @@ namespace SMX
|
|||||||
public Byte panelThreshold6Low, panelThreshold6High;
|
public Byte panelThreshold6Low, panelThreshold6High;
|
||||||
public Byte panelThreshold8Low, panelThreshold8High;
|
public Byte panelThreshold8Low, panelThreshold8High;
|
||||||
|
|
||||||
|
// Master delay debouncing (version >= 3). If enabled, this will add a
|
||||||
|
// corresponding delay to inputs, which the game needs to compensate for.
|
||||||
|
// This is disabled by default.
|
||||||
|
public UInt16 debounceDelayMs;
|
||||||
|
|
||||||
|
// Packed flags (SMXConfigFlags).
|
||||||
|
public Byte flags;
|
||||||
|
|
||||||
|
// It would be simpler to set flags to [MarshalAs(UnmanagedType.U8)], but
|
||||||
|
// that doesn't work.
|
||||||
|
public SMXConfigFlags configFlags {
|
||||||
|
get {
|
||||||
|
return (SMXConfigFlags) flags;
|
||||||
|
}
|
||||||
|
|
||||||
|
set {
|
||||||
|
flags = (Byte) value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Pad this struct to exactly 250 bytes.
|
// Pad this struct to exactly 250 bytes.
|
||||||
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 166)]
|
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 166)]
|
||||||
|
|
||||||
public Byte[] padding;
|
public Byte[] padding;
|
||||||
|
|
||||||
// enabledSensors is a mask of which panels are enabled. Return this as an array
|
// enabledSensors is a mask of which panels are enabled. Return this as an array
|
||||||
@ -248,11 +274,15 @@ namespace SMX
|
|||||||
private static extern bool SMX_GetTestData(int pad, out SMXSensorTestModeData data);
|
private static extern bool SMX_GetTestData(int pad, out SMXSensorTestModeData data);
|
||||||
[DllImport("SMX.dll", CallingConvention = CallingConvention.Cdecl)]
|
[DllImport("SMX.dll", CallingConvention = CallingConvention.Cdecl)]
|
||||||
private static extern bool SMX_SetLights(byte[] buf);
|
private static extern bool SMX_SetLights(byte[] buf);
|
||||||
[DllImport("SMX.dll", CallingConvention = CallingConvention.Cdecl)]
|
|
||||||
private static extern bool SMX_ReenableAutoLights();
|
|
||||||
[DllImport("SMX.dll", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)]
|
[DllImport("SMX.dll", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)]
|
||||||
private static extern IntPtr SMX_Version();
|
private static extern IntPtr SMX_Version();
|
||||||
|
|
||||||
|
public enum LightsType
|
||||||
|
{
|
||||||
|
LightsType_Released, // animation while panels are released
|
||||||
|
LightsType_Pressed, // animation while panel is pressed
|
||||||
|
};
|
||||||
|
|
||||||
public static string Version()
|
public static string Version()
|
||||||
{
|
{
|
||||||
if(!DLLAvailable()) return "";
|
if(!DLLAvailable()) return "";
|
||||||
@ -420,10 +450,38 @@ namespace SMX
|
|||||||
SMX_SetLights(buf);
|
SMX_SetLights(buf);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void ReenableAutoLights()
|
// SMXPanelAnimation
|
||||||
|
[DllImport("SMX.dll", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)]
|
||||||
|
[return:MarshalAs(UnmanagedType.I1)]
|
||||||
|
private static extern bool SMX_LightsAnimation_Load(byte[] buf, int size, int pad, int type, out IntPtr error);
|
||||||
|
[DllImport("SMX.dll", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)]
|
||||||
|
private static extern void SMX_LightsAnimation_SetAuto(bool enable);
|
||||||
|
|
||||||
|
public static bool LightsAnimation_Load(byte[] buf, int pad, LightsType type, out string error)
|
||||||
|
{
|
||||||
|
if(!DLLAvailable())
|
||||||
|
{
|
||||||
|
error = "SMX.DLL not available";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
error = "";
|
||||||
|
IntPtr error_pointer;
|
||||||
|
bool result = SMX_LightsAnimation_Load(buf, buf.Length, pad, (int) type, out error_pointer);
|
||||||
|
if(!result)
|
||||||
|
{
|
||||||
|
// SMX_LightsAnimation_Load takes a char **error, which is set to the error
|
||||||
|
// string.
|
||||||
|
error = Marshal.PtrToStringAnsi(error_pointer);
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void LightsAnimation_SetAuto(bool enable)
|
||||||
{
|
{
|
||||||
if(!DLLAvailable()) return;
|
if(!DLLAvailable()) return;
|
||||||
SMX_ReenableAutoLights();
|
SMX_LightsAnimation_SetAuto(enable);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user