How would you make a 2D KITT?

Hey guys,

Just received my PB V3 and I’m enjoying trying to learn how to make/tweak patterns, but I have no coding experience and I’m basically banging rocks together until something lights up.
The intro to Pixelblaze tutorial was very helpful but once I try and step into 2D I’m completely lost.
(I’m currently trying to replicate 1D patterns on a 2D matrix to make them “thicker”)

I was wondering if someone could show a caveman such as myself how you might go about adapting the KITT pattern to work on a 2D matrix (more like 1.5D I guess, since each row is just a copy of row 0).
I’m wired zigzag if that matters.

Thanks in advance!

It’s pretty trivial if you just think of it this way:

Instead of index and pixelcount, you need to move along X, and ignore the Y (or if you want it moving up and down, instead of left right, use Y, not X)

Knowing how many pixels you have could help, but really you don’t need to know, to make it matrix agnostic. Just moving between 0 and 1 slowly and lighting up pixels within a range of values. (So if your leading edge is currently at .7, lighting .7 brightly and then dropping brightness downward toward .5 (for example))

If this didn’t help, we can walk through this. It’s been on my TODO list for a while. Converting code to run 2D in the example patterns [ideally just adding a Render2D function] is worthwhile.

Hi @Aarvix - I’m sure we can get you going.

A quick question to get started - were you able to use the mapper tab to load the default matrix map and use the zig-zag line correctly? Are you wired up and working on an 8x8 so far?

Hi guys,

Yes, I have my matrix set up in the mapper tab, the default “matrix” code looks correct once I adjusted the width variable. I just don’t know how to actually USE the X/Y in my render2D function.

my wiring and the animation on the map tab both follow the same path:
---------->
<----------
---------->
etc

My matrix is 28w x 5h, beginning at top left, but I imagine that’s not super important. From what I’ve read here the preference seems to be to write code that’s width/height agnostic if possible.

Awesome! Got it.

KITT uses index, which goes from 0 to pixelCount.

You’ll want to write a render2D which uses x instead. Since our maps always pass in values between 0 and 1 for each dimension like x, you’ll want to convert x into something that goes from 0 to the number of columns you have (28). E.g.,

columns = 28

// inside render2D
col = floor(x * columns)

Notice you’ll also want to use that columns config parameter for the size of the overall array that KITT sets up, and for anywhere you find pixelCount being used in beforeRender() to advance the head, fill, and fade.

Give that a shot, and if you get stuck, post your code here!

1 Like

success!

Now I need to reconcile WHY it works.

(also added sliders and HSV colorpicker because why not.


/*
  Knight Rider: A car named KITT gains sentience and fights critme and all that 
  good stuff.
  
  Derivative of the 1D KITT pattern, but now functions on a matrix with multiple rows.
  
    https://www.youtube.com/watch?v=3ugNIZ96UK4
*/

leader = 0
direction = 1
width = 27
rows = pixelCount / width
pixels = array(width)


var hue = 0, saturation = 0, v = 0
export function hsvPickerColor(_h, _s, _v) {
  hue = _h
  saturation = _s
  v = _v
}

var speed = 0.025
export function slideranimationSpeed(_spd) {
    speed = .02 + pixelCount / 2500 * _spd
}

var fade = .0015
export function slidertailLength(_f) {
  fade = .0005 + .0025 * _f
}

export function beforeRender(delta) {


  lastLeader = floor(leader)
  leader += direction * delta * speed
  
  if (leader >= width) {
    direction = -direction
    leader = width - 1
  }
  
  if (leader < 0) {
    direction = -direction
    leader = 0
  }

  // Fill pixels between frames. Added after the video walkthrough was uploaded.
  up = lastLeader < leader 
  for (i = lastLeader; i != floor(leader); up ? i++ : i-- ) pixels[i] = 1
    
  for (i = 0; i < width; i++) {
    pixels[i] -= delta * fade
    pixels[i] = max(0, pixels[i])
  }
}

export function render2D(index, x, y) {
  col = floor(x * width)
  
  v = pixels[col]
  v = v * v * v
  hsv(hue, saturation, v)
}

Thanks for pointing me to the right rocks to bang together.

ps: had to google how to insert code, might make a good sticky.

2 Likes

I’d prefer a solution without a column variable.

It shouldn’t matter how many columns exist.

If you track a value between 0 and 1, and make the tail point in the correct direction, you don’t need to actually know columns, it’ll work fine if you just check for the head value and then reduce the brightness relative to the head. Make the head bright red, and lower the brightness as you move away from the head.

Making it entirely matrix agnostic is better than still requiring to know how many columns.

@Scruffynerf,
I think that would be a different pattern and/or implementation, at least the way I understand what you describe. The KITT implementation relies on a leader painting into an array, then fading those out over time. In the corners, the leader changes direction and will overdraw the tail, which is still fading out in the direction of the corner. Replicating that without an array would be tricky, especially for certain values of the fade variable. Quantizing it into a number of columns is a good strategy to adapt this implementation without completely reinventing it.

It would be possible to virtualize the KITT array to a size that doesn’t match the matrix width, then pick an index based on the relative X of a given pixel. Much like resizing an image. Either nearest neighbor or some kind of interpolation could work. You could have a smaller source array, but that wouldn’t look as good (just like upscaling an image).

A simple solution would be to keep the pixels array 1:1 with pixelCount, changing only how render2D chooses which element from the pixels array to render. This calculates more elements than necessary, so knowing and using the width as @Aarvix has done allows for more FPS. For example, on a 8x8 matrix, it would calculate a 64 pixel long KITT pattern, then scale it down to 8 pixels wide.

Taking the original KITT code, and only adding this render2D:

export function render2D(index, x, y) {
  v = pixels[floor(x * pixelCount)]
  v = v * v * v
  hsv(0, 1, v)
}

This works as long as x is within the range from 0 to 1, exclusive (which it is when using world units). The expression floor(x * pixelCount) will resolve to a number between 0 and pixelCount-1, which matches the pixels array nicely!

3 Likes

Ok, I’ll take it as a challenge to reimplement it as a purely matrix solution, no columns, entirely within a render2D function if possible. I think it’s doable, without losing the KITT look and feel.

But I agree your solution does work well too.

2 Likes

@Scruffynerf you could also use @zranger1’s trick to infer matrix dimensions to make it config-free!

A while back, I spent a little time thinking about KITT without arrays, only the head index and computed distance. Here’s my current new KITT on the block in 1D. The only thing it doesn’t do (yet) is the brightness blend when switching directions, but I think that could be done by running two simultaneous waves going in opposite directions…

// GLOBAL VARIABLES
export var head = 0;
export var speed = 2.5;
var size = 0.3
export var dir = 1; 

// short functions that calculate the position and speed
// of the bolt, in both directions
var modes = array(2);
modes[0] = forward;
modes[1] = reverse;

// UI sliders
export function sliderSpeed(v) {
  speed = max(0.025,10 * v);
}

export function sliderSize(v) {
  size = max(0.005,(0.5- (v/2)));
}

function forward() {
  head += speed;
  if (head >= (pixelCount-1)) dir = 1;
}

function reverse() {
  head -= speed;
  if (head <= 0) dir = 0;
}

// DRAWING
export function beforeRender(delta) {
  modes[dir]();
}

export function render(index) {
  var m = ((dir) ? (index-head) : (head-index)) / pixelCount;
  m = (m > 0) ? 1-m : 0;
  hsv(0,1, (m > size) * m*m*m*m)
}

Just for reference cause I had to find it myself, Jeff meant this post..

In that case, he’s doing it for esthetics: rounder circles, by ensuring centered pixels.

Still brewing over my own answer to the problem. Maybe tomorrow. I still also need to post a task tomorrow. Maybe they’ll be related,.if my brain comes up with a good newbie task for it.