PDA

View Full Version : Newbie: Ray Tracing problem



zerosum
September 10th, 2008, 11:43 AM
Hi, it's me again.

So, defying my ignorance, I tried to make a ray tracing experiment. And of course, it doesn't work.

I think it's a pretty basic thing with the drawing api, but I have tried many different options, and none work.

Basically, I want my raytracer to be instantiated when the mouse button is pressed, and when released, it should gently disappear. Even though the first part work, the second doesn't, and I am not sure why (I have tried all .graphics.clear() possibilities, I think, but I am fairly new to AS3's magic). I have commented so it helps (and there is stuff I will use later on for collision detection).

Anyway, here's the code (sorry about the mess, it's pretty much a what's in my mind prototype, building on old scraps of code)- any hint as to why it doesn't work would be awesome! Thanks a bunch!


package
{
import flash.display.*;
import flash.events.MouseEvent;
import flash.events.Event;

public class Testing extends Sprite
{
//variables related to the movement of the cone
public var oldX:Number;
public var oldY:Number;
public var vx:Number;
public var vy:Number;
public var dx:Number;
public var dy:Number;
public var speed:Number;
public var angle:Number;

//variables to create the raytracer
public var cone:Sprite; //the sprite that should contain the raytracer
public var lth:Number = 100; //length of the rays
public var dpth:Number = 100; //depth of the rays - will use for collision testing
public var coneAngle:Number = 60; //angle of the tracer
public var coneAngleStep:Number = 20; //times I check for collision
public var rayAngle:Number; //in use later
public var rayAngleRad:Number; //in use later


public function Testing()
{
init();
}

private function init():void
{
addEventListener(Event.ENTER_FRAME, enterF);
}

private function enterF(event:Event):void
{
stage.addEventListener(MouseEvent.MOUSE_DOWN, accion);
}


private function accion(event:MouseEvent):void
{
oldX = mouseX;
oldY = mouseY;
stage.addEventListener(MouseEvent.MOUSE_UP, reaccion);
addEventListener(Event.ENTER_FRAME, annoy);
}

private function reaccion(event:MouseEvent):void
{
removeChild(cone); //once the button is released, the cone should disappear
removeEventListener(Event.ENTER_FRAME, annoy);
}


private function annoy(event:Event):void
{
var vx:Number = oldX - mouseX;
var vy:Number = oldY - mouseY;
var speed = Math.sqrt(dx * dx + dy * dy);//will be used later, with collision detection


var dx:Number = mouseX - oldX;
var dy:Number = mouseY - oldY;
var angle:Number = Math.atan2(dy, dx); //calculate the angle between the mouse and where it moves

var cone:Sprite = new Sprite(); //creating the cone
cone.graphics.lineStyle(1, 0xff0000); //givin' it some style

for (var x:uint = 0; x <= coneAngle; x += coneAngle/coneAngleStep) //basic raytracer
{
cone.graphics.moveTo(oldX, oldY);
var rayAngle:Number = angle/(Math.PI/180) - 90*(coneAngle/2) + x;
var rayAngleRad:Number = rayAngle * Math.PI / 180;
trace(rayAngleRad);
cone.graphics.lineTo(oldX - (lth) * Math.cos(rayAngleRad), oldY - (lth) * Math.sin(rayAngleRad));
}
addChild(cone);

}
}
}

Smee
September 10th, 2008, 03:27 PM
What's happening is that you have your 'cone' variable declared at the top as a Sprite, and your code is written as if the 'cone' is always the same instance of Sprite. But, what you're doing in your 'annoy' method, is creating a new instance of Sprite each time, giving it a graphics style, then adding it to the stage. You're overwriting the last Sprite instance every time, so normally the garbage collector would get rid of it, but because you're adding it to the stage, it has a reference to it and therefore stays.

What you need to do is give reference to one instance of Sprite at the top where to declare your 'cone' variable:


//the sprite that should contain the raytracer
public var cone:Sprite = new Sprite();

Then, in annoy, just change the line where to create a new sprite to:


cone.graphics.clear();

This will just erase all the red lines already drawn, and then draw new ones.

Also, because of this change, you no longer need "addChild(cone)" to run every frame, so you can actually put it in the MOUSE_DOWN event.


One more thing, although it's not a big issue:


var vx:Number = oldX - mouseX;
var vy:Number = oldY - mouseY;
var speed = Math.sqrt(dx * dx + dy * dy);

var dx:Number = mouseX - oldX;
var dy:Number = mouseY - oldY;
var angle:Number = Math.atan2(dy, dx);

Here you have vx, vy, dx, and dy; I'll use the x variables since the y is the same idea:


vx = ax - bx;
dx = bx - ax;

Is the same as

vx = ax - bx;
dx = -ax + bx;

Which is therefore the same as

vx = ax - bx;
dx = -vx;

So, you can remove the dx variables altogether and just use:


var vx:Number = oldX - mouseX;
var vy:Number = oldY - mouseY;
var speed = Math.sqrt(dx * dx + dy * dy);

var angle:Number = Math.atan2(-vy, -vx);

Sorry for the long post.. better to explain thoroughly than not enough I think.

zerosum
September 11th, 2008, 11:41 AM
Thanks a lot for the reply! It seems I have a bit of a problem with instantiating stuff ...
And thanks also for picking up on the last bit of code - the problems of cut and paste :)

Now that I'm at it, I have a second question, for kind readers: Everything in the following code works fine. However, what I'd like to do is: when the mouse is moved in a direction, the tracer cone moves in the same direction, keeping the mouseX, mouseY as center, and casting the rays from there. But it doesn't work: the cone starts on one side of the mouse, and moves from there. I have an intuition of where the problem is (line 79), but I am not sure as to how to do change those values (trig was never my strength).

(oh, and if the explanation doesn't work, try the thing - a swf is better than many words; and there are some hacks there just for testing stuff, disregard anything about the mouse, or the horrible for loop in line 83 :) ).

Thanks again!


package
{
import flash.display.*;
import flash.events.MouseEvent;
import flash.events.Event;
import flash.ui.Mouse;

public class Testing extends Sprite
{
//variables related to the movement of the cone
public var oldX:Number;
public var oldY:Number;
public var vx:Number;
public var vy:Number;
public var speed:Number;
public var angle:Number;

//variables to create the raytracer
public var cone:Sprite = new Sprite(); //the sprite that should contain the raytracer
public var lth:Number = 100; //length of the rays
public var dpth:Number = 100; //depth of the rays - will use for collision testing
public var coneAngle:Number = 80; //angle of the tracer
public var coneAngleStep:Number = 30; //times I check for collision
public var rayAngle:Number; //in use later
public var rayAngleRad:Number; //in use later
public var centerX:Number;
public var centerY:Number;


public function Testing()
{
init();
}

private function init():void
{
addEventListener(Event.ENTER_FRAME, enterF);
}

private function enterF(event:Event):void
{
//Mouse.hide();
stage.addEventListener(MouseEvent.MOUSE_DOWN, accion);
}


private function accion(event:MouseEvent):void
{
addChild(cone);
oldX = mouseX;
oldY = mouseY;
stage.addEventListener(MouseEvent.MOUSE_UP, reaccion);
addEventListener(Event.ENTER_FRAME, annoy);
}

private function reaccion(event:MouseEvent):void
{
removeChild(cone); //once the button is released, the cone should disappear
removeEventListener(Event.ENTER_FRAME, annoy);
}


private function annoy(event:Event):void
{
var vx:Number = oldX - mouseX;
var vy:Number = oldY - mouseY;
var speed = Math.sqrt(vx * vx + vy * vy);//will be used later, with collision detection
var angle:Number = Math.atan2(-vy, -vx); //calculate the angle between the mouse and where it moves

var centerX:Number = oldX - vx;
var centerY:Number = oldY - vy;

cone.graphics.clear(); // updating the graphics
cone.graphics.lineStyle(1, 0xff0000); //givin' it some style
cone.graphics.moveTo(centerX, centerY);
for (var x:uint = 0; x <= coneAngle; x += coneAngle/coneAngleStep) //basic raytracer
{

var rayAngle:Number = angle/(Math.PI/180) - 90*(coneAngle/2) - x;
var rayAngleRad:Number = rayAngle * Math.PI / 180;

for(var y:uint = 0; y <= dpth; y++)
{
y = y
}
cone.graphics.lineTo(centerX + (lth/dpth*y) * Math.cos(rayAngleRad), centerY + (lth/dpth*y) * Math.sin(rayAngleRad));
}
cone.graphics.lineTo(centerX, centerY);
}
}
}

Smee
September 11th, 2008, 03:49 PM
The mouse positions rest on integers, so if you use the old mouse position and the new mouse position to calculate the angle, then when you move the mouse slowly it's going to be very shaky. Are you intending to use the mouse for this in the long run or is it just for testing?

Also, can you explain what you're trying to do with this part:


for(var y:uint = 0; y <= dpth; y++)
{
y = y
}

zerosum
September 11th, 2008, 04:25 PM
Thanks for the reply!
OK, so I want to use this with the mouse all the time - it's the basic interaction for a little game I am building. That's why it's kind of important that it is slightly precise ...

As for the hack - I just wanted to quick and dirty delimit an area, instead of seeing all the rays ... not the most elegant way of going around, I admit :)

Smee
September 11th, 2008, 06:49 PM
Well if it's important to use the direction that the mouse moved in, then you'll need to find a more accurate way of calculating that angle. I can clean your code to do what you described, but it will shake when moving slowly because of the mouse position being integers.

Edit:

Here it is. Notice that if you move very slowly, the angle is always a multiple of 45 because of the integers.


package
{
import flash.display.Sprite;

import flash.events.Event;
import flash.events.MouseEvent;

import flash.geom.Rectangle;

import flash.ui.Mouse;

public class Testing extends Sprite
{
// Box properties
private var box:Rectangle = new Rectangle(250, 250, 150, 100);

// Mouse movement properties
private var oldMouseX:Number = mouseX;
private var oldMouseY:Number = mouseY;

private var mouseMovementRadians:Number = 0;
private var mouseMovementDistance:Number = 0;

// Ray tracing properties
private var cone:Sprite = new Sprite();
private var coneSpread:Number = 80;
private var coneStep:Number = 30;

private var rayDepth:Number = 100;
private var rayLength:Number = 100;


public function Testing()
{
init();
}

private function init():void
{
// Mouse.hide();

stage.addEventListener(MouseEvent.MOUSE_DOWN, startRayTrace);
stage.addEventListener(MouseEvent.MOUSE_MOVE, updateMouseMovementInfo);
}

private function startRayTrace(event:MouseEvent):void
{
// In case they right clicked, hide the mouse again when they left click.
// Mouse.hide();

addChild(cone);

stage.addEventListener(MouseEvent.MOUSE_UP, stopRayTrace);

addEventListener(Event.ENTER_FRAME, rayTrace);
}

private function stopRayTrace(event:MouseEvent):void
{
removeChild(cone);

removeEventListener(Event.ENTER_FRAME, rayTrace);
}

private function updateMouseMovementInfo($event:MouseEvent):void
{
// Find the ditsances of the x and y vectors
var dx:Number = oldMouseX - mouseX;
var dy:Number = oldMouseY - mouseY;

mouseMovementRadians = -Math.atan2(dx, dy);
mouseMovementDistance = Math.sqrt(dx*dx + dy*dy);

oldMouseX = mouseX;
oldMouseY = mouseY;
}

private function rayTrace(event:Event):void
{
var i:int;

// Find the radians for each step and angle
var stepRadians:Number = coneSpread/coneStep * Math.PI/180;
var offsetRadians:Number = -coneSpread/2 * Math.PI/180;

cone.graphics.clear();
cone.graphics.lineStyle(1, 0xFF0000);

cone.graphics.moveTo(mouseX, mouseY);

// Ray-tracing
for(i = 0; i < coneStep; i ++)
{
var radians:Number = mouseMovementRadians + i*stepRadians + offsetRadians;

var x:int = Math.sin(radians)*rayLength + mouseX;
var y:int = -Math.cos(radians)*rayLength + mouseY;

cone.graphics.lineTo(x, y);
}

cone.graphics.lineTo(mouseX, mouseY);
}
}
}

zerosum
September 12th, 2008, 03:31 PM
Wow, thanks a bunch - that works really fine for my purposes. Very much appreciated!
If it's not asking for too much, could you quickly blurb why it will be shaky by using integers? (and should I use something else)? Again, if it's too much, no worries :)
Thanks!

Smee
September 12th, 2008, 04:29 PM
If you make a new program and just trace out the mouseX and mouseY properties on MOUSE_MOVE, then you'll see that they are always integers. So if you move your mouse slow enough that you're only moving one pixel at a time, then you will only be able to move in angles that are multiples of 45.

Imagine a 3x3 grid, and your mouse is in the center cell; if you move your mouse slow enough that it only moves to one cell outside it's current cell, then it will be forced to move at an angle of n*45. Unfortunately, I don't think there's a way around this.

zerosum
September 17th, 2008, 12:51 PM
Smee, that was a great explanation - thanks a lot for the help!