What do you do to revitalize a slowing project?

•February 23, 2013 • Leave a Comment

The following draft of a post has been sitting around since early 2012, and I had never published it. Since I think it still contains some potentially useful ideas, though some of the Leges Motus-specific details are a bit out-of-date, I have decided to publish it as-is, without being edited to reflect the time gap. Hopefully it will be useful to someone.

- Greywhind

—-

It’s been seven months since the most recent stable release of Leges Motus. The release is almost ready – there are only a few more bugs to fix before it can be packaged up and sent out there.

This time has mostly been filled with work on new features, bug fixes, testing, and the like, but some of it has also been downtime. The developers are all very busy people, with schedules that can often leave little time or energy to work on more coding.

So how, then, do you keep a project alive when it’s going slowly? That’s the question I’ve been asking myself, and I think there are a few important elements:

1. At least one or two members of the development team have to be willing to put in some extra time. Why? Encouraging others to work on the project is much more effective when you’re doing cool things yourself. They won’t listen if you’re also sitting around doing nothing.

2. You have to be understanding of their schedules. Nagging won’t help – you just need to show your continued involvement, produce some new features or fix some bugs, and keep them informed of what’s going on with the project. As they hear of exciting new developments, they’ll hopefully start to move the project up on their priority list.

3. If tasks assigned to another developer are blocking other progress and making things slow down, you have a few options. First, you should probably try talking to them. Often, offering to help or just giving them a polite reminder that you care about and need their work can do the trick (though they may still eventually slow down or stop work, you can probably get at least the current blocker resolved). But if that doesn’t work, you’ll need to consider trying to take over their assignment. You should try to do this in a way that makes them still feel useful (not just kicking them out of the project, but instead asking their permission to do the task for them). If you don’t know enough about their work to do this, you might need to either spend a lot of time getting to know their code or find another developer (either already associated with or completely new to the project) to do the task. This can be the hardest part.

4. Bringing a new developer into the mix can also be a great way to pump some energy into the project. If you have at least one dev with enough time to show the new person the ropes, you can get them moving and bring some enthusiasm and new ideas into the mix. Just make sure they don’t end up pulling all the weight, because they’ll get burnt out.

5. Finally, sometimes there’s nothing to do but give it some time. Interest flows in cycles – if you give it a rest, doing some small things when you feel like it, you will probably end up with a growing feeling that you want to work on the project. If you’re lucky, everyone else will too, and interest will pick back up.

The trick to revitalizing a slowing project is to keep your contributions steady and interesting, to talk to the other developers about the project, and to avoid nagging too much – if someone is too busy, work around it rather than alienating them with constant pushing.

So what have I been doing for Leges Motus? I’ve been making a new online server browser, which can now be seen on the website. I’ve added a scroll-wheel key binding to switch to the next and previous weapons. I’ve planned/opened a few new to-do tickets on the bug tracker. And I’ve been talking about the project with other developers, keeping them in the loop and keeping Leges Motus on their minds. It takes more than one man to make a project, but only one to keep it kicking.

Leges Motus Progress – 2D Particle Effects, GL_POINT_SPRITE

•November 27, 2011 • Leave a Comment

A few days ago, I posted a brief description of the beginnings of the new particle effects system in Leges Motus. Since then, I have been working hard to finish up the system to the point where it’s fully usable in-game. It has now reached that state, and is fully integrated with gameplay (though it may be used in more places in the future). Particle effects spawn whenever a player fires, and can be specified for each weapon in a config file.

For this purpose, I have made several improvements and additions to the particle effects system. I have added a “SimpleLineEmitter” class, which can be used to randomly spawn particles along a given line, instead of just at a specific point. This works nicely for tracer trails, and I am currently using it for the rifle’s effect.

Additionally, I have changed the drawing code for particle effects. The original code drew each particle like any other sprite, in immediate mode and with a quad. This was extremely inefficient, so I created a new method in DrawContext that allows drawing an image repeatedly in many places at once using GL_POINT_SPRITE and VertexArrays. This should allow drawing many more particles at a time, because it reduces the number of draw calls significantly.

I keep arrays of the particle centers and colors in the emitter, trying to initialize it to the max number of particles that will be spawned by the emitter at a time, but increasing the size if necessary. These are simply float arrays, with the centers array being twice the size of the number of particles, so it can hold the x, y for each one in sequence, and the colors array being four times the size of the number of particles, so it can hold the RGBA values for each particle in sequence. I bind the sprite I want to draw, and then pass these arrays into the following function (LM_gl and LM_GL are just macros around gl_ and GL_):


void GLESContext::draw_bound_point_sprites(const float vertices[], int n, int size_x, int size_y, const float colors[]) {
LM_gl(Enable, (LM_GL(POINT_SPRITE)));
LM_gl(TexEnvi, (LM_GL(POINT_SPRITE), LM_GL(COORD_REPLACE), LM_GL(TRUE)));

// Set the size of all the point sprites
LM_gl(PointSize, (max(size_x,size_y)));

unbind_vbo();

// Configure the point array
LM_gl(VertexPointer, (2, LM_GL(FLOAT), 0, vertices));

// Enable the use of the color array
LM_gl(EnableClientState, (LM_GL(COLOR_ARRAY)));

// Configure the color array
LM_gl(ColorPointer, (4, LM_GL(FLOAT), 0, colors));

// use glDrawArrays to draw the points
LM_gl(DrawArrays, (LM_GL(POINTS), 0, n));

// Disable the client states
LM_gl(DisableClientState, (LM_GL(COLOR_ARRAY)));
LM_gl(Disable, (LM_GL(POINT_SPRITE)));
}

Unfortunately, in standard OpenGL, there does not seem to be an easy way (other than shaders) to specify the size of each image point separately. OpenGLES has glPointSizePointerOES, which would allow specifying a size for each particle alongside the x/y coordinates.

I am very satisfied with the results of this new particle effect system. I think it will add a lot of visual interest to the game. Screenshot:

A bunch of AIs shooting each other, showing off the new particle effects system in Leges Motus.

A bunch of AIs shooting each other, showing off the new particle effects system in Leges Motus.

Leges Motus Progress – Basic Particle Effects

•November 24, 2011 • Leave a Comment

One of the main reasons we wanted to complete the Leges Motus revamp was to improve the graphical quality and special effects capabilities of the game. One of my top-priority goals in this vein was to create a particle-effect system that would be able to add some visual flair to firing, getting hit, and so forth.

So, a couple of days ago, I got it into my head to make this happen, and I have now implemented a basic particle effects system, which, while still likely needing some expansion and optimization, will allow a wide range of cool effects to be created with relative ease. Below is a screenshot of a test application using the new system to create a fireworks-like effect.

Leges Motus particle system test.

A test of the new basic particle effects system for Leges Motus, showing some simple fireworks-like bursts.

To create this system, I took some inspiration from the following papers: “The Ocean Spray in Your Face” (Jeff Lander, 1998) and “Building an Advanced Particle System” (John Van der Burg, 2007). These are excellent resources, and I highly recommend them for their simple and understandable explanations, though I found that they left a few crucial details lacking.

First, they provide little information on rendering questions, such as adding shaders to particle effects and optimizing your drawing routine. I have not yet switched to rendering particles with GL_POINT_SPRITE, instead of quads like a normal sprite, but it will probably be important to do so for performance reasons. I have also not yet really experimented with shaders on particle effects, but I expect to look into that eventually as an addition to the system.

The basic system is as follows:

There is a simple Particle class, with important properties for each particle to keep track of separately:


Point m_pos;
Point m_prev_pos;
Vector m_vel;
uint64_t m_energy_left;
uint64_t m_initial_energy;
Color m_color;
float m_size;

ParticleEmitter is a base class, which provides the basic functionality that is required to spawn a bunch of particles. This functionality is then extended by each specific type of emitter. Currently, I have only implemented a single specific emitter type, a basic burst-like emitter that is surprisingly flexible.


class ParticleEmitter {
private:
Image* m_image;
DrawContext::BlendMode m_blend_mode;
int m_alive_count;
Point m_center;
ParticleManager* m_manager;
protected:
std::list m_particles;
public:
ParticleEmitter(ParticleManager* manager, Point center, Image* image, DrawContext::BlendMode mode = DrawContext::BLEND_ADD);
virtual ~ParticleEmitter();

void draw(DrawContext* context);

std::list& get_particles();

void clear();

Particle* request_particle();
void free_particle(Particle* particle, std::list::iterator it);

virtual bool update(uint64_t timediff);

Point get_center();

void set_center(Point center);
void set_center(float center_x, float center_y);
};

Each specific emitter type will be given its own settings, in the form of a struct. The struct for the SimpleRadialEmitter class looks like:


struct SimpleRadialEmitterSettings {
float particle_speed;
float speed_variance;
int spawn_per_second;
int spawn_variance;
uint64_t lifetime_millis;
uint64_t lifetime_variance;
float rotation_rads;
float rotation_variance;
Vector global_force;
int max_spawn;
uint64_t emitter_stop_spawning_millis;
uint64_t emitter_lifetime_millis;
};

Finally, all the emitters currently active are managed by the ParticleManager class, which runs their update and draw loops and deletes them when they’re done. It also contains a (potentially resizable) pool of particles, to avoid object creation/deletion too frequently during gameplay.

The ParticleManager is also a “Widget,” which means it can be placed into any other widget in the Leges Motus graphics framework. This allows the particle system to be easily overlaid on the existing game display, or anywhere else we need it.

This development is moving slowly, due to the final other programmer, Archaemic, no longer having time to work on the project. Going it alone, it’ll take me quite a while to get all the features (especially menus) done. I don’t plan to give up, though.

Leges Motus Progress – AI and Player Update Setup

•August 27, 2011 • Leave a Comment

I have been very remiss in posting updates about Leges Motus for the last few months, and for that, I apologize. The game has been moving forward slowly since late May, but it should be picking up some more speed again soon.

Our most recent updates saw the way that clients send information to the server about their actions changed somewhat, reducing the load on the network as less information needs to be sent frequently to the server.

Earlier, before the end of May, we focused heavily on getting AI support fully implemented. The AI is now working well, though there are still a few things that could be improved, and it has proven very fun to play against for human players, which is exactly what we were aiming for.

Our artist (http://www.stevensugar.com/) has sent us a few new concept pieces, and we expect to get him working on making final in-game art soon.

One of the concept art pieces created for Leges Motus.

One of the concept art pieces created for Leges Motus.

Keep watching for more information!

Leges Motus Progress: Pathfinding with Ray Casting and A*

•March 30, 2011 • Leave a Comment

Since our last update, the Leges Motus team has been working hard on a new portion of the AI: pathfinding. Having an AI that jumps around randomly, trying to slowly get towards the goal, won’t get you very far in terms of realistic simulation or believable gameplay. We decided to make a pathfinding system that would allow our AIs to find paths to any point on the map.

Here is a screenshot of our pathfinding system in progress – you can see the line showing the calculated path to the goal:

Testing the Leges Motus pathfinding system

A test version of the Leges Motus pathfinding system.

We could not simply use a standard A* algorithm based on a grid of connected space, because it would assume the ability to turn in mid-air, which is not possible in zero-gravity. We decided, however, that we still wanted to try to use A* as the final pathfinding algorithm. To do so, we had to make a connected graph of all points on walls in the map that could be reached from other walls. We created a data structure, which we call a SparseIntersectMap, to hold this information. It contains x, y, and angle coordinates, mapped to other x, y, angle coordinates, with distances. It also contains the means to find the correct bin for an arbitrary x, y, and angle value, with a specific granularity. This allows us to take an approximate location, reducing the storage costs to a manageable number from the unimaginable cost of storing connections at every pixel.

Another class is used to create this graph, casting rays from each bin, getting results, and storing them in the SparseIntersectMap. Another shortcut is taken here – once a location is reachable from another bin, we store the back-connection as well, avoiding casting again when we reach that bin.

Finally, once we have the graph, a standard A* algorithm can be used to find the paths. This is the most standard part of the process.

We found that there were some problems with this approach. Casting at large angles could cause bounces between obstacles that were almost touching, leading to paths between walls that were not actually passable. Therefore, we cast only at angles that are somewhat closer to the normal of the wall. Additionally, ray casting from within an object causes the cast to ignore that object, so we ensure that all casts are done slightly inside the wall, avoiding ignoring any other objects that may be touching the wall.

The result is a fairly decent pathfinding system. It still could use more tweaks: it sometimes finds paths that go very close to a wall, where the player will actually hit the wall instead of getting past. This is somewhat less of a problem than it might seem at first – even real players cannot always calculate their trajectories properly to get there on the first jump. Nonetheless, we are looking into ways to reduce this problem.

We have also added a non-standard extra weighting to the A* algorithm – we found that paths would often be placed in such a way that the player could get stuck jumping from one wall to another, always calculating a new path that led back to the old location before continuing. Therefore, we put weights such that the most recent old location is less desirable. This has reduced this problem.

I’ll keep posting as things progress – the next step will be to create a finite state machine system, allowing us to add some more strategy to our reactions, following an overall ‘personality’ or ‘current objective.’

Leges Motus Progress: Simple and Fuzzy Logic AIs

•March 3, 2011 • Leave a Comment

Time has gotten away from me once again, and I have not posted a recent update on our progress with Leges Motus’ AI creation. First of all, the most recent code for Leges Motus is now on GitHub (https://github.com/jpfau/legesmotus), instead of SourceForge’s SVN. Secondly, we have been making significant progress on the AI creation.

We started with a very simple, deterministic AI, to test out our ability to make a completely player-less client. This was very easy, due to the design of the re-written client and logic code. The simple AI essentially works as follows:

  1. Find the nearest enemy we can see.
  2. Find out if we can see the enemy gate.
  3. If we are not on the enemy gate and we can see it, turn the gun towards the gate and then jump towards it.
  4. If we can see an unfrozen enemy, turn the gun towards them and fire (with some random inaccuracy).
  5. If we can’t do either of those, jump randomly (with a chance of jumping towards the gate, even if we can’t see it, in hopes of getting closer).

That’s about it.  It’s simple, it looks a bit silly jumping randomly into walls, but it works. It can even win games against human players. But really, it’s just a baseline test.

The Fuzzy Logic AI is more interesting, though still not up to the level we will be trying to reach for our final AI. The system is approximately as follows:

  1. A config file specifies the ranges and edge functions for each fuzzy logic category/bin within that category. Example: dist_to_player.melee is somewhere around 0-64 pixels away, with dist_to_player being the category and melee being the bin.
  2. The categories are loaded, and put into a FuzzyLogic object. A FuzzyEnvironment is passed in each frame with the appropriate values set for each category.
  3. The FuzzyLogic passes the bin values into various Rules, which are constructed in a hierarchical structure (Example: two terminal bin values inside an Or rule inside an AND rule, all in a NOT).
  4. The Rules each output a float value, which is used to determine the importance of various actions or the value of various things. For example, a rule is used to determine which enemy player is most dangerous.
  5. The output actions are similar to the possible output actions of the Simple AI, but decided upon in a much more flexible and interesting way. This makes the AI seem less computer-like and more like it is making informed decisions.

We are also looking into making the FuzzyLogicAI switch weapons as appropriate to the situation. This could be slightly more difficult, since the weapon system was not designed to allow easy external determinations of weapon damage at a specific range, exact effects, etc. We will be considering how best to make these decisions general enough to work with any weapon setup.

Some Fuzzy Logic AIs helping me capture the enemy's gate.

Some Fuzzy Logic AIs helping me capture the enemy's gate.

Unfortunately, the FuzzyLogicAI is still unable to jump in a useful manner (other than randomly) except when it sees an enemy gate. This means that our next step will be pathfinding. We plan to store information about the reachability graph for each map on disk, and to load that into memory when the AI joins. This could have a lot of space/time tradeoff issues to look at, and we will then need to determine the best algorithm (probably A* of some sort) to find the paths through the map once the graph is made.

I’ll keep updates coming as we continue the AI work.

Managing RayCasts in Box2D

•February 26, 2011 • Leave a Comment

The Box2D physics library is quite impressively simple to use, for the most part. It manages all the necessary calculations internally to provide for standard movement and collisions. When your project needs to go outside this standard behavior, however, Box2D can sometimes be a little bit more complicated. Specifically, the RayCast system is slightly cumbersome to deal with, at times. Since I have recently been dealing with this for Leges Motus’ AI code (which I’ll talk about in another post), I will present my thoughts on managing RayCasts in Box2D.

To call a ray cast, you use the method:

RayCast(b2RayCastCallback& listener, b2Vec2& start_point, b2Vec2& end_point);

It uses a callback, defined by having your class implement b2RayCastCallback, with the signature

float32 ReportFixture(b2Fixture* fixture, const b2Vec2& point, const b2Vec2& normal, float32 fraction);

This callback reports the current fraction of the ray at which the hit has occurred, as well as which fixture, which point in space, and the normal of the surface on the fixture. You need to return a fraction, after which the RayCast will ignore all points. If you return 1, it will continue to give you all additional hit results. If you return 0, it will immediately stop giving you further results. If you return the current fraction, it no longer returns anything beyond that fraction, but will continue to check within the remaining part of the ray.

Unfortunately, the callback is entirely unordered – there is no requirement that the closest points will be given first. This makes dealing with it somewhat annoying, at times. Additionally, it stores no information about the ray cast after the callback is done, unless you store it yourself, and returns no information from the ray cast call itself. You have to use an instance field or global variable to keep the results you care about. This can get somewhat ugly, if you do not want to add random global variables in random classes that need a ray cast for something.

I recommend making a class specifically to handle ray casts. It should wrap the ray cast method itself (possibly providing a simple way to cast in a direction, instead of specifying the exact end point). Additionally, it should implement the callback and keep global info about the ray cast in a struct or inner class. You might want to store a sorted list of all hit points, or just the closest. It depends on your needs. Then, make that data accessible to the object that called in for the ray cast. This will make it much easier to write small amounts of code to do a ray cast, then get the results. Processing the results might still take some work, but it won’t be as messy.

Also, I recommend that you have all your physical objects extend some sort of “physics object” base class, so that you can set them as user data on their physics bodies, and you can therefore check their type and such.

Hopefully this will help you manage ray casts in Box2D. If you have other suggestions or questions, feel free to leave a comment.

Some current example code for a class we use for this purpose in Leges Motus follows (note: you can check the current code for Leges Motus at https://github.com/jpfau/legesmotus, if you want an updated version or to see the context):

RayCast.hpp:

/*
 * common/RayCast.hpp
 *
 * This file is part of Leges Motus, a networked, 2D shooter set in zero gravity.
 * 
 * Copyright 2009-2011 Andrew Ayer, Nathan Partlan, Jeffrey Pfau
 * 
 * Leges Motus is free and open source software.  You may redistribute it and/or
 * modify it under the terms of version 2, or (at your option) version 3, of the
 * GNU General Public License (GPL), as published by the Free Software Foundation.
 * 
 * Leges Motus is distributed in the hope that it will be useful, but WITHOUT ANY
 * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
 * PARTICULAR PURPOSE.  See the full text of the GNU General Public License for
 * further detail.
 * 
 * For a full copy of the GNU General Public License, please see the COPYING file
 * in the root of the source code tree.  You may also retrieve a copy from
 * <http://www.gnu.org/licenses/gpl-2.0.txt>, or request a copy by writing to the
 * Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
 * 02111-1307  USA
 * 
 */

#ifndef LM_COMMON_RAYCAST_HPP
#define LM_COMMON_RAYCAST_HPP

#include "common/physics.hpp"

namespace LM {
	class PhysicsObject;
	class Player;
	class MapObject;

	class RayCast : public b2RayCastCallback {
	public:
	
	struct RayCastResult {
		const PhysicsObject*	start_object;	// The object (if any) where this ray started
		b2Vec2		ray_start;	// The starting point of the ray cast
		float		ray_direction;	// The angle (in radians) at which the ray was cast
		b2Vec2		ray_end;	// The maximum point on the ray
		PhysicsObject* 	closest_object; // The closest object
		float		shortest_dist;	// The closest hit-point on that object
		b2Vec2		hit_point;	// The point where the ray hit
	};

	private:
		RayCastResult m_ray_cast;
		const b2World* m_physics;
		bool m_ignore_collidable;

	public:
		RayCast();
		RayCast(const b2World* physics);
		
		~RayCast();
		
		RayCastResult& get_result();
		
		void set_physics(const b2World* physics);
	
		float cast_at_player(const Point& ray_start, const Player* other_player, float max_radius = -1);
		float cast_at_player(const b2Vec2& ray_start, const Player* other_player, float max_radius = -1);
		
		float cast_at_obstacle(const Point& ray_start, const MapObject* object, float max_radius = -1, bool ignore_collidable = false);
		float cast_at_obstacle(const b2Vec2& ray_start, const MapObject* object, float max_radius = -1, bool ignore_collidable = false);
		
		float cast_in_vel_dir(const Player* player);
	
		float do_ray_cast(const b2Vec2& start_point, float direction, float distance, const PhysicsObject* starting_object = NULL, bool ignore_collidable = false);
	
		// Box2D Physics Callbacks
		float32 ReportFixture(b2Fixture* fixture, const b2Vec2& point, const b2Vec2& normal, float32 fraction);
	};
}

#endif

RayCast.cpp:

/*
 * common/RayCast.cpp
 *
 * This file is part of Leges Motus, a networked, 2D shooter set in zero gravity.
 * 
 * Copyright 2009-2011 Andrew Ayer, Nathan Partlan, Jeffrey Pfau
 * 
 * Leges Motus is free and open source software.  You may redistribute it and/or
 * modify it under the terms of version 2, or (at your option) version 3, of the
 * GNU General Public License (GPL), as published by the Free Software Foundation.
 * 
 * Leges Motus is distributed in the hope that it will be useful, but WITHOUT ANY
 * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
 * PARTICULAR PURPOSE.  See the full text of the GNU General Public License for
 * further detail.
 * 
 * For a full copy of the GNU General Public License, please see the COPYING file
 * in the root of the source code tree.  You may also retrieve a copy from
 * <http://www.gnu.org/licenses/gpl-2.0.txt>, or request a copy by writing to the
 * Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
 * 02111-1307  USA
 * 
 */

#include "RayCast.hpp"
#include "common/misc.hpp"
#include "common/PhysicsObject.hpp"
#include "common/MapObject.hpp"
#include "common/Player.hpp"

using namespace LM;
using namespace std;


RayCast::RayCast() {
	m_physics = NULL;
	m_ignore_collidable = false;
}

RayCast::RayCast(const b2World* physics) {
	m_physics = physics;
	m_ignore_collidable = false;
}

RayCast::~RayCast() {
}

RayCast::RayCastResult& RayCast::get_result() {
	return m_ray_cast;
}

void RayCast::set_physics(const b2World* physics) {
	m_physics = physics;
}

float RayCast::do_ray_cast(const b2Vec2& start_point, float direction, float distance = -1, const PhysicsObject* starting_object, bool ignore_collidable) {
	m_ignore_collidable = ignore_collidable;
	
	if (distance == -1) {
		distance = 20000;
	}
	float end_x = start_point.x + cos(direction) * distance;
	float end_y = start_point.y + sin(direction) * distance;
	
	m_ray_cast.ray_start = start_point;
	m_ray_cast.ray_end = b2Vec2(end_x, end_y);
	m_ray_cast.ray_direction = direction;
	m_ray_cast.start_object = starting_object;
	m_ray_cast.closest_object = NULL;
	m_ray_cast.shortest_dist = -1;
	
	if (m_physics == NULL) {
		return -1;
	}
	
	m_physics->RayCast(this, start_point, m_ray_cast.ray_end);
	
	return m_ray_cast.shortest_dist;
}

float RayCast::cast_at_player(const Point& ray_start, const Player* other_player, float max_radius) {
	b2Vec2 start = b2Vec2(ray_start.x, ray_start.y);

	return cast_at_player(start, other_player, max_radius);
}

float RayCast::cast_at_player(const b2Vec2& ray_start, const Player* other_player, float max_radius) {
	b2Vec2 target_pos = b2Vec2(to_physics(other_player->get_x()), to_physics(other_player->get_y()));
	float x_end = target_pos.x - ray_start.x;
	float y_end = target_pos.y - ray_start.y;
	float wanted_angle = atan2(y_end, x_end);
	
	// Perform the first raycast, at the center of the player.
	do_ray_cast(ray_start, wanted_angle, max_radius);

	b2Body* body = other_player->get_physics_body();
	// XXX: Do we just want to use the first fixture?
	b2Fixture* fixture = &body->GetFixtureList()[0];
	b2Shape* shape = fixture->GetShape();
	if (shape->GetType() == b2Shape::e_polygon) {
		b2PolygonShape* polyshape = static_cast<b2PolygonShape*>(shape);
		int index = 0;
		while (index < polyshape->GetVertexCount()) {
			// Check if we've seen the player
			PhysicsObject* hitobj = m_ray_cast.closest_object;
			if (hitobj != NULL) {
				if (hitobj->get_type() == PhysicsObject::PLAYER && (max_radius == -1 || to_game(m_ray_cast.shortest_dist) < max_radius)) {
					Player* hitplayer = static_cast<Player*>(hitobj);
					if (hitplayer->get_id() == other_player->get_id()) {
						break;
					}
				}
			}
			
			b2Vec2 vertex = polyshape->GetVertex(index);
		
			float x_end = to_physics(other_player->get_x()) + vertex.x * cos(other_player->get_rotation_radians());
			float y_end = to_physics(other_player->get_y()) + vertex.y * sin(other_player->get_rotation_radians());
			float wanted_angle = atan2(y_end, x_end);
			
			do_ray_cast(ray_start, wanted_angle, max_radius);
			index++;
		}
	}
	
	PhysicsObject* hitobj = m_ray_cast.closest_object;
	if (hitobj == NULL) {
		return numeric_limits<float>::max();
	}
	
	if (hitobj->get_type() != PhysicsObject::PLAYER || (max_radius != -1 && to_game(m_ray_cast.shortest_dist) > max_radius)) {
		return numeric_limits<float>::max();
	}
	
	Player* hitplayer = static_cast<Player*>(hitobj);
	if (hitplayer->get_id() != other_player->get_id()) {
		return numeric_limits<float>::max();
	}
	
	return m_ray_cast.shortest_dist;
}

float RayCast::cast_at_obstacle(const Point& ray_start, const MapObject* object, float max_radius, bool ignore_collidable) {
	b2Vec2 start = b2Vec2(ray_start.x, ray_start.y);

	return cast_at_obstacle(start, object, max_radius, ignore_collidable);
}

float RayCast::cast_at_obstacle(const b2Vec2& ray_start, const MapObject* object, float max_radius, bool ignore_collidable) {
	Point object_pos = object->get_position();
	b2Vec2 target_pos = b2Vec2(to_physics(object_pos.x), to_physics(object_pos.y));
	float x_end = target_pos.x - ray_start.x;
	float y_end = target_pos.y - ray_start.y;
	float wanted_angle = atan2(y_end, x_end);
	
	// Perform the first raycast, at the center of the object
	do_ray_cast(ray_start, wanted_angle, max_radius, NULL, ignore_collidable);
	
	// Cast at the other corners
	const b2Shape* shape = object->get_bounding_shape();
	if (shape->GetType() == b2Shape::e_polygon) {
		const b2PolygonShape* polyshape = static_cast<const b2PolygonShape*>(shape);
		int index = 0;
		while (index < polyshape->GetVertexCount()) {
			// Check if we've seen the object
			PhysicsObject* hitobj = m_ray_cast.closest_object;
			if (hitobj != NULL) {
				if (hitobj->get_type() == PhysicsObject::MAP_OBJECT && (max_radius == -1 || to_game(m_ray_cast.shortest_dist) < max_radius)) {
					if (hitobj == object) {
						break;
					}
				}
			}
			
			b2Vec2 vertex = polyshape->GetVertex(index);
		
			float x_end = to_physics(object_pos.x) + vertex.x * cos(to_radians(object->get_rotation()));
			float y_end = to_physics(object_pos.y) + vertex.y * sin(to_radians(object->get_rotation()));
			float wanted_angle = atan2(y_end, x_end);
			
			do_ray_cast(ray_start, wanted_angle, max_radius, NULL, ignore_collidable);
			index++;
		}
	}
	
	PhysicsObject* hitobj = m_ray_cast.closest_object;
	if (hitobj == NULL) {
		return numeric_limits<float>::max();
	}
	
	if (hitobj->get_type() != PhysicsObject::MAP_OBJECT || (max_radius != -1 && to_game(m_ray_cast.shortest_dist) > max_radius) || hitobj != object) {
		return numeric_limits<float>::max();
	}
	
	return to_game(m_ray_cast.shortest_dist);
}

float RayCast::cast_in_vel_dir(const Player* player) {
	b2Vec2 ray_start = b2Vec2(to_physics(player->get_x()), to_physics(player->get_y()));
	float wanted_angle = atan2(player->get_y_vel(), player->get_x_vel());
	
	float found_distance = 0;
	
	// Perform the first raycast, from the center of the player.
	do_ray_cast(ray_start, wanted_angle, -1.0f, player);
	
	found_distance = m_ray_cast.shortest_dist;

	b2Body* body = player->get_physics_body();
	// XXX: Do we just want to use the first fixture?
	b2Fixture* fixture = &body->GetFixtureList()[0];
	b2Shape* shape = fixture->GetShape();
	if (shape->GetType() == b2Shape::e_polygon) {
		b2PolygonShape* polyshape = static_cast<b2PolygonShape*>(shape);
		int index = 0;
		while (index < polyshape->GetVertexCount()) {			
			b2Vec2 vertex = polyshape->GetVertex(index);
		
			float x_start = to_physics(player->get_x()) + vertex.x * cos(player->get_rotation_radians());
			float y_start = to_physics(player->get_y()) + vertex.y * sin(player->get_rotation_radians());
			b2Vec2 start = b2Vec2(x_start, y_start);
			
			do_ray_cast(start, wanted_angle, -1.0f, player);
			
			if (m_ray_cast.shortest_dist < found_distance) {
				found_distance = m_ray_cast.shortest_dist;
			}
			
			index++;
		}
	}
	
	return to_game(found_distance);
}

float32 RayCast::ReportFixture(b2Fixture* fixture, const b2Vec2& point, const b2Vec2& normal, float32 fraction) {
	b2Body* body = fixture->GetBody();
	
	if (body->GetUserData() == NULL) {
		WARN("Body has no user data!");
		return 1;
	}
	
	if (fraction < 0) {
		return 1;
	}
	
	PhysicsObject* hitobj = static_cast<PhysicsObject*>(body->GetUserData());
	Point end = Point(point.x, point.y);
	float dist = (end-Point(m_ray_cast.ray_start.x, m_ray_cast.ray_start.y)).get_magnitude();
	
	if (fixture->IsSensor()) {
		return 1;
	}
	
	if (m_ray_cast.shortest_dist != -1 && dist > m_ray_cast.shortest_dist) {
		return 1;
	}
	
	if (hitobj->get_type() == PhysicsObject::MAP_OBJECT) {
		MapObject* object = static_cast<MapObject*>(hitobj);
		
		if (!m_ignore_collidable && !object->is_collidable()) {
			return 1;
		}
	}
	
	m_ray_cast.shortest_dist = dist;
	
	m_ray_cast.closest_object = hitobj;
	
	m_ray_cast.hit_point = b2Vec2(end.x, end.y);
	
	return 1;
}
 
Follow

Get every new post delivered to your Inbox.