示例#1
0
static int private_add_shape (
	LIPhyShape*           self,
	btConvexShape*        shape,
	const LIMatTransform* transform)
{
	btVector3 p(0.0f, 0.0f, 0.0f);
	btQuaternion r(0.0f, 0.0f, 0.0f, 1.0f);
	btTransform center_of_mass(r, btVector3 (
		self->center_of_mass.x, self->center_of_mass.y, self->center_of_mass.z));

	if (transform != NULL)
	{
		p = btVector3 (transform->position.x, transform->position.y,
		               transform->position.z);
		r = btQuaternion(transform->rotation.x, transform->rotation.y,
		                 transform->rotation.z, transform->rotation.w);
	}
	try
	{
		self->shape->addChildShape (center_of_mass.inverse() * btTransform (r, p), shape);
	}
	catch (...)
	{
		return 0;
	}

	/* Update the bounding box. */
	btVector3 min;
	btVector3 max;
	self->shape->getAabb(btTransform::getIdentity (), min, max);
	self->bounds.min = limat_vector_init (min[0], min[1], min[2]);
	self->bounds.max = limat_vector_init (max[0], max[1], max[2]);

	return 1;
}
示例#2
0
/**
 * \brief Calculates the face normal of the stick when one vertex is overridden.
 * \param self Terrain stick.
 * \param vertex_x Index of the overridden vertex.
 * \param vertex_y Index of the overridden vertex.
 * \param vertex_offset Overridden vertex offset.
 * \param result Return location for the vector.
 */
void liext_terrain_stick_get_normal_override (
	const LIExtTerrainStick* self,
	int                      vertex_x,
	int                      vertex_y,
	float                    vertex_offset,
	LIMatVector*             result)
{
	float offsets[2][2];
	LIMatVector v1;
	LIMatVector v2;
	LIMatVector v3;
	LIMatVector v4;
	LIMatVector n1;
	LIMatVector n2;

	offsets[0][0] = self->vertices[0][0].offset;
	offsets[1][0] = self->vertices[1][0].offset;
	offsets[0][1] = self->vertices[0][1].offset;
	offsets[1][1] = self->vertices[1][1].offset;
	offsets[vertex_x][vertex_y] = vertex_offset;
	v1 = limat_vector_init (1.0f, offsets[1][0] - offsets[0][0], 0.0f);
	v2 = limat_vector_init (0.0f, offsets[0][1] - offsets[0][0], 1.0f);
	v3 = limat_vector_init (-1.0f, offsets[1][0] - offsets[1][1], 0.0f);
	v4 = limat_vector_init (0.0f, offsets[0][1] - offsets[1][1], -1.0f);
	n1 = limat_vector_normalize (limat_vector_cross (v2, v1));
	n2 = limat_vector_normalize (limat_vector_cross (v4, v3));
	*result = limat_vector_multiply (limat_vector_add (n1, n2), 0.5f);
}
示例#3
0
/**
 * \brief Resets the vertices to the flat orientation.
 * \param self Terrain stick.
 */
void liext_terrain_stick_reset_vertices (
	LIExtTerrainStick* self)
{
	memset (self->vertices, 0, sizeof (self->vertices));
	self->vertices[0][0].normal = limat_vector_init (0.0f, 1.0f, 0.0f);
	self->vertices[1][0].normal = limat_vector_init (0.0f, 1.0f, 0.0f);
	self->vertices[0][1].normal = limat_vector_init (0.0f, 1.0f, 0.0f);
	self->vertices[1][1].normal = limat_vector_init (0.0f, 1.0f, 0.0f);
}
示例#4
0
static void Heightmap_new (LIScrArgs* args)
{
	int i;
	int size;
	float spacing;
	float scaling;
	LIExtHeightmap* heightmap;
	LIExtHeightmapModule* module;
	LIImgImage* image;
	LIMatVector position;
	LIScrData* data;

	/* Get arguments. */
	module = liscr_script_get_userdata (args->script, LIEXT_SCRIPT_HEIGHTMAP);
	if (!liscr_args_geti_vector (args, 0, &position))
		position = limat_vector_init (0.0f, 0.0f, 0.0f);
	if (!liscr_args_geti_int (args, 1, &size))
		size = 33;
	else if (size < 0)
		size = 0;
	if (!liscr_args_geti_float (args, 2, &spacing))
		spacing = 1.0f;
	else if (spacing <= 0.0f)
		spacing = 1.0f;
	if (!liscr_args_geti_float (args, 3, &scaling))
		scaling = 1.0f;
	else if (scaling <= 0.0f)
		scaling = 1.0f;
	if (liscr_args_geti_data (args, 4, LIEXT_SCRIPT_IMAGE, &data))
		image = liscr_data_get_data (data);
	else
		image = NULL;

	/* Ensure that the size is valid. */
	for (i = 32 ; i < 65536 ; i *= 2)
	{
		if (size == i + 1)
			break;
	}
	if (size != i + 1)
	{
		lisys_error_set (EINVAL, "invalid heightmap size");
		lisys_error_report ();
		return;
	}

	/* Allocate the heightmap. */
	heightmap = liext_heightmap_new (module, image, &position, size, spacing, scaling);
	if (heightmap == NULL)
		return;

	/* Allocate the userdata. */
	data = liscr_data_new (args->script, args->lua, heightmap, LIEXT_SCRIPT_HEIGHTMAP, liext_heightmap_free);
	if (data == NULL)
	{
		liext_heightmap_free (heightmap);
		return;
	}
	liscr_args_seti_stack (args);
}
示例#5
0
static void Widgets_find_widget (LIScrArgs* args)
{
	int x;
	int y;
	LIExtModule* module;
	LIWdgWidget* widget;
	LIMatVector vector;
	LIScrData* data;

	if (!liscr_args_gets_vector (args, "point", &vector) &&
	    !liscr_args_geti_vector (args, 0, &vector))
	{
		SDL_GetMouseState (&x, &y);
		vector = limat_vector_init (x, y, 0.0f);
	}

	module = liscr_script_get_userdata (args->script, LIEXT_SCRIPT_WIDGETS);
	widget = liwdg_manager_find_widget_by_point (module->widgets, (int) vector.x, (int) vector.y);
	if (widget == NULL)
		return;
	data = liwdg_widget_get_script (widget);
	if (data == NULL)
		return;
	liscr_args_seti_data (args, data);
}
示例#6
0
static void Widget_get_offset (LIScrArgs* args)
{
	int x;
	int y;
	LIMatVector v;

	liwdg_widget_get_offset (args->self, &x, &y);
	v = limat_vector_init (x, y, 0.0f);
	liscr_args_seti_vector (args, &v);
}
示例#7
0
/**
 * \brief Calculates the face normal of the stick.
 * \param self Terrain stick.
 * \param result Return location for the vector.
 */
void liext_terrain_stick_get_normal (
	const LIExtTerrainStick* self,
	LIMatVector*             result)
{
	LIMatVector v1;
	LIMatVector v2;
	LIMatVector v3;
	LIMatVector v4;
	LIMatVector n1;
	LIMatVector n2;

	v1 = limat_vector_init (1.0f, self->vertices[1][0].offset - self->vertices[0][0].offset, 0.0f);
	v2 = limat_vector_init (0.0f, self->vertices[0][1].offset - self->vertices[0][0].offset, 1.0f);
	v3 = limat_vector_init (-1.0f, self->vertices[0][1].offset - self->vertices[1][1].offset, 0.0f);
	v4 = limat_vector_init (0.0f, self->vertices[1][0].offset - self->vertices[1][1].offset, -1.0f);
	n1 = limat_vector_normalize (limat_vector_cross (v2, v1));
	n2 = limat_vector_normalize (limat_vector_cross (v4, v3));
	*result = limat_vector_normalize (limat_vector_add (n1, n2));
}
示例#8
0
/**
 * \brief Creates a new terrain stick.
 * \return Terrain stick, or NULL.
 */
LIExtTerrainStick* liext_terrain_stick_new (
	int   material,
	float height)
{
	LIExtTerrainStick* self;

	/* Allocate self. */
	self = lisys_calloc (1, sizeof (LIExtTerrainStick));
	if (self == NULL)
		return NULL;
	self->material = material;
	self->height = height;
	self->vertices[0][0].normal = limat_vector_init (0.0f, 1.0f, 0.0f);
	self->vertices[1][0].normal = limat_vector_init (0.0f, 1.0f, 0.0f);
	self->vertices[0][1].normal = limat_vector_init (0.0f, 1.0f, 0.0f);
	self->vertices[1][1].normal = limat_vector_init (0.0f, 1.0f, 0.0f);

	return self;
}
示例#9
0
static void Terrain_build_chunk_model (LIScrArgs* args)
{
	int grid_x;
	int grid_z;
	LIExtTerrain* self;
	LIExtTerrainModule* module;
	LIMatVector offset;
	LIMdlModel* model;
	LIScrData* data;

	/* Get the arguments. */
	self = args->self;
	module = liscr_script_get_userdata (args->script, LIEXT_SCRIPT_TERRAIN);
	if (!liscr_args_geti_int (args, 0, &grid_x) || grid_x < 0)
		return;
	if (!liscr_args_geti_int (args, 1, &grid_z) || grid_z < 0)
		return;
	if (!liscr_args_geti_vector (args, 2, &offset))
		offset = limat_vector_init (grid_x * self->grid_size, 0.0f, grid_z * self->grid_size);

	/* Build the model. */
	model = liext_terrain_build_chunk_model (self, grid_x, grid_z, &offset);
	if (model == NULL)
		return;

	/* Copy the model. */
	model = limdl_model_new_copy (model, 0);
	if (model == NULL)
		return;

	/* Allocate the unique ID. */
	if (!limdl_manager_add_model (module->program->models, model))
	{
		limdl_model_free (model);
		return;
	}

	/* Allocate the userdata. */
	data = liscr_data_new (args->script, args->lua, model, LISCR_SCRIPT_MODEL, limdl_manager_free_model);
	if (data == NULL)
	{
		limdl_model_free (model);
		return;
	}
	liscr_args_seti_stack (args);
}
示例#10
0
void liwdg_widget_set_allocation (
	LIWdgWidget* self,
	int          x,
	int          y,
	int          w,
	int          h)
{
	LIMatVector pos;

	if (self->allocation.x != x ||
	    self->allocation.y != y ||
	    self->allocation.width != w ||
	    self->allocation.height != h)
	{
		self->allocation.x = x;
		self->allocation.y = y;
		self->allocation.width = w;
		self->allocation.height = h;
		pos = limat_vector_init (x, y, 0.0f);
		liren_render_overlay_set_position (self->manager->render, self->overlay, &pos);
		private_rebuild (self, PRIVATE_REBUILD_REQUEST | PRIVATE_REBUILD_HORZ | PRIVATE_REBUILD_VERT | PRIVATE_REBUILD_CHILDREN);
	}
}
示例#11
0
static void Voxel_copy_region (LIScrArgs* args)
{
	int i;
	int length;
	int sector;
	int offset[3];
	LIArcPacket* packet;
	LIScrData* data;
	LIExtModule* module;
	LIMatVector point;
	LIMatVector size;
	LIVoxVoxel* result;

	/* Get region offset and size. */
	module = liscr_script_get_userdata (args->script, LIEXT_SCRIPT_VOXEL);
	if (liscr_args_gets_int (args, "sector", &sector))
	{
		lialg_sectors_index_to_offset (module->program->sectors, sector,
			offset + 0, offset + 1, offset + 2);
		point = limat_vector_init (offset[0], offset[1], offset[2]);
		point = limat_vector_multiply (point, module->voxels->tiles_per_line);
		size.x = size.y = size.z = module->voxels->tiles_per_line;
		length = module->voxels->tiles_per_sector;
	}
	else if (liscr_args_gets_vector (args, "point", &point) &&
	         liscr_args_gets_vector (args, "size", &size))
	{
		if (point.x < 0.0f || point.y < 0.0f || point.z < 0.0f ||
		    size.x < 1.0f || size.y < 1.0f || size.z < 1.0f)
			return;
		length = (int) size.x * (int) size.y * (int) size.z;
	}
	else
		return;

	/* Read voxel data. */
	result = lisys_calloc (length, sizeof (LIVoxVoxel));
	if (result == NULL)
		return;
	livox_manager_copy_voxels (module->voxels,
		(int) point.x, (int) point.y, (int) point.z,
		(int) size.x, (int) size.y, (int) size.z, result);

	/* Create a packet writer. */
	packet = liarc_packet_new_writable (0);
	if (packet == NULL)
	{
		lisys_free (result);
		return;
	}

	/* Write the dimensions. */
	if (!liarc_writer_append_uint32 (packet->writer, (int) size.x) ||
		!liarc_writer_append_uint32 (packet->writer, (int) size.y) ||
		!liarc_writer_append_uint32 (packet->writer, (int) size.z))
	{
		lisys_free (result);
		return;
	}

	/* Write voxel data. */
	for (i = 0 ; i < length ; i++)
	{
		if (!livox_voxel_write (result + i, packet->writer))
		{
			lisys_free (result);
			return;
		}
	}
	lisys_free (result);

	/* Return data. */
	data = liscr_data_new (args->script, args->lua, packet, LISCR_SCRIPT_PACKET, liarc_packet_free);
	if (data == NULL)
	{
		liarc_packet_free (packet);
		return;
	}
	liscr_args_seti_stack (args);
}
示例#12
0
static void Voxel_find_blocks (LIScrArgs* args)
{
	int sx;
	int sy;
	int sz;
	int index;
	int line;
	int stamp;
	float radius;
	LIAlgRange sectors;
	LIAlgRange blocks;
	LIAlgRange range;
	LIAlgRangeIter iter0;
	LIAlgRangeIter iter1;
	LIExtModule* module;
	LIMatVector min;
	LIMatVector max;
	LIMatVector point;
	LIMatVector size;
	LIVoxBlock* block;
	LIVoxSector* sector;

	/* Initialize arguments. */
	if (!liscr_args_gets_vector (args, "point", &point))
		return;
	liscr_args_gets_float (args, "radius", &radius);
	liscr_args_set_output (args, LISCR_ARGS_OUTPUT_TABLE_FORCE);
	module = liscr_script_get_userdata (args->script, LIEXT_SCRIPT_VOXEL);
	line = module->voxels->blocks_per_line * module->voxels->sectors->count;

	/* Calculate sight volume. */
	size = limat_vector_init (radius, radius, radius);
	min = limat_vector_subtract (point, size);
	max = limat_vector_add (point, size);
	sectors = lialg_range_new_from_aabb (&min, &max, module->voxels->sectors->width);
	sectors = lialg_range_clamp (sectors, 0, module->voxels->sectors->count - 1);
	blocks = lialg_range_new_from_aabb (&min, &max, module->voxels->sectors->width / module->voxels->blocks_per_line);
	blocks = lialg_range_clamp (blocks, 0, module->voxels->blocks_per_line * module->voxels->sectors->count - 1);

	/* Loop through visible sectors. */
	LIALG_RANGE_FOREACH (iter0, sectors)
	{
		/* Get voxel sector. */
		sector = lialg_sectors_data_index (module->voxels->sectors, LIALG_SECTORS_CONTENT_VOXEL, iter0.index, 0);
		if (sector == NULL)
			continue;

		/* Calculate visible block range. */
		livox_sector_get_offset (sector, &sx, &sy, &sz);
		sx *= module->voxels->blocks_per_line;
		sy *= module->voxels->blocks_per_line;
		sz *= module->voxels->blocks_per_line;
		range.min = 0;
		range.max = module->voxels->blocks_per_line;
		range.minx = LIMAT_MAX (blocks.minx - sx, 0);
		range.miny = LIMAT_MAX (blocks.miny - sy, 0);
		range.minz = LIMAT_MAX (blocks.minz - sz, 0);
		range.maxx = LIMAT_MIN (blocks.maxx - sx, module->voxels->blocks_per_line - 1);
		range.maxy = LIMAT_MIN (blocks.maxy - sy, module->voxels->blocks_per_line - 1);
		range.maxz = LIMAT_MIN (blocks.maxz - sz, module->voxels->blocks_per_line - 1);

		/* Loop through visible blocks. */
		LIALG_RANGE_FOREACH (iter1, range)
		{
			block = livox_sector_get_block (sector, iter1.x, iter1.y, iter1.z);
			stamp = livox_block_get_stamp (block);
			index = (sx + iter1.x) + (sy + iter1.y) * line + (sz + iter1.z) * line * line;
			liscr_args_setf_float (args, index, stamp);
		}
示例#13
0
/**
 * \brief Casts a sphere against the stick and returns the hit fraction.
 *
 * FIXME: Doesn't work yet.
 *
 * \param self Terrain stick.
 * \param bot00 Bottom surface Y offset.
 * \param bot10 Bottom surface Y offset.
 * \param bot01 Bottom surface Y offset.
 * \param bot11 Bottom surface Y offset.
 * \param top00 Top surface Y offset.
 * \param top10 Top surface Y offset.
 * \param top01 Top surface Y offset.
 * \param top11 Top surface Y offset.
 * \param sphere_rel_cast_start Cast start position of the sphere, in grid units relative to the column origin.
 * \param sphere_rel_cast_end Cast end position of the sphere, in grid units relative to the column origin.
 * \param sphere_radius Sphere radius, in grid units.
 * \param result Return location for the hit fraction.
 * \return Nonzero if hit. Zero otherwise.
 */
int liext_terrain_stick_cast_sphere (
	const LIExtTerrainStick* self,
	float                    bot00,
	float                    bot10,
	float                    bot01,
	float                    bot11,
	float                    top00,
	float                    top10,
	float                    top01,
	float                    top11,
	const LIMatVector*       sphere_rel_cast_start,
	const LIMatVector*       sphere_rel_cast_end,
	float                    sphere_radius,
	LIExtTerrainCollision*   result)
{
	float min;
	float max;
	LIExtTerrainCollision best;
	LIExtTerrainCollision frac;
	LIMatVector v;
	LIMatVector vtx[3];
	LIMatVector point;
	LIMatPlane plane;

	frac.x = 0;
	frac.z = 0;
	best.fraction = LIMAT_INFINITE;
	v = limat_vector_subtract (*sphere_rel_cast_end, *sphere_rel_cast_start);

	/* Left. */
	limat_plane_init (&plane, -1.0f, 0.0f, 0.0f, 0.0f);
	frac.fraction = limat_plane_cast_sphere (&plane, sphere_rel_cast_start, sphere_rel_cast_end, sphere_radius);
	if (frac.fraction >= 0.0f && best.fraction > frac.fraction)
	{
		point = limat_vector_add (*sphere_rel_cast_start, limat_vector_multiply (v, frac.fraction));
		min = limat_mix (bot00, bot01, point.z);
		max = limat_mix (top00, top01, point.z);
		if (point.z >= 0 && point.z <= 1.0f && min <= point.y && point.y <= max)
		{
			/* Direct face hit. */
			best = frac;
			best.normal = limat_vector_init (-1.0f, 0.0f, 0.0f);
			best.point = limat_vector_init (0.0f, point.y, point.z);
		}
		else
		{
			/* Potential edge hit. */
			point.z = LIMAT_CLAMP (point.z, 0.0f, 1.0f);
			min = limat_mix (bot00, bot01, point.z);
			max = limat_mix (top00, top01, point.z);
			point.y = LIMAT_CLAMP (point.y, min, max);
			if (limat_intersect_point_cast_sphere (&point, sphere_rel_cast_start, sphere_rel_cast_end, sphere_radius, &frac.fraction))
			{
				if (frac.fraction >= 0.0f && best.fraction > frac.fraction)
				{
					best = frac;
					best.normal = limat_vector_init (-1.0f, 0.0f, 0.0f);
					best.point = limat_vector_init (0.0f, point.y, point.z);
				}
			}
		}
	}

	/* Right. */
	limat_plane_init (&plane, 1.0f, 0.0f, 0.0f, 1.0f);
	frac.fraction = limat_plane_cast_sphere (&plane, sphere_rel_cast_start, sphere_rel_cast_end, sphere_radius);
	if (frac.fraction >= 0.0f && best.fraction > frac.fraction)
	{
		point = limat_vector_add (*sphere_rel_cast_start, limat_vector_multiply (v, frac.fraction));
		min = limat_mix (bot10, bot11, point.z);
		max = limat_mix (top10, top11, point.z);
		if (point.z >= 0 && point.z <= 1.0f && min <= point.y && point.y <= max)
		{
			/* Direct face hit. */
			best = frac;
			best.normal = limat_vector_init (1.0f, 0.0f, 0.0f);
			best.point = limat_vector_init (1.0f, point.y, point.z);
		}
		else
		{
			/* Potential edge hit. */
			point.z = LIMAT_CLAMP (point.z, 0.0f, 1.0f);
			min = limat_mix (bot10, bot11, point.z);
			max = limat_mix (top10, top11, point.z);
			point.y = LIMAT_CLAMP (point.y, min, max);
			if (limat_intersect_point_cast_sphere (&point, sphere_rel_cast_start, sphere_rel_cast_end, sphere_radius, &frac.fraction))
			{
				if (frac.fraction >= 0.0f && best.fraction > frac.fraction)
				{
					best = frac;
					best.normal = limat_vector_init (1.0f, 0.0f, 0.0f);
					best.point = limat_vector_init (1.0f, point.y, point.z);
				}
			}
		}
	}

	/* Front. */
	limat_plane_init (&plane, 0.0f, 0.0f, -1.0f, 0.0f);
	frac.fraction = limat_plane_cast_sphere (&plane, sphere_rel_cast_start, sphere_rel_cast_end, sphere_radius);
	if (frac.fraction >= 0.0f && best.fraction > frac.fraction)
	{
		point = limat_vector_add (*sphere_rel_cast_start, limat_vector_multiply (v, frac.fraction));
		min = limat_mix (bot00, bot10, point.z);
		max = limat_mix (top00, top10, point.z);
		if (point.x >= 0 && point.x <= 1.0f && min <= point.y && point.y <= max)
		{
			/* Direct face hit. */
			best = frac;
			best.normal = limat_vector_init (0.0f, 0.0f, -1.0f);
			best.point = limat_vector_init (point.x, point.y, 0.0f);
		}
		else
		{
			/* Potential edge hit. */
			point.x = LIMAT_CLAMP (point.x, 0.0f, 1.0f);
			min = limat_mix (bot00, bot10, point.z);
			max = limat_mix (top00, top10, point.z);
			point.y = LIMAT_CLAMP (point.y, min, max);
			if (limat_intersect_point_cast_sphere (&point, sphere_rel_cast_start, sphere_rel_cast_end, sphere_radius, &frac.fraction))
			{
				if (frac.fraction >= 0.0f && best.fraction > frac.fraction)
				{
					best = frac;
					best.normal = limat_vector_init (0.0f, 0.0f, -1.0f);
					best.point = limat_vector_init (point.x, point.y, 0.0f);
				}
			}
		}
	}

	/* Back. */
	limat_plane_init (&plane, 0.0f, 0.0f, 1.0f, 1.0f);
	frac.fraction = limat_plane_cast_sphere (&plane, sphere_rel_cast_start, sphere_rel_cast_end, sphere_radius);
	if (frac.fraction >= 0.0f && best.fraction > frac.fraction)
	{
		point = limat_vector_add (*sphere_rel_cast_start, limat_vector_multiply (v, frac.fraction));
		min = limat_mix (bot01, bot11, point.z);
		max = limat_mix (top01, top11, point.z);
		if (point.x >= 0 && point.x <= 1.0f && min <= point.y && point.y <= max)
		{
			/* Direct face hit. */
			best = frac;
			best.normal = limat_vector_init (0.0f, 0.0f, 1.0f);
			best.point = limat_vector_init (point.x, point.y, 1.0f);
		}
		else
		{
			/* Potential edge hit. */
			point.x = LIMAT_CLAMP (point.x, 0.0f, 1.0f);
			min = limat_mix (bot01, bot11, point.z);
			max = limat_mix (top01, top11, point.z);
			point.y = LIMAT_CLAMP (point.y, min, max);
			if (limat_intersect_point_cast_sphere (&point, sphere_rel_cast_start, sphere_rel_cast_end, sphere_radius, &frac.fraction))
			{
				if (frac.fraction >= 0.0f && best.fraction > frac.fraction)
				{
					best = frac;
					best.normal = limat_vector_init (0.0f, 0.0f, 1.0f);
					best.point = limat_vector_init (point.x, point.y, 1.0f);
				}
			}
		}
	}

	/* Bottom. */
	vtx[2] = limat_vector_init (0.0f, bot00, 0.0f);
	vtx[1] = limat_vector_init (0.0f, bot01, 1.0f);
	vtx[0] = limat_vector_init (1.0f, bot11, 1.0f);
	limat_plane_init_from_points (&plane, vtx + 0, vtx + 1, vtx + 2);
	lisys_assert (plane.y < 0.0f);
	frac.fraction = limat_plane_cast_sphere (&plane, sphere_rel_cast_start, sphere_rel_cast_end, sphere_radius);
	if (frac.fraction >= 0.0f && best.fraction > frac.fraction)
	{
		point = limat_vector_add (*sphere_rel_cast_start, limat_vector_multiply (v, frac.fraction));
		if (point.x >= 0.0f && point.z >= 0.0f && point.z <= 1.0f && point.z >= point.x)
		{
			/* Direct face hit. */
			best = frac;
			limat_plane_get_normal (&plane, &best.normal);
			best.point = limat_vector_add (point, limat_vector_multiply (best.normal, -sphere_radius));
		}
		else
		{
			/* TODO: Potential edge hit. */
		}
	}
	vtx[2] = limat_vector_init (0.0f, bot00, 0.0f);
	vtx[1] = limat_vector_init (1.0f, bot11, 1.0f);
	vtx[0] = limat_vector_init (1.0f, bot10, 0.0f);
	limat_plane_init_from_points (&plane, vtx + 0, vtx + 1, vtx + 2);
	lisys_assert (plane.y < 0.0f);
	frac.fraction = limat_plane_cast_sphere (&plane, sphere_rel_cast_start, sphere_rel_cast_end, sphere_radius);
	if (frac.fraction >= 0.0f && best.fraction > frac.fraction)
	{
		point = limat_vector_add (*sphere_rel_cast_start, limat_vector_multiply (v, frac.fraction));
		if (point.x >= 0.0f && point.z >= 0.0f && point.x <= 1.0f && point.x >= point.z)
		{
			/* Direct face hit. */
			best = frac;
			limat_plane_get_normal (&plane, &best.normal);
			best.point = limat_vector_add (point, limat_vector_multiply (best.normal, -sphere_radius));
		}
		else
		{
			/* TODO: Potential edge hit. */
		}
	}

	/* Top. */
	vtx[2] = limat_vector_init (0.0f, top00, 0.0f);
	vtx[1] = limat_vector_init (1.0f, top10, 0.0f);
	vtx[0] = limat_vector_init (1.0f, top11, 1.0f);
	limat_plane_init_from_points (&plane, vtx + 0, vtx + 1, vtx + 2);
	frac.fraction = limat_plane_cast_sphere (&plane, sphere_rel_cast_start, sphere_rel_cast_end, sphere_radius);
	lisys_assert (plane.y > 0.0f);
	if (frac.fraction >= 0.0f && best.fraction > frac.fraction)
	{
		point = limat_vector_add (*sphere_rel_cast_start, limat_vector_multiply (v, frac.fraction));
		if (point.x >= 0.0f && point.z >= 0.0f && point.x <= 1.0f && point.x >= point.z)
		{
			/* Direct face hit. */
			best = frac;
			limat_plane_get_normal (&plane, &best.normal);
			best.point = limat_vector_add (point, limat_vector_multiply (best.normal, -sphere_radius));
		}
		else
		{
			/* TODO: Potential edge hit. */
		}
	}
	vtx[2] = limat_vector_init (0.0f, top00, 0.0f);
	vtx[1] = limat_vector_init (1.0f, top11, 1.0f);
	vtx[0] = limat_vector_init (0.0f, top10, 1.0f);
	limat_plane_init_from_points (&plane, vtx + 0, vtx + 1, vtx + 2);
	lisys_assert (plane.y > 0.0f);
	frac.fraction = limat_plane_cast_sphere (&plane, sphere_rel_cast_start, sphere_rel_cast_end, sphere_radius);
	if (frac.fraction >= 0.0f && best.fraction > frac.fraction)
	{
		point = limat_vector_add (*sphere_rel_cast_start, limat_vector_multiply (v, frac.fraction));
		if (point.x >= 0.0f && point.z >= 0.0f && point.z <= 1.0f && point.z >= point.x)
		{
			/* Direct face hit. */
			best = frac;
			limat_plane_get_normal (&plane, &best.normal);
			best.point = limat_vector_add (point, limat_vector_multiply (best.normal, -sphere_radius));
		}
		else
		{
			/* TODO: Potential edge hit. */
		}
	}

	/* Check whether a collision occurred. */
	if (best.fraction > 1.0f || best.fraction == LIMAT_INFINITE)
		return 0;
	*result = best;
	return 1;
}