Skip to main content

Overview

The Spiral serializer arranges DMX channels in a spiral pattern starting from the origin (0,0) and moving outward. This creates a distinctive visual pattern useful for debugging video transmission, testing capture systems, and creating visual effects.

Class: Spiral

Namespace: Global
Implements: IDMXSerializer

Configuration Constants

blockSize
const int
default:"8"
Size in pixels of each channel block (8×8 pixels). Larger than other serializers for better visibility.

Instance Variables

x
int
Current X position in the spiral (in block coordinates, not pixels)
y
int
Current Y position in the spiral (in block coordinates, not pixels)
state
int
Current direction state:
  • 0 = Moving right (+X)
  • 1 = Moving up (+Y)
  • 2 = Moving left (-X)
  • 3 = Moving down (-Y)
visited
List<Vector2Int>
Tracks all visited positions to prevent overlap and trigger direction changes

How It Works

Spiral Algorithm

The spiral follows a clockwise pattern:
    ↓ Start (0,0)
    ┌─→─→─→─┐
    ↑       ↓
    ↑   ┌─→─┘
    ↑   ↑   
    └─←─┘   
Movement pattern:
  1. Start at (0,0) moving right
  2. Move right until hitting a wall or visited cell → turn up
  3. Move up until collision → turn left
  4. Move left until collision → turn down
  5. Move down until collision → turn right
  6. Repeat, spiraling inward

Direction States

StateDirectionDelta
0Right(+1, 0)
1Up(0, +1)
2Left(-1, 0)
3Down(0, -1)

Collision Detection

The spiral changes direction when:
  • Next position has been visited before (self-collision)
  • Next position is out of bounds (< 0 or >= texture size)

Methods

InitFrame

Resets the spiral state at the start of each frame.
public void InitFrame(ref List<byte> channelValues)
channelValues
ref List<byte>
required
The list of channel values (not used by this method)
Actions:
  • Resets position to (0, 0)
  • Clears the visited positions list
  • Resets direction state to 0 (right)

SerializeChannel

Encodes a DMX channel at the current spiral position and advances to the next position.
public void SerializeChannel(ref Color32[] pixels, byte channelValue, 
    int channel, int textureWidth, int textureHeight)
pixels
ref Color32[]
required
The pixel array to write to
channelValue
byte
required
The DMX channel value (0-255) to encode as grayscale
channel
int
required
The DMX channel number (not directly used - position determined by spiral)
textureWidth
int
required
Width of the output texture in pixels
textureHeight
int
required
Height of the output texture in pixels
Process:
  1. Calculates texture dimensions in block coordinates
  2. Converts current block position (x, y) to pixel position
  3. Writes an 8×8 grayscale block at the current position
  4. Calculates the next position based on current direction
  5. Checks for collisions (visited or out-of-bounds)
  6. If collision detected, rotates direction clockwise
  7. Adds current position to visited list
  8. Updates position to next position

CalculateNextMove

Calculates the next position based on the current direction state.
private void CalculateNextMove(ref int nextX, ref int nextY)
Movement logic:
switch (state)
{
    case 0: nextX = x + 1; break; // Right
    case 1: nextY = y + 1; break; // Up
    case 2: nextX = x - 1; break; // Left
    case 3: nextY = y - 1; break; // Down
}

DeserializeChannel

Decoding is not implemented.
public void DeserializeChannel(Texture2D tex, ref byte channelValue, 
    int channel, int textureWidth, int textureHeight)
Not Implemented: This method throws NotImplementedException. Spiral is currently encode-only.

Usage Example

// Create spiral serializer
var spiral = new Spiral();

// Initialize frame
var channelValues = new List<byte>();
for (int i = 0; i < 64; i++)
    channelValues.Add((byte)(i * 4)); // Gradient pattern

spiral.InitFrame(ref channelValues);

// Serialize all channels
int textureWidth = 256;
int textureHeight = 256;
Color32[] pixels = new Color32[textureWidth * textureHeight];

for (int ch = 0; ch < channelValues.Count; ch++)
{
    spiral.SerializeChannel(ref pixels, channelValues[ch], ch, 
        textureWidth, textureHeight);
}

// Result: Channels spiral outward from top-left corner
// Channel 0 at (0,0), Channel 1 at (8,0), Channel 2 at (16,0)...
// Eventually wrapping around in a spiral pattern

Visual Pattern Example

For a 64×64 pixel texture (8×8 blocks) with 8 channels:
┌───┬───┬───┬───┐
│ 0 │ 1 │ 2 │ 3 │  Channel 0→1→2→3 (right)
├───┼───┼───┼───┤
│ 7 │   │   │ 4 │  Channel 7 (left) and 4 (down)
├───┼───┼───┼───┤
│ 6 │   │   │ 5 │  Channel 6 (left) and 5 (down)
└───┴───┴───┴───┘
    Channel ordering forms clockwise spiral

Channel Capacity

Capacity depends on texture size and block size:
int blocksWide = textureWidth / blockSize;
int blocksHigh = textureHeight / blockSize;
int totalChannels = blocksWide * blocksHigh;

// Example: 256×256 texture, 8×8 blocks
// Blocks: (256/8) × (256/8) = 32 × 32 = 1,024 channels

Capacity by Resolution

ResolutionBlock GridChannels
128×12816×16256
256×25632×321,024
512×51264×644,096
1024×1024128×12816,384
1920×1080240×13532,400

Advantages

Visual Tracking

Easy to track data flow and identify transmission issues

Debugging

Pattern makes it obvious if frames are dropped or corrupted

Large Blocks

8×8 blocks are highly visible and compression-resistant

Deterministic

Same spiral path every frame ensures consistency

Limitations

Encode-Only: Deserialization is not implemented. Spiral is primarily for visualization and debugging.
  • Position-Dependent: Channel values are determined by spiral position, not channel number
  • Sequential Only: Must encode all channels in order; cannot encode individual channels
  • No Random Access: Cannot directly encode channel N without encoding 0 through N-1
  • Frame State: Requires InitFrame to be called before each frame

Use Cases

1. Visual Debugging

Verify video capture is working correctly:
// Send gradient pattern
for (int i = 0; i < 100; i++)
{
    spiral.SerializeChannel(ref pixels, (byte)i, i, width, height);
}
// Visual result: Gradient spiraling from dark (center) to bright (outside)

2. Transmission Testing

Test video quality by analyzing spiral continuity:
// Send incrementing values
for (int i = 0; i < 256; i++)
{
    spiral.SerializeChannel(ref pixels, (byte)i, i, width, height);
}
// Breaks in the spiral indicate frame drops or corruption

3. Visual Effects

Create animated spiral patterns:
// Animated gradient
for (int i = 0; i < numChannels; i++)
{
    byte value = (byte)((i + frameCount) % 256);
    spiral.SerializeChannel(ref pixels, value, i, width, height);
}
// Result: Gradient appears to rotate outward

Best Practices

1. Always Call InitFrame

Failing to call InitFrame will cause the spiral to continue from the previous frame’s position.
// Correct usage
void RenderFrame()
{
    spiral.InitFrame(ref channelValues);
    
    foreach (var channel in channelValues)
    {
        spiral.SerializeChannel(ref pixels, channel.value, 
            channel.id, width, height);
    }
}

2. Sequential Encoding

Encode channels in sequential order:
// Correct: Sequential
for (int i = 0; i < channels.Count; i++)
    spiral.SerializeChannel(...);

// Incorrect: Random order will break spiral pattern
foreach (var ch in channels.OrderByDescending(c => c.priority))
    spiral.SerializeChannel(...); // Wrong!

3. Verify Capacity

Ensure channel count fits in texture:
int maxChannels = (textureWidth / blockSize) * (textureHeight / blockSize);

if (channelValues.Count > maxChannels)
{
    Debug.LogError($"Too many channels! Max: {maxChannels}, Got: {channelValues.Count}");
    return;
}

Comparison with Other Serializers

FeatureSpiralBinaryVRSL
Block Size8×84×416×16
PatternSpiralGridGrid
EncodingGrayscaleBinaryGrayscale
Channels (256×256)1,024416169
VisualHighLowMedium
DecodingNoYesYes
Use CaseDebugDataProduction

Performance

  • Encoding: ~0.015ms per channel
  • Collision Detection: O(n) where n = visited positions
  • Memory: O(n) for visited list (cleared each frame)
  • InitFrame: ~0.001ms
The visited list grows with each channel, making later channels slightly slower to encode. For typical frame sizes (< 1000 channels), this is negligible.

Advanced Usage

Custom Spiral Patterns

Modify the direction sequence for different patterns:
// Counter-clockwise spiral
private void CalculateNextMove(ref int nextX, ref int nextY)
{
    nextX = x;
    nextY = y;
    switch (state)
    {
        case 0: nextX = x + 1; break; // Right
        case 1: nextY = y - 1; break; // Down (reversed)
        case 2: nextX = x - 1; break; // Left
        case 3: nextY = y + 1; break; // Up (reversed)
    }
}

Spiral from Center

Start from texture center instead of corner:
public void InitFrame(ref List<byte> channelValues)
{
    x = (textureWidth / blockSize) / 2;
    y = (textureHeight / blockSize) / 2;
    visited.Clear();
    state = 0;
}

Troubleshooting

The spiral may terminate early if it gets trapped. This happens when:
  • All adjacent cells are visited or out of bounds
  • Texture dimensions aren’t evenly divisible by block size
Solution: Ensure texture size is a multiple of 8 pixels.
This occurs if InitFrame is not called before encoding a frame.Solution: Always call InitFrame at the start of each frame.
Verify channels are being encoded in sequential order.Solution: Encode channels 0, 1, 2, 3… in order.

When to Use

Good for:
  • Debugging video transmission systems
  • Visual monitoring of data flow
  • Testing video capture quality
  • Creating animated spiral effects
  • Demonstrations and presentations
Not suitable for:
  • Production DMX systems (no decoder)
  • Applications requiring random channel access
  • Systems needing bidirectional communication
  • Maximum channel density requirements

Build docs developers (and LLMs) love