Archive

Monthly Archives: December 2013

It's that time again!  Pick out the things you don't like about yourself and change them!

  • Spend 24 minutes per day, 3 times per week drawing
  • Spend 24 minutes per day, 3 times per week writing
  • Spend 24 minutes per day, 3 times per week reading
  • Spend 24 minutes per day, 3 times per week composing
  • Learn the first 500 Chinese Pinyin characters
  • Finish two games

A friend of mine was playing with some shadow casting. He requested help with a bit of the math to cast a line from the center of a light to a point to the edge.

In short, here's a picture:

Shadow cast from a point light to a point.

Shadow cast from a point light to a point.

There's no need to use trigonometry to solve this problem. All we need to do is solve some simple vector math problems. I'll try and run through step by step, then give the code at the end. For those with programming inclination, when I say, foo.xy /= 3, that means foo.x /= 3 and foo.y /= 3. When I say foo.xy + bar.xy, it means foo.x + bar.x and foo.y + bar.y.

Let's begin.

The distance between the light and the point forms a line. The line can be represented as a vector. We can take the vector, found by point.xy - light.xy, and stretch it from the point to the edge of the light.

point.xy - light.xy translates the point into a zero-centered coordinate system. That is, instead of having a light at <123, 456> and a point at <124, 457>, we're assuming the light is at zero and moving the light appropriately. That is, the light would be at <0,0> and the point at <1,1>. It means we can use a few neat math tricks like 'normalization'.

So why not just take that vector (found with e.xy - l.xy) and apply it again? "Because it doesn't reach the edge then! Or it stretches past!"

shadow_unnormalized

Ah! So we normalize it so that the slope is the same (e.xy - l.xy)/magnitude(e.xy-l.xy) and then multiply by the magnitude of the distance between the edge of the circle and the point. c.xy = magnitude(l.radius - magnitude(e.xy-l.xy))*(e.xy-l.xy)/magnitude(e.xy-l.xy);

"Whoa. Wait. How's that again?"

Intuitively, imagine a line from the center of the light to a point that's exactly one unit away. Assume the center is at (0,0).

L.........P
0.........1.........2.........3

If the 'light radius' is 3, then all you have to do is multiply the 'unit length' by three to reach the edge. Since our point is already one unit away from the center, we just have to multiply by (3 - 1) = 2!

L-------->P------------------>|
0.........1.........2.........3

If we treat the distance from the light to the point as 'normalized' (as though it were 1), then we can multiply it by the distance from the point to the edge of the circle and it will fill exactly that space.

That's all there is to it. Apply these steps and you can cast a line from a point to the edge of a circle.

shadow_renormalized

Here's the full source:

import javax.swing.*;
import java.awt.*;

/**
 * Created by jcatrambone on 2013/12/20.
 */
public class MainApp extends JFrame {
	public Vector2f lightCenter;
	public float lightRadius;
	public float lightSize = 3.0f; // This is just for display purposes.  How 'big' the light appears.
	public Vector2f entityCenter;
	public float entitySize = 1.0f;

	public static void main(String[] args) {
		MainApp app = new MainApp();

		app.lightCenter = new Vector2f(200f, 200f);
		app.lightRadius = 200f;
		app.entityCenter = new Vector2f(280f, 280f);

		app.setSize(600, 600);
		app.setVisible(true);
	}

	@Override
	public void paint(Graphics g1d) {
		Graphics2D g = (Graphics2D)g1d;
		g.setColor(Color.black);
		g.fillRect(0, 0, 600, 600);

		// Draw the outside boundary of the light.
		g.setPaint(Color.yellow);
		g.drawOval((int)(lightCenter.x-lightRadius), (int)(lightCenter.y-lightRadius), (int)(lightRadius*2), (int)(lightRadius*2));

		// Draw the center of the light.
		g.setPaint(Color.yellow);
		g.drawOval((int) (lightCenter.x - lightSize), (int) (lightCenter.y - lightSize), (int) (lightSize * 2), (int) (lightSize * 2));

		// Draw the entity edge.
		g.setPaint(Color.green);
		g.drawOval((int)(entityCenter.x - entitySize), (int)(entityCenter.y - entitySize), (int)(entitySize*2), (int)(entitySize*2));

		// Draw the important shadow!
		// Shadow starts at the entity, so we only need to calculate the line from the entity to the edge.
		// Translate the entity into the -1,1 range, centered at zero.
		Vector2f entityLocalSpace = entityCenter.minus(lightCenter);
		// Normalize the edge to preserve the angle.
		Vector2f normalizedEdge = entityLocalSpace.getNormalized();
		// Now stretch this to the edge of the circle.  Since the edge is normalized, it has length 1,
		// so all we have to do is multiply by the length of a line from the entity to the edge of the light.
		Vector2f shadowLine = normalizedEdge.multiply(lightRadius - entityLocalSpace.getMagnitude());
		// c.xy = magnitude(l.xy-e.xy)*(e.xy-l.xy)/magnitude(e.xy-l.xy);
		// Whoops.  That first magnitude should be magnitude(l.radius - magnitude(e.xy-l.xy))
		g.setPaint(Color.green);
		g.drawLine((int)entityCenter.x, (int)entityCenter.y, (int)(entityCenter.x+shadowLine.x), (int)(entityCenter.y+shadowLine.y));
	}
}

class Vector2f {
	public float x, y;

	public Vector2f() {
		x=0; y=0;
	}

	public Vector2f(float x, float y) {
		this.x = x;
		this.y = y;
	}

	public Vector2f getNormalized() {
		float mag = this.getMagnitude();
		return new Vector2f(this.x/mag, this.y/mag);
	}

	public float getMagnitude() {
		return (float)Math.sqrt(x*x + y*y);
	}

	public Vector2f add(Vector2f v) {
		return new Vector2f(this.x + v.x, this.y + v.y);
	}

	public Vector2f multiply(float scalar) {
		return new Vector2f(this.x * scalar, this.y * scalar);
	}

	public Vector2f minus(Vector2f v) {
		// foo.minus(bar) = foo - bar
		return new Vector2f(this.x - v.x, this.y - v.y);
	}
}

I've made JavaRBM available to the public on BitBucket: https://bitbucket.org/JosephCatrambone/javarbm

It's not yet licensed, which means there's an implicit non-commercial personal-educational-use license, but I'll run through the code again adding license info at the top when I have time.

I came across an enticing article written by Ben Nesvig earlier today called "Lessons from Writing 750 Words a Day for 365 Days". The idea behind writing for extended periods over the course of a year is rather inspirational. I started this blog a while back with the idea that I'd draw 100 pictures over the course of a year (not finished yet), with the idea I'd produce a game every few months, and do a few hundred math problems. I like the idea of writing at length on a daily basis because I've recently grown concerned with the style and quality of my writing. Sitting among friends, we'd start to hack together short stories for our own amusement. Much to my horror, after smashing away at my keyboard for ten or twenty minutes, I found a blob of text more akin to a plagiarized Wikipedia article or rejected CVPR entry than an honestly entertaining story. I'm still not decided on whether it's better to have a scientific paper written in a more narrative or technical tone, but I can say for certain that it is socially advantageous to be able to tell a story.

It is towards this end that I'd like to propose something not so far off of the 750w/day setup. I'd like to reap the benefits of improved writing through daily practice, but it is necessary to balance this desire against the plausibility of doing so in tandem with a full time graduate program, a full time job, a social life, exercise, and an ambient desire to write software and video games. I suppose a reasonable compromise might be 500 words on weeknights. Maybe every other night? I guess we'll see.