Basic Collision for 3D: Line Collision

Use space bar to make the sphere jump

For this kind of collision you might ask, why not use Box2D again. But as I mentioned in the other line collision tutorials I wrote, you don't always have to use Box2D for every kind of odd collision detection. So here is a simplified way of using oblique platforms in 3D.

If you don't know the straight line equation or the logic behind the detection of when and where two straight lines meet here is a quick review.

This is a line:

1
2
 
2x + y = 3

In fact, every equation where you have X, or Y, or both and neither of them are raised to any power, you have a straight line. So this is a line:

1
2
 
y = 3

You just plugged 0 for the value of X in the previous equation.

The traditional representation of a straight line equation is:

1
2
 
ax + y = b

The coefficient of X (a) is the slope of the line, and b is the y intersect. Let me show you what the intersect means first. b is the Y value at which the line crosses the Y axis. At that particular point, if you can visualize it in your head, x = 0. Just plug in 0 for X into the following line equation:

1
2
 
2x - y = 5

And solving for Y you will see that the Y intersect is -5. Meaning the line crosses the Y axis at the point (0,-5).

Here's another line:

1
2
 
x = y

And I trust by now you see that this line is a 45 degree line, its slope is 1 and the intersect point is (0,0).

And what is the slope? Slope is rise over run, or y over x. It is a number that tells you how oblique a line is, as well as the direction of the line: is it going down or up?

If two lines have different slopes, they will cross at some point in space.

So these lines will cross:

1
2
3
4
 
2x + y = 3
 
-3x - y = 5

One has the slope of 2 and the other -3. And now the really important bit:

The point in space where the two lines cross is shared by the two lines. At that particular moment the two line equations are the same. Or rather, they both produce the same result, the same point. And to find that point you use the algebraic method of substitution. First you solve one equation to either X or Y and then replace that same variable in the second equation with that.

So:

1
2
3
4
 
y = 3 - 2x;
 
-3x - (3 - 2x) = 5;

Now you can solve X and replace it again in the first equation to solve for Y and you got yourself that point shared by the two lines. The point where they meet.

In code what you end up doing is having lines for platforms, and the trajectory of your game elements as they move are calculated as lines as well, and with that you can calculate if the game element is colliding with the line or not.

You will see this represented in the code, don't worry. But you can also check out the other tutorials on line collision here:

Here is the 2D representation of the same logic used in the 3D one:

Of course you could easily check for depth displacement and cause the sphere to fall down from the sides of the platforms. But this tutorial is mainly about line collision.

And finally the code:

The Document Class

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
 
package {
 
 
 
	import away3d.cameras.*;
 
	import away3d.containers.*;
 
	import away3d.materials.*;
 
	import away3d.primitives.*;
 
 
 
	import flash.display.Sprite;
 
	import flash.events.Event;
 
	import flash.events.KeyboardEvent;
 
	import flash.geom.*;
 
 
 
	[SWF(width="1000", height="800", backgroundColor="#CCCCCC", frameRate="30")]
 
	public class LineCollision3D extends Sprite {
 
 
 
		private var _view:View3D;
 
		private var _container:ObjectContainer3D;
 
		private var camera:TargetCamera3D;
 
 
 
 
 
		private var _lineCollection:Vector.<Line> = new Vector.<Line>();
 
		private var hero:Hero3D;
 
 
 
		public function LineCollision3D() 	{
 
			createLines();
 
			create3DElements();
 
 
 
			stage.addEventListener(KeyboardEvent.KEY_UP, on_KeyUp, false, 0, true);
 
			addEventListener(Event.ENTER_FRAME, onLoop, false, 0, true);
 
		}
 
 
 
		private function createLines ():void {
 
 
 
			var line:Line;
 
			//create line objects (they will not be drawn on Stage in the 3D version)
 
			line = new Line (new Point(-300,300), new Point(380,200));
 
			_lineCollection.push(line);
 
 
 
			line = new Line (new Point(-300,-20), new Point(180,110));
 
			_lineCollection.push(line);
 
 
 
			line = new Line (new Point(-125,-130), new Point(125,-200));
 
			_lineCollection.push(line);
 
 
 
		}
 
 
 
		private function create3DElements ():void {
 
 
 
			_container = new ObjectContainer3D();
 
			var scene:Scene3D = new Scene3D();
 
 
 
			camera = new TargetCamera3D();
 
			camera.lookAt (new Vector3D(0,0,0));
 
			camera.z = -900;
 
 
 
			_view = new View3D({scene:scene, camera:camera});
 
			_view.x = 500;
 
			_view.y = 400;
 
			addChild(_view);
 
 
 
			//create planes based on the line data information
 
			var plane:Plane;
 
			for (var i:int = 0; i < _lineCollection.length; i++) {
 
				plane = new Plane();
 
				//grab the line middle point for the plane
 
				plane.x = _lineCollection[i].data.middle.x;
 
				plane.y = _lineCollection[i].data.middle.y;
 
				plane.segmentsH = 2;
 
				plane.segmentsW = 6;
 
				plane.material = new WireColorMaterial(0xFF9900,{wireColor:0x222222});
 
				plane.width = _lineCollection[i].data.length;
 
				plane.height = 80;
 
				//rotate plane based on line angle
 
				plane.rotationZ = _lineCollection[i].data.angle * 180 / Math.PI;
 
				plane.bothsides = true;
 
				_container.addChild(plane);
 
			}
 
 
 
			//create the sphere
 
			hero = new Hero3D(40);
 
			hero.x = 60;
 
			hero.y = -400;
 
			_container.addChild(hero);
 
			_container.rotationX = 180;
 
			_view.scene.addChild(_container);
 
			camera.target = hero;
 
		}
 
 
 
 
 
		private function onLoop (event:Event):void {
 
 
 
			hero.update();
 
 
 
			//loop through lines and check for collision
 
			for (var i:int = 0; i < _lineCollection.length; i++) {
 
				if(_lineCollection[i].checkSpritePosition(hero)) {
 
					hero.platform = _lineCollection[i];
 
					break;
 
				}
 
			}
 
 
 
			hero.move();
 
 
 
			if (hero.y > 400) {
 
				hero.x = 60;
 
				hero.y = -400;
 
				hero.vx = hero.vy = 0;
 
			}
 
 
 
			_view.scene.rotationY = mouseX - 500 * 0.002;
 
			_view.render();
 
 
 
		}
 
 
 
		private function on_KeyUp (event:KeyboardEvent):void {
 
			switch (event.keyCode) {
 
				case 32:
 
					//SPACE Bar is up
 
					hero.jump();
 
					break;
 
			}
 
		}
 
 
 
	}
 
}

The Hero Class

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
 
package {
 
	import away3d.containers.ObjectContainer3D;
 
	import away3d.materials.WireColorMaterial;
 
	import away3d.primitives.Sphere;
 
 
 
 
 
	public class Hero3D extends ObjectContainer3D {
 
 
 
		private const JUMP:int = 16;
 
		private const MAX_SPEED_X:int = 18;
 
		private const MAX_SPEED_Y:int = 12;
 
 
 
		public var vx:Number = 0;
 
		public var vy:Number = 0;
 
		public var nextX:Number;
 
		public var nextY:Number;
 
		public var width:int;
 
 
 
		private var _inAir:Boolean = true;
 
 
 
		private var _platform:Line = null;
 
		private var _container:ObjectContainer3D;
 
		private var _sphere:Sphere;
 
 
 
		function Hero3D (r:int = 10) {
 
			super();
 
 
 
			width = 2*r;
 
 
 
			_sphere = new Sphere();
 
 
 
			_sphere.radius = r;
 
			//make the sphere sit right on top of the container center point
 
			_sphere.y -= r + 2;
 
			_sphere.material = new WireColorMaterial(0xFF3399,{wireColor:0x000000});
 
 
 
			addChild(_sphere);
 
 
 
		}
 
 
 
		public function set platform (value:Line):void {
 
 
 
			if (value) {
 
				if (!_platform) vx *= 0.8;
 
				_inAir = false;
 
				vy = 0;
 
			} else {
 
				if (_platform && vy > 0) {
 
					vx *= 0.3;				
 
				} 
 
				_inAir = true;
 
			}
 
			_platform = value;
 
		}
 
 
 
		public function get platform ():Line {
 
			return _platform;
 
		}
 
 
 
		public function get height ():Number {
 
			return width;
 
		}
 
 
 
		public function update ():void {
 
			if (_inAir) {
 
				//add gravity
 
				vy += 2;
 
			}
 
 
 
			if (!_inAir) {
 
				vy = 0;
 
				if (Math.abs(vx) > MAX_SPEED_X*0.6) { 
 
					vx *= 0.8;
 
				}
 
			} 
 
			if (_platform) {
 
				if (_platform.data.slope != 0) {
 
					var angle:Number = _platform.data.angle;
 
					vx +=  4*angle;
 
 
 
				}
 
			}
 
			if (vx != 0) _sphere.rotationZ += vx;
 
			nextX = x + vx;
 
			nextY = y + vy;
 
		}
 
 
 
		public function move ():void {
 
			x = nextX;
 
			y = nextY ;
 
		}
 
 
 
		public function jump ():void {
 
			if (!_platform) return;
 
			vy = -JUMP;
 
			this.platform = null;
 
		}
 
	}
 
}

The Line Class

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
 
package {
 
	import away3d.containers.ObjectContainer3D;
 
 
 
	import flash.geom.Point;
 
	import flash.geom.Rectangle;
 
 
 
	public class Line  {
 
		private var _lineData:LineData;
 
		private var _bounds:Rectangle;
 
 
 
		function Line(start:Point, end:Point) {
 
			_lineData = new LineData(start,end);
 
			_bounds = _lineData.bounds;
 
		}
 
		public function get data():LineData {
 
			return _lineData;
 
		}
 
		//this checks for collision 
 
		public function checkSpritePosition(h:Hero3D):Boolean {
 
			var hBox:Rectangle = new Rectangle(h.nextX - h.width / 2,h.nextY - h.height / 2,h.width,h.height);
 
			if (!hBox.intersects(_bounds)) {
 
				if (this == h.platform) {
 
					h.platform = null;
 
				}
 
				if (h.platform && (h.x < h.platform.data.startPoint.x || h.x  > h.platform.data.endPoint.x )) {
 
					h.platform = null;
 
				}
 
				return false;
 
			}
 
			//if you're here, the line is within the boundaries of the hero, check for collision
 
 
 
			//if hero is already on this line, simply calculate the next Y  
 
			if (h.platform) {
 
				if (h.platform == this) {
 
					h.nextY = _lineData.translateFromX(h.nextX);
 
				}
 
				return true;
 
			}
 
 
 
			//if hero is int the air, find point where its trajectory will cross this line
 
			var intersect:Point = getIntersection(new Point(h.x,h.y),new Point(h.nextX,h.nextY));
 
 
 
			//check for collision with that point
 
			if (h.vy > 0 && h.y < intersect.y && h.nextY >= intersect.y) {
 
				h.nextY = intersect.y;
 
				h.vy = 0;
 
				return true;
 
			}
 
 
 
			//if everything else failed, the hero is still in the air
 
			h.platform = null;
 
			return false;
 
		}
 
		public function getIntersection(start:Point, end:Point):Point {
 
 
 
			//if line is horizontal hero moves across it as with any other simple platform
 
			if (_lineData.slope == 0) {
 
				return new Point(end.x, _lineData.startPoint.y);
 
			}
 
 
 
			//create a line data object with the hero's trajectory
 
			var newLine:LineData = new LineData(start,end);
 
			var intersect:Point = new Point();
 
 
 
			//find the point of intersection between the line and hero's trajectory
 
			intersect.x = (newLine.yAxis - _lineData.yAxis) / (_lineData.slope - newLine.slope);
 
			intersect.y = _lineData.slope * intersect.x + _lineData.yAxis;
 
			return intersect;
 
		}
 
	}
 
}

The LineData Class

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
 
package {
 
 
 
	import flash.geom.Point;
 
	import flash.geom.Rectangle;
 
 
 
	public class LineData {
 
 
 
		public var startPoint:Point;
 
		public var endPoint:Point;
 
		public var slope:Number;
 
		public var yAxis:Number;
 
		public var width:Number;
 
		public var height:Number;
 
		public var angle:Number;
 
		public var sin:Number;
 
		public var cos:Number;
 
		private var _risesToZero:Boolean;
 
 
 
		function LineData (start:Point, end:Point) {
 
 
 
			//get the top left corner of the rectangle boundary of the line, no matter what the line looks like
 
			var startX:Number = Math.min(start.x, end.x);
 
			var startY:Number = Math.min(start.y, end.y);
 
			var endX:Number = Math.max(start.x, end.x);
 
			var endY:Number = Math.max(start.y, end.y);
 
 
 
			//whether the line rises to X = 0 or away from X = 0 
 
			_risesToZero = ((start.y < end.y && start.x < end.x) || (end.y < start.y && end.x < start.x));
 
 
 
			this.startPoint = new Point(startX, startY);
 
			this.endPoint = new Point(endX, endY);
 
 
 
			calculateLineData();
 
		}
 
 
 
		public function get length ():Number {
 
			return Point.distance(startPoint, endPoint);
 
		}
 
 
 
		public function get middle ():Point {
 
			return new Point((startPoint.x + endPoint.x)/2, (startPoint.y + endPoint.y)/2);			
 
		}
 
 
 
		public function get bounds ():Rectangle {
 
			return new Rectangle(Math.min(startPoint.x, endPoint.x),
 
								 Math.min(startPoint.y, endPoint.y),
 
								 width, height);
 
		}
 
 
 
		/*public function drawLine ():Shape {
 
			var shape:Shape = new Shape();
 
			shape.graphics.lineStyle(1,0xCCCCCC);
 
			if (_risesToZero) {
 
				shape.graphics.moveTo(0, 0);
 
				shape.graphics.lineTo(end.x - start.x, end.y - start.y);
 
			} else {
 
				shape.graphics.moveTo(end.x - start.x, 0);
 
				shape.graphics.lineTo(0, end.y - start.y);
 
			}
 
			return shape;
 
		}*/
 
 
 
		//solve the line equation for Y with the X value given
 
		public function translateFromX (xMove:Number):Number {
 
 
 
			if (slope == 0) {
 
				return startPoint.y;
 
			}
 
 
 
			return slope * xMove + yAxis;
 
 
 
		}
 
 
 
		//solve the line equation for X with the Y value given
 
		/*public function translateFromY (yMove:Number):Number {
 
			return (yMove - yAxis)/ slope
 
		}*/
 
 
 
		private function calculateLineData ():void {
 
 
 
			width = endPoint.x - startPoint.x;
 
			height = Math.abs(endPoint.y - startPoint.y);
 
 
 
			angle = Math.atan2(endPoint.y - startPoint.y, endPoint.x - startPoint.x);
 
 
 
			if (!_risesToZero) angle *= -1;
 
 
 
			sin = Math.sin(angle);
 
			cos = Math.cos(angle);
 
 
 
			slope = Math.tan(angle);
 
 
 
			if (slope == Number.POSITIVE_INFINITY) {
 
				slope = 1000000;
 
			} else if (slope == Number.NEGATIVE_INFINITY) {
 
				slope = -1000000;
 
			}
 
 
 
			if (!_risesToZero) {
 
				yAxis = endPoint.y - slope * startPoint.x;
 
			} else {
 
				yAxis = startPoint.y  - slope * startPoint.x;
 
			}
 
 
 
		}
 
	}
 
 
 
}