/* ScummVM - Graphic Adventure Engine
 *
 * ScummVM is the legal property of its developers, whose names
 * are too numerous to list here. Please refer to the COPYRIGHT
 * file distributed with this source distribution.
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program 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
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 *
 */

/*
 * This file is based on WME.
 * http://dead-code.org/redir.php?target=wme
 * Copyright (c) 2003-2013 Jan Nedoma and contributors
 */

#include "common/math.h"
#include "common/util.h"
#include "engines/wintermute/ad/ad_block.h"
#include "engines/wintermute/ad/ad_game.h"
#include "engines/wintermute/ad/ad_generic.h"
#include "engines/wintermute/ad/ad_geom_ext.h"
#include "engines/wintermute/ad/ad_geom_ext_node.h"
#include "engines/wintermute/ad/ad_path3d.h"
#include "engines/wintermute/ad/ad_path_point3d.h"
#include "engines/wintermute/ad/ad_scene.h"
#include "engines/wintermute/ad/ad_scene_geometry.h"
#include "engines/wintermute/ad/ad_walkplane.h"
#include "engines/wintermute/ad/ad_waypoint_group3d.h"
#include "engines/wintermute/base/base_file_manager.h"
#include "engines/wintermute/base/base_game.h"
#include "engines/wintermute/base/base_sprite.h"
#include "engines/wintermute/base/file/base_file.h"
#include "engines/wintermute/base/gfx/opengl/base_render_opengl3d.h"
#include "engines/wintermute/base/gfx/3ds/camera3d.h"
#include "engines/wintermute/base/gfx/3ds/light3d.h"
#include "engines/wintermute/base/gfx/3ds/loader3ds.h"
#include "engines/wintermute/base/gfx/3ds/mesh3ds.h"
#include "engines/wintermute/math/math_util.h"
#include "engines/wintermute/system/sys_class_registry.h"
#include "engines/wintermute/wintermute.h"
#include "math/glmath.h"

namespace Wintermute {

IMPLEMENT_PERSISTENT(AdSceneGeometry, false)

//////////////////////////////////////////////////////////////////////////
AdSceneGeometry::AdSceneGeometry(BaseGame *gameRef) : BaseObject(gameRef) {
	_activeCamera = _activeLight = -1;
	_viewMatrix.setToIdentity();
	//m_WaypointHeight = 5.0f;
	//m_WaypointHeight = 1.0f;
	_waypointHeight = 10.0f;
	_wptMarker = NULL;

	_PFReady = true;
	_PFTargetPath = NULL;
	_PFMaxTime = 15;
	_PFRerun = false;

	_PFSource = _PFTarget = _PFAlternateTarget = Math::Vector3d(0, 0, 0);
	_PFAlternateDist = FLT_MAX;

	_drawingViewport.setRect(0, 0, 0, 0);

	_lastWorldMat.setToIdentity();
	_lastViewMat.setToIdentity();
	_lastProjMat.setToIdentity();

	_lastOffsetX = _lastOffsetY = 0;
	_lastScrollX = _lastScrollY = 0;

	_lastValuesInitialized = false;
	_maxLightsWarning = false;
}

//////////////////////////////////////////////////////////////////////////
AdSceneGeometry::~AdSceneGeometry() {
	cleanup();
	delete _wptMarker;
}

//////////////////////////////////////////////////////////////////////////
void AdSceneGeometry::cleanup() {
	uint i;

	for (i = 0; i < _planes.size(); i++) {
		delete _planes[i];
	}
	_planes.clear();

	for (i = 0; i < _blocks.size(); i++) {
		delete _blocks[i];
	}
	_blocks.clear();

	for (i = 0; i < _generics.size(); i++) {
		delete _generics[i];
	}
	_generics.clear();

	for (i = 0; i < _waypointGroups.size(); i++) {
		delete _waypointGroups[i];
	}
	_waypointGroups.clear();

	for (i = 0; i < _cameras.size(); i++) {
		//		CBRenderD3D* _renderer = (CBRenderD3D*)_gameRef->_renderer;
		//		if(m_Renderer->m_Camera == _cameras[i]) m_Renderer->m_Camera = NULL;

		delete _cameras[i];
	}
	_cameras.clear();

	for (i = 0; i < _lights.size(); i++) {
		delete _lights[i];
	}
	_lights.clear();

	_activeCamera = _activeLight = -1;
	_viewMatrix.setToIdentity();

	for (i = 0; i < _PFPath.size(); i++) {
		delete _PFPath[i];
	}
	_PFPath.clear();

	_PFTargetPath = NULL;
}

//////////////////////////////////////////////////////////////////////////
AdGeomExt *AdSceneGeometry::getGeometryExtension(char *filename) {
	AdGeomExt *ret = new AdGeomExt(_gameRef);

	bool loadOK = false;
	if (BaseFileManager::getEngineInstance()->openFile(filename) != nullptr) {
		loadOK = ret->loadFile(filename);
	}

	// no ext file found, just use the defaults
	if (!loadOK) {
		ret->addStandardNodes();
	}

	return ret;
}

//////////////////////////////////////////////////////////////////////////
bool AdSceneGeometry::loadFile(const char *filename) {
	cleanup();

	// load waypoint graphics from resources
	if (!_wptMarker) {
		_wptMarker = new BaseSprite(_gameRef);
		if (_wptMarker) {
			if (!_wptMarker->loadFile("wpt.sprite")) {
				delete _wptMarker;
				_wptMarker = NULL;
			}
		}
	}

	Common::String filenameTmp(filename);

	if (!filenameTmp.hasSuffix(".3ds") && !filenameTmp.hasSuffix(".3DS")) {
		_gameRef->LOG(0, "Error: no suitable loader found for file '%s'", filename);
		return false;
	}

	filenameTmp.replace(filenameTmp.size() - 3, 3, "geometry", 0, 8);
	AdGeomExt *geomExt = getGeometryExtension(filenameTmp.begin());

	// Light3D, AdBlock, AdGeneric and AdWalkplane all inherit from BaseScriptable
	// the latter one is overriding the new operator such that instances are registered
	// in the system class registry
	// for the most part, the subclasses of BaseScriptable override the new operator themselves,
	// but here this is not the case. So these instances are not supposed to be registered
	// and doing so would create faulty savegames. The persistence of them will be handled by AdSceneGeometry
	SystemClassRegistry::getInstance()->_disabled = true;

	BaseArray<Mesh3DS *> meshes;
	BaseArray<Common::String> meshNames;

	if (!load3DSFile(filename, meshes, meshNames, _lights, _cameras, _gameRef)) {
		delete geomExt;
		return false;
	}

	uint i;

	// load meshes
	for (i = 0; i < meshes.size(); i++) {
		AdGeomExtNode *ExtNode = geomExt->matchName(meshNames[i].c_str());

		if (!ExtNode) {
			continue;
		}

		switch (ExtNode->_type) {
		case GEOM_WALKPLANE: {
			AdWalkplane *plane = new AdWalkplane(_gameRef);
			plane->setName(meshNames[i].c_str());
			plane->_mesh = meshes[i];
			// TODO: These constants are endianness dependent
			plane->_mesh->fillVertexBuffer(0xFF0000FF);
			plane->_receiveShadows = ExtNode->_receiveShadows;
			_planes.add(plane);
			} break;

		case GEOM_BLOCKED: {
			AdBlock *block = new AdBlock(_gameRef);
			block->setName(meshNames[i].c_str());
			block->_mesh = meshes[i];
			block->_mesh->fillVertexBuffer(0xFFFF0000);
			block->_receiveShadows = ExtNode->_receiveShadows;
			_blocks.add(block);
			} break;

		case GEOM_WAYPOINT: {
			Mesh3DS *mesh = meshes[i];
			// TODO: groups
			if (_waypointGroups.size() == 0) {
				_waypointGroups.add(new AdWaypointGroup3D(_gameRef));
			}
			_waypointGroups[0]->addFromMesh(mesh);
			delete mesh;
			} break;

		case GEOM_GENERIC: {
			AdGeneric *generic = new AdGeneric(_gameRef);
			generic->setName(meshNames[i].c_str());
			generic->_mesh = meshes[i];
			generic->_mesh->fillVertexBuffer(0xFF00FF00);
			generic->_receiveShadows = ExtNode->_receiveShadows;
			_generics.add(generic);
			} break;
		}
	}

	SystemClassRegistry::getInstance()->_disabled = false;

	if (_cameras.size() > 0) {
		setActiveCamera(0, -1.0f, -1.0f, -1.0f);
	}
	createLights();

	if (_lights.size() > 0) {
		uint activeLight = 0;
		setActiveLight(activeLight);
	}

	delete geomExt;

	// drop waypoints to the ground
	dropWaypoints();

	if (getFilename() != filename) {
		setFilename(filename);
	}

	return true;
}

//////////////////////////////////////////////////////////////////////////
bool AdSceneGeometry::dropWaypoints() {
	for (uint i = 0; i < _waypointGroups.size(); i++) {
		for (uint j = 0; j < _waypointGroups[i]->_points.size(); j++) {
			Math::Vector3d *point = _waypointGroups[i]->_points[j];
			point->y() = getHeightAt(*point) + _waypointHeight;
		}
	}
	return true;
}

//////////////////////////////////////////////////////////////////////////
bool AdSceneGeometry::setActiveCamera(int camera, float fov, float nearClipPlane, float farClipPlane) {
	if (camera < 0 || static_cast<uint>(camera) >= _cameras.size()) {
		_gameRef->LOG(0, "Warning: Camera %d is out of bounds.", camera);
		return false;
	} else {
		_activeCamera = camera;

		if (fov >= 0.0f) {
			_cameras[camera]->_fov = fov;
		} else {
			_cameras[camera]->_fov = _cameras[camera]->_originalFOV;
		}

		_cameras[camera]->_nearClipPlane = nearClipPlane;
		_cameras[camera]->_farClipPlane = farClipPlane;

		_cameras[camera]->getViewMatrix(&_viewMatrix);
		return true;
	}
}

//////////////////////////////////////////////////////////////////////////
bool AdSceneGeometry::setActiveCamera(const char *camera, float fov, float nearClipPlane, float farClipPlane) {
	for (uint i = 0; i < _cameras.size(); i++) {
		if (scumm_stricmp(_cameras[i]->getName(), camera) == 0)
			return setActiveCamera(i, fov, nearClipPlane, farClipPlane);
	}

	_gameRef->LOG(0, "Warning: Camera '%s' not found.", camera);
	return false;
}

//////////////////////////////////////////////////////////////////////////
Camera3D *AdSceneGeometry::getActiveCamera() {
	if (_activeCamera >= 0 && static_cast<uint>(_activeCamera) < _cameras.size()) {
		return _cameras[_activeCamera];
	} else {
		return nullptr;
	}
}

//////////////////////////////////////////////////////////////////////////
bool AdSceneGeometry::setActiveLight(int light) {
	if (light < 0 || static_cast<uint>(light) >= _lights.size()) {
		_gameRef->LOG(0, "Warning: Light %d is out of bounds.", light);
		return false;
	} else {
		_activeLight = light;
		return true;
	}
}

//////////////////////////////////////////////////////////////////////////
bool AdSceneGeometry::setActiveLight(char *light) {
	for (uint i = 0; i < _lights.size(); i++) {
		if (scumm_stricmp(_lights[i]->getName(), light) == 0) {
			return setActiveLight(i);
		}
	}

	_gameRef->LOG(0, "Warning: Light '%s' not found.", light);
	return false;
}

//////////////////////////////////////////////////////////////////////////
Math::Matrix4 *AdSceneGeometry::getViewMatrix() {
	return &_viewMatrix;
}

//////////////////////////////////////////////////////////////////////////
bool AdSceneGeometry::storeDrawingParams() {
	// implement this later
	//	CBRenderD3D* m_Renderer = (CBRenderD3D*)_gameRef->m_Renderer;

	//  // store values
	//	m_Renderer->m_Device->GetViewport(&m_DrawingViewport);

	//	m_Renderer->m_Device->GetTransform(D3DTS_WORLD, &m_LastWorldMat);
	//	m_Renderer->m_Device->GetTransform(D3DTS_VIEW, &m_LastViewMat);
	//	m_Renderer->m_Device->GetTransform(D3DTS_PROJECTION, &m_LastProjMat);

	warning("AdSceneGeometry::storeDrawingParams not yet implemented");

	AdScene *scene = ((AdGame *)_gameRef)->_scene;
	if (scene) {
		_lastScrollX = scene->getOffsetLeft();
		_lastScrollY = scene->getOffsetTop();
	} else {
		_lastScrollX = 0;
		_lastScrollY = 0;
	}

	Rect32 rc;
	_gameRef->getCurrentViewportRect(&rc);
//	float width = (float)rc.right - (float)rc.left;
//	float height = (float)rc.bottom - (float)rc.top;

	// margins
	//	int mleft = rc.left;
	//	int mright = m_Renderer->m_Width - Width - rc.left;
	//	int mtop = rc.top;
	//	int mbottom = m_Renderer->m_Height - Height - rc.top;

	//	m_LastOffsetX = _gameRef->_offsetX + (mleft - mright)/2;
	//	m_LastOffsetY = _gameRef->_offsetY + (mtop - mbottom)/2;

	_lastValuesInitialized = true;

	return true;
}

//////////////////////////////////////////////////////////////////////////
bool AdSceneGeometry::render(bool render) {
	//	store values
	//	StoreDrawingParams();
	if (render) {
		_gameRef->_renderer3D->renderSceneGeometry(_planes, _blocks, _generics, _lights, getActiveCamera());
	}

	return true;
}

//////////////////////////////////////////////////////////////////////////
bool AdSceneGeometry::renderShadowGeometry() {
	_gameRef->_renderer3D->renderShadowGeometry(_planes, _blocks, _generics, getActiveCamera());
	return true;
}

//////////////////////////////////////////////////////////////////////////
float AdSceneGeometry::getHeightAt(Math::Vector3d pos, float tolerance, bool *intFound) {
	float ret = pos.y();
	Math::Vector3d intersection;
	Math::Vector3d dir = Math::Vector3d(0, -1, 0);

	pos.y() += tolerance;

	bool intFoundTmp = false;

	for (uint32 i = 0; i < _planes.size(); i++) {
		for (int j = 0; j < _planes[i]->_mesh->faceCount(); j++) {
			uint16 *triangle = _planes[i]->_mesh->getFace(j);
			float *v0 = _planes[i]->_mesh->getVertexPosition(triangle[0]);
			float *v1 = _planes[i]->_mesh->getVertexPosition(triangle[1]);
			float *v2 = _planes[i]->_mesh->getVertexPosition(triangle[2]);

			if (lineIntersectsTriangle(pos, dir,
			                           Math::Vector3d(v0[0], v0[1], v0[2]),
			                           Math::Vector3d(v1[0], v1[1], v1[2]),
			                           Math::Vector3d(v2[0], v2[1], v2[2]),
			                           intersection.x(), intersection.y(), intersection.z())) {
				if (intersection.y() > pos.y() + tolerance) {
					continue; // only fall down
				}

				if (!intFoundTmp || ABS(ret - pos.y()) > ABS(intersection.y() - pos.y())) {
					ret = intersection.y();
				}

				intFoundTmp = true;
			}
		}
	}

	if (intFound) {
		*intFound = intFoundTmp;
	}

	return ret;
}

//////////////////////////////////////////////////////////////////////////
bool AdSceneGeometry::directPathExists(Math::Vector3d *p1, Math::Vector3d *p2) {
	// pretty sure this stuff can be somewhat simplified by factoring it out
	// into some functions but let's leave this to a later point

	// test walkplanes
	for (uint i = 0; i < _planes.size(); i++) {
		for (int j = 0; j < _planes[i]->_mesh->faceCount(); j++) {
			uint16 *triangle = _planes[i]->_mesh->getFace(j);
			float *v0 = _planes[i]->_mesh->getVertexPosition(triangle[0]);
			float *v1 = _planes[i]->_mesh->getVertexPosition(triangle[1]);
			float *v2 = _planes[i]->_mesh->getVertexPosition(triangle[2]);
			Math::Vector3d intersection;
			float dist;

			if (lineSegmentIntersectsTriangle(*p1, *p2, Math::Vector3d(v0[0], v0[1], v0[2]),
			                                  Math::Vector3d(v1[0], v1[1], v1[2]),
			                                  Math::Vector3d(v2[0], v2[1], v2[2]),
			                                  intersection, dist)) {
				if (lineIntersectsTriangle(*p1, *p1 - *p2, v0, v1, v2,
				                           intersection.x(), intersection.y(), intersection.z())) {
					return false;
				}

				if (lineIntersectsTriangle(*p2, *p2 - *p1, v0, v1, v2,
				                           intersection.x(), intersection.y(), intersection.z())) {
					return false;
				}
			}
		}
	}

	// test blocks
	for (uint i = 0; i < _blocks.size(); i++) {
		if (!_blocks[i]->_active) {
			continue;
		}

		for (int j = 0; j < _blocks[i]->_mesh->faceCount(); j++) {
			uint16 *triangle = _blocks[i]->_mesh->getFace(j);
			float *v0 = _blocks[i]->_mesh->getVertexPosition(triangle[0]);
			float *v1 = _blocks[i]->_mesh->getVertexPosition(triangle[1]);
			float *v2 = _blocks[i]->_mesh->getVertexPosition(triangle[2]);
			Math::Vector3d intersection;
			float dist;

			if (lineSegmentIntersectsTriangle(*p1, *p2, Math::Vector3d(v0[0], v0[1], v0[2]),
			                                  Math::Vector3d(v1[0], v1[1], v1[2]),
			                                  Math::Vector3d(v2[0], v2[1], v2[2]),
			                                  intersection, dist)) {
				if (lineIntersectsTriangle(*p1, *p1 - *p2, v0, v1, v2,
				                           intersection.x(), intersection.y(), intersection.z())) {
					return false;
				}

				if (lineIntersectsTriangle(*p2, *p2 - *p1, v0, v1, v2,
				                           intersection.x(), intersection.y(), intersection.z())) {
					return false;
				}
			}
		}
	}

	return true;
}

//////////////////////////////////////////////////////////////////////////
Math::Vector3d AdSceneGeometry::getBlockIntersection(Math::Vector3d *p1, Math::Vector3d *p2) {
	// test blocks
	for (uint i = 0; i < _blocks.size(); i++) {
		if (!_blocks[i]->_active) {
			continue;
		}

		for (int j = 0; j < _blocks[i]->_mesh->faceCount(); j++) {
			uint16 *triangle = _blocks[i]->_mesh->getFace(j);
			float *v0 = _blocks[i]->_mesh->getVertexPosition(triangle[0]);
			float *v1 = _blocks[i]->_mesh->getVertexPosition(triangle[1]);
			float *v2 = _blocks[i]->_mesh->getVertexPosition(triangle[2]);

			Math::Vector3d intersection;
			float dist;

			if (lineSegmentIntersectsTriangle(*p1, *p2, v0, v1, v2, intersection, dist)) {
				if (lineIntersectsTriangle(*p1, *p1 - *p2, v0, v1, v2, intersection.x(), intersection.y(), intersection.z())) {
					return intersection;
				}

				if (lineIntersectsTriangle(*p2, *p2 - *p1, v0, v1, v2, intersection.x(), intersection.y(), intersection.z())) {
					return intersection;
				}
			}
		}
	}

	return Math::Vector3d(0, 0, 0);
}

//////////////////////////////////////////////////////////////////////////
bool AdSceneGeometry::convert2Dto3DTolerant(int x, int y, Math::Vector3d *pos) {
	bool Ret = convert2Dto3D(x, y, pos);
	if (Ret) {
		return Ret;
	}

	int lenLeft = 0;
	int lenRight = 0;
	int lenDown = 0;
	int lenUp = 0;

	int i;

	// left
	for (i = 0; i < 1000; i += 10) {
		if (convert2Dto3D(x - i, y, pos)) {
			lenLeft = i;
			break;
		}
	}

	// right
	for (i = 0; i < 1000; i += 10) {
		if (convert2Dto3D(x + i, y, pos)) {
			lenRight = i;
			break;
		}
	}

	// up
	for (i = 0; i < 1000; i += 10) {
		if (convert2Dto3D(x, y - i, pos)) {
			lenUp = i;
			break;
		}
	}

	// down
	for (i = 0; i < 1000; i += 10) {
		if (convert2Dto3D(x, y + i, pos)) {
			lenDown = i;
			break;
		}
	}

	if (!lenLeft && !lenRight && !lenUp && !lenDown) {
		return false;
	}

	int offsetX = INT_MAX_VALUE;
	int offsetY = INT_MAX_VALUE;

	if (lenLeft || lenRight) {
		if (lenRight) {
			if (lenLeft && lenLeft < lenRight) {
				offsetX = -lenLeft;
			} else {
				offsetX = lenRight;
			}
		} else {
			offsetX = -lenLeft;
		}
	}

	if (lenUp || lenDown) {
		if (lenDown) {
			if (lenUp && lenUp < lenDown)
				offsetY = -lenUp;
			else
				offsetY = lenDown;
		} else
			offsetY = -lenUp;
	}

	if (abs(offsetX) < abs(offsetY)) {
		x += offsetX;
	} else {
		y += offsetY;
	}

	return convert2Dto3D(x, y, pos);
}

//////////////////////////////////////////////////////////////////////////
bool AdSceneGeometry::convert2Dto3D(int x, int y, Math::Vector3d *pos) {
	bool intFound = false;
	float minDist = FLT_MAX;

	Math::Ray ray = _gameRef->_renderer3D->rayIntoScene(x, y);

	for (uint32 i = 0; i < _planes.size(); i++) {
		for (int j = 0; j < _planes[i]->_mesh->faceCount(); j++) {
			uint16 *triangle = _planes[i]->_mesh->getFace(j);
			float *v0 = _planes[i]->_mesh->getVertexPosition(triangle[0]);
			float *v1 = _planes[i]->_mesh->getVertexPosition(triangle[1]);
			float *v2 = _planes[i]->_mesh->getVertexPosition(triangle[2]);
			Math::Vector3d intersection;

			if (lineIntersectsTriangle(ray.getOrigin(), ray.getDirection(),
			                           Math::Vector3d(v0[0], v0[1], v0[2]),
			                           Math::Vector3d(v1[0], v1[1], v1[2]),
			                           Math::Vector3d(v2[0], v2[1], v2[2]),
			                           intersection.x(), intersection.y(), intersection.z())) {
				Math::Vector3d lineSegement = intersection - getActiveCamera()->_position;
				float dist = lineSegement.getMagnitude();

				if (dist < minDist) {
					*pos = intersection;
					minDist = dist;
				}

				intFound = true;
			}
		}
	}

	return intFound;
}

//////////////////////////////////////////////////////////////////////////
bool AdSceneGeometry::getPath(Math::Vector3d source, Math::Vector3d target, AdPath3D *path, bool rerun) {
	if (!_PFReady) {
		return false;
	} else {
		source.y() = getHeightAt(source, _waypointHeight) + _waypointHeight;
		target.y() = getHeightAt(target, _waypointHeight) + _waypointHeight;

		_PFReady = false;
		_PFSource = source;
		_PFTarget = target;
		_PFTargetPath = path;
		_PFAlternateTarget = Math::Vector3d(0, 0, 0);
		_PFAlternateDist = FLT_MAX;

		_PFTargetPath->reset();
		_PFTargetPath->setReady(false);
		_PFRerun = rerun;

		// prepare working path
		uint i;
		uint j;

		for (i = 0; i < _PFPath.size(); i++) {
			delete _PFPath[i];
		}

		_PFPath.clear();

		// first point
		_PFPath.add(new AdPathPoint3D(source, 0));

		// last point
		_PFPath.add(new AdPathPoint3D(target, FLT_MAX));

		// add all active waypoints
		for (i = 0; i < _waypointGroups.size(); i++) {
			if (_waypointGroups[i]->_active) {
				for (j = 0; j < _waypointGroups[i]->_points.size(); j++) {
					_PFPath.add(new AdPathPoint3D(*_waypointGroups[i]->_points[j], FLT_MAX));
				}
			}
		}

		return true;
	}
}

//////////////////////////////////////////////////////////////////////////
void AdSceneGeometry::pathFinderStep() {
	uint i;

	// get lowest unmarked
	float lowest_dist = FLT_MAX;
	AdPathPoint3D *lowest_pt = NULL;

	for (i = 0; i < _PFPath.size(); i++) {
		if (!_PFPath[i]->_marked && _PFPath[i]->_distance < lowest_dist) {
			lowest_dist = _PFPath[i]->_distance;
			lowest_pt = _PFPath[i];
		}
	}

	if (lowest_pt == NULL) { // no path -> terminate PathFinder
		_PFReady = true;

		if (!_PFRerun) {
			if (_PFAlternateTarget != Math::Vector3d(0, 0, 0)) {
				getPath(_PFSource, _PFAlternateTarget, _PFTargetPath, true);
			} else {
				_PFTargetPath->setReady(true);
			}
		} else {
			_PFTargetPath->setReady(true);
		}

		return;
	}

	lowest_pt->_marked = true;

	// target point marked, generate path and terminate
	if (lowest_pt->_pos == _PFTarget) {
		while (lowest_pt != NULL) {
			_PFTargetPath->_points.insert_at(0, new Math::Vector3d(lowest_pt->_pos));
			lowest_pt = lowest_pt->_origin;
		}
		// remove current position
		if (_PFTargetPath->_points.size() > 0) {
			delete _PFTargetPath->_points[0];
			_PFTargetPath->_points.remove_at(0);
		}

		_PFReady = true;
		_PFTargetPath->setReady(true);
		return;
	}

	// otherwise keep on searching
	for (i = 0; i < _PFPath.size(); i++) {
		if (!_PFPath[i]->_marked) {
			float dist = getPointsDist(lowest_pt->_pos, _PFPath[i]->_pos);
			if (dist >= 0 && lowest_pt->_distance + dist < _PFPath[i]->_distance) {
				_PFPath[i]->_distance = lowest_pt->_distance + dist;
				_PFPath[i]->_origin = lowest_pt;
			} else {
				if (!_PFRerun && _PFPath[i]->_pos == _PFTarget) {
					Math::Vector3d Line = _PFPath[i]->_pos - lowest_pt->_pos;
					float Len = Line.getMagnitude();

					if (Len < _PFAlternateDist) {
						_PFAlternateDist = Len;
						_PFAlternateTarget = getBlockIntersection(&lowest_pt->_pos, &_PFPath[i]->_pos);

						Math::Vector3d Dir = _PFAlternateTarget - lowest_pt->_pos;
						Dir.normalize();
						_PFAlternateTarget -= Dir * 30;
					}
				}
			}
		}
	}
}

//////////////////////////////////////////////////////////////////////////
float AdSceneGeometry::getPointsDist(Math::Vector3d p1, Math::Vector3d p2) {
	if (!directPathExists(&p1, &p2)) {
		return -1;
	}

	Math::Vector3d vect = p2 - p1;
	return vect.getMagnitude();
}

//////////////////////////////////////////////////////////////////////////
bool AdSceneGeometry::initLoop() {
	uint32 start = _gameRef->_currentTime;
	while (!_PFReady && _gameRef->_currentTime - start <= _PFMaxTime) {
		pathFinderStep();
	}

	return true;
}

//////////////////////////////////////////////////////////////////////////
bool AdSceneGeometry::createLights() {
	// disable all lights
	for (int i = 0; i < _gameRef->_renderer3D->maximumLightsCount(); i++) {
		_gameRef->_renderer3D->disableLight(i);
	}

	int lightCount = MIN(static_cast<int>(_lights.size()), _gameRef->_renderer3D->maximumLightsCount());

	for (int i = 0; i < lightCount; i++) {
		_lights[i]->setLight(i);
	}

	return true;
}

//////////////////////////////////////////////////////////////////////////
bool compareLights(const Light3D *light1, const Light3D *light2) {
	return light1->_distance < light2->_distance;
}

//////////////////////////////////////////////////////////////////////////
bool AdSceneGeometry::enableLights(Math::Vector3d point, BaseArray<char *> &ignoreLights) {
	const int maxLightCount = 100;

	int activeLightCount = 0;
	for (uint i = 0; i < _lights.size(); i++) {
		_lights[i]->_isAvailable = false;
		if (_lights[i]->_active) {
			++activeLightCount;
		}
	}

	if (activeLightCount <= _gameRef->_renderer3D->maximumLightsCount()) {
		for (uint i = 0; i < _lights.size(); i++) {
			_lights[i]->_isAvailable = true;
		}
	} else {
		if (!_maxLightsWarning) {
			_gameRef->LOG(0, "Warning: Using more lights than the hardware supports (%d)", _gameRef->_renderer3D->maximumLightsCount());
			_maxLightsWarning = true;
		}

		Common::Array<Light3D *> activeLights;

		// compute distance to point
		for (uint i = 0; i < _lights.size(); i++) {
			if (!_lights[i]->_active) {
				continue;
			}

			Math::Vector3d dif;

			if (_lights[i]->_isSpotlight) {
				Math::Vector3d dir = _lights[i]->_target - _lights[i]->_position;
				dif = (_lights[i]->_position + dir * 0.75f) - point;
			} else {
				dif = _lights[i]->_position - point;
			}

			_lights[i]->_distance = dif.getMagnitude();

			activeLights.push_back(_lights[i]);
		}

		// sort by distance
		if (activeLights.size() > 0) {
			Common::sort(activeLights.begin(), activeLights.end(), compareLights);

			for (uint i = 0; i < activeLights.size(); i++) {
				activeLights[i]->_isAvailable = static_cast<int>(i) < _gameRef->_renderer3D->maximumLightsCount();
			}
		}
	}

	// light all available lights
	for (int i = 0; i < maxLightCount; i++) {
		_gameRef->_renderer3D->disableLight(i);
	}

	activeLightCount = 0;

	for (uint i = 0; i < _lights.size(); i++) {
		if (activeLightCount >= _gameRef->_renderer3D->maximumLightsCount()) {
			break;
		}

		if (ignoreLights.size()) {
			bool ignore = false;

			for (uint j = 0; j < ignoreLights.size(); j++) {
				if (scumm_stricmp(_lights[i]->getName(), ignoreLights[j]) == 0) {
					ignore = true;
					break;
				}
			}

			if (ignore) {
				continue; // skip this light
			}
		}

		if (_lights[i]->_isAvailable) {
			if (_lights[i]->_active) {
				_gameRef->_renderer3D->enableLight(i);
				++activeLightCount;
			}
		}
	}

	return true;
}

//////////////////////////////////////////////////////////////////////////
bool AdSceneGeometry::correctTargetPoint(const Math::Vector3d &source, Math::Vector3d *target) {
	// the source parameter is not even used in wme3d
	int i;
	int MaxLen = 1000;
	int Step = 10;
	Math::Vector3d newTarget;

	int lenLeft = 0;
	int lenRight = 0;
	int lenUp = 0;
	int lenDown = 0;

	// left
	newTarget = *target;
	for (i = 1; i <= MaxLen; i += Step) {
		newTarget.x() -= i;
		if (!directPathExists(target, &newTarget)) {
			lenLeft = i;
			break;
		}
	}

	// right
	newTarget = *target;
	for (i = 1; i <= MaxLen; i += Step) {
		newTarget.x() += i;
		if (!directPathExists(target, &newTarget)) {
			lenRight = i;
			break;
		}
	}

	// up
	newTarget = *target;
	for (i = 1; i <= MaxLen; i += Step) {
		newTarget.z() -= i;
		if (!directPathExists(target, &newTarget)) {
			lenUp = i;
			break;
		}
	}

	// down
	newTarget = *target;
	for (i = 1; i <= MaxLen; i += Step) {
		newTarget.z() += i;
		if (!directPathExists(target, &newTarget)) {
			lenDown = i;
			break;
		}
	}

	if (!lenLeft && !lenRight && !lenUp && !lenDown) {
		return true;
	}

	int offsetX = INT_MAX_VALUE;
	int offsetZ = INT_MAX_VALUE;

	if (lenLeft || lenRight) {
		if (lenRight) {
			if (lenLeft && lenLeft < lenRight) {
				offsetX = -lenLeft;
			} else {
				offsetX = lenRight;
			}
		} else {
			offsetX = -lenLeft;
		}
	}

	if (lenUp || lenDown) {
		if (lenDown) {
			if (lenUp && lenUp < lenDown) {
				offsetZ = -lenUp;
			} else {
				offsetZ = lenDown;
			}
		} else {
			offsetZ = -lenUp;
		}
	}

	if (abs(offsetX) < abs(offsetZ)) {
		target->x() += offsetX;
	} else {
		target->z() += offsetZ;
	}

	return true;
}

//////////////////////////////////////////////////////////////////////////
bool AdSceneGeometry::enableNode(const char *nodeName, bool enable) {
	bool ret = false;

	uint i;
	for (i = 0; i < _blocks.size(); i++) {
		if (scumm_stricmp(nodeName, _blocks[i]->getName()) == 0) {
			_blocks[i]->_active = enable;
			ret = true;
		}
	}

	for (i = 0; i < _planes.size(); i++) {
		if (scumm_stricmp(nodeName, _planes[i]->getName()) == 0) {
			_planes[i]->_active = enable;
			ret = true;
		}
	}

	for (i = 0; i < _generics.size(); i++) {
		if (scumm_stricmp(nodeName, _generics[i]->getName()) == 0) {
			_generics[i]->_active = enable;
			ret = true;
		}
	}

	return ret;
}

//////////////////////////////////////////////////////////////////////////
bool AdSceneGeometry::isNodeEnabled(const char *nodeName) {
	for (uint i = 0; i < _blocks.size(); i++) {
		if (scumm_stricmp(nodeName, _blocks[i]->getName()) == 0) {
			return _blocks[i]->_active;
		}
	}
	for (uint i = 0; i < _planes.size(); i++) {
		if (scumm_stricmp(nodeName, _planes[i]->getName()) == 0) {
			return _planes[i]->_active;
		}
	}

	for (uint i = 0; i < _generics.size(); i++) {
		if (scumm_stricmp(nodeName, _generics[i]->getName()) == 0) {
			return _generics[i]->_active;
		}
	}

	return false;
}

//////////////////////////////////////////////////////////////////////////
bool AdSceneGeometry::enableLight(const char *lightName, bool enable) {
	bool ret = false;

	uint i;
	for (i = 0; i < _lights.size(); i++) {
		if (scumm_stricmp(lightName, _lights[i]->getName()) == 0) {
			_lights[i]->_active = enable;
			ret = true;
		}
	}
	createLights();

	return ret;
}

//////////////////////////////////////////////////////////////////////////
bool AdSceneGeometry::isLightEnabled(const char *lightName) {
	for (uint i = 0; i < _lights.size(); i++) {
		if (scumm_stricmp(lightName, _lights[i]->getName()) == 0) {
			return _lights[i]->_active;
		}
	}
	return false;
}

//////////////////////////////////////////////////////////////////////////
bool AdSceneGeometry::setLightColor(const char *lightName, uint32 color) {
	bool ret = false;

	uint i;
	for (i = 0; i < _lights.size(); i++) {
		if (scumm_stricmp(lightName, _lights[i]->getName()) == 0) {
			_lights[i]->_diffuseColor = color;
			ret = true;
		}
	}
	createLights();

	return ret;
}

//////////////////////////////////////////////////////////////////////////
uint32 AdSceneGeometry::getLightColor(const char *lightName) {
	for (uint i = 0; i < _lights.size(); i++) {
		if (scumm_stricmp(lightName, _lights[i]->getName()) == 0) {
			return _lights[i]->_diffuseColor;
		}
	}
	return 0;
}

//////////////////////////////////////////////////////////////////////////
Math::Vector3d AdSceneGeometry::getLightPos(const char *lightName) {
	for (uint i = 0; i < _lights.size(); i++) {
		if (scumm_stricmp(lightName, _lights[i]->getName()) == 0) {
			return _lights[i]->_position;
		}
	}
	return Math::Vector3d(0, 0, 0);
}

//////////////////////////////////////////////////////////////////////////
bool AdSceneGeometry::persist(BasePersistenceManager *persistMgr) {
	BaseObject::persist(persistMgr);

	persistMgr->transferFloat(TMEMBER(_waypointHeight));
	persistMgr->transferPtr(TMEMBER(_wptMarker));

	if (!persistMgr->getIsSaving()) {
		//m_WptMarker = NULL;
		loadFile(getFilename());
		_lastValuesInitialized = false;
	}

	persistMgr->transferSint32(TMEMBER(_activeCamera));
	persistMgr->transferSint32(TMEMBER(_activeLight));
	persistMgr->transferMatrix4(TMEMBER(_viewMatrix));

	_PFPath.persist(persistMgr);
	persistMgr->transferBool(TMEMBER(_PFReady));
	persistMgr->transferVector3d(TMEMBER(_PFSource));
	persistMgr->transferVector3d(TMEMBER(_PFTarget));
	persistMgr->transferVector3d(TMEMBER(_PFAlternateTarget));
	persistMgr->transferPtr(TMEMBER(_PFTargetPath));
	persistMgr->transferUint32(TMEMBER(_PFMaxTime));
	persistMgr->transferBool(TMEMBER(_PFRerun));

	// now save/load light/blocks/walkplanes/generic node states by name
	int i;

	//////////////////////////////////////////////////////////////////////////
	int32 numLights = _lights.size();
	persistMgr->transferSint32(TMEMBER(numLights));
	for (i = 0; i < numLights; i++) {
		if (persistMgr->getIsSaving()) {
			persistMgr->transferCharPtr(TMEMBER(_lights[i]->_name));
			_lights[i]->persist(persistMgr);
		} else {
			char *name = nullptr;
			persistMgr->transferCharPtr(TMEMBER(name));
			bool found = false;

			for (uint j = 0; j < _lights.size(); j++) {
				if (scumm_stricmp(name, _lights[j]->getName()) == 0) {
					_lights[j]->persist(persistMgr);
					found = true;
					break;
				}
			}

			if (!found) {
				Light3D *light = new Light3D(_gameRef);
				light->persist(persistMgr);
				delete light;
			}

			if (name) {
				delete[] name;
			}
		}
	}
	createLights();

	//////////////////////////////////////////////////////////////////////////
	int32 numBlocks = _blocks.size();
	persistMgr->transferSint32(TMEMBER(numBlocks));
	for (i = 0; i < numBlocks; i++) {
		if (persistMgr->getIsSaving()) {
			persistMgr->transferCharPtr(TMEMBER(_blocks[i]->_name));
			_blocks[i]->persist(persistMgr);
		} else {
			char *name = nullptr;
			persistMgr->transferCharPtr(TMEMBER(name));
			bool found = false;
			for (uint j = 0; j < _blocks.size(); j++) {
				if (scumm_stricmp(name, _blocks[j]->getName()) == 0) {
					_blocks[j]->persist(persistMgr);
					found = true;
					break;
				}
			}
			if (!found) {
				AdBlock *block = new AdBlock(_gameRef);
				block->persist(persistMgr);
				delete block;
			}

			if (name) {
				delete[] name;
			}
		}
	}

	//////////////////////////////////////////////////////////////////////////
	int32 numPlanes = _planes.size();
	persistMgr->transferSint32(TMEMBER(numPlanes));
	for (i = 0; i < numPlanes; i++) {
		if (persistMgr->getIsSaving()) {
			persistMgr->transferCharPtr(TMEMBER(_planes[i]->_name));
			_planes[i]->persist(persistMgr);
		} else {
			char *name = nullptr;
			persistMgr->transferCharPtr(TMEMBER(name));
			bool found = false;
			for (uint j = 0; j < _planes.size(); j++) {
				if (scumm_stricmp(name, _planes[j]->getName()) == 0) {
					_planes[j]->persist(persistMgr);
					found = true;
					break;
				}
			}
			if (!found) {
				AdWalkplane *plane = new AdWalkplane(_gameRef);
				plane->persist(persistMgr);
				delete plane;
			}

			if (name) {
				delete[] name;
			}
		}
	}

	//////////////////////////////////////////////////////////////////////////
	int32 numGenerics = _generics.size();
	persistMgr->transferSint32(TMEMBER(numGenerics));
	for (i = 0; i < numGenerics; i++) {
		if (persistMgr->getIsSaving()) {
			persistMgr->transferCharPtr(TMEMBER(_generics[i]->_name));
			_generics[i]->persist(persistMgr);
		} else {
			char *name = nullptr;
			persistMgr->transferCharPtr(TMEMBER(name));
			bool found = false;
			for (uint j = 0; j < _generics.size(); j++) {
				if (scumm_stricmp(name, _generics[j]->getName()) == 0) {
					_generics[j]->persist(persistMgr);
					found = true;
					break;
				}
			}
			if (!found) {
				AdGeneric *generic = new AdGeneric(_gameRef);
				generic->persist(persistMgr);
				delete generic;
			}

			if (name) {
				delete[] name;
			}
		}
	}

	if (!persistMgr->getIsSaving()) {
		_maxLightsWarning = false;
	}

	return true;
}

//////////////////////////////////////////////////////////////////////////
bool AdSceneGeometry::convert3Dto2D(Math::Vector3d *pos, int32 *x, int32 *y) {
	Math::Matrix4 worldMat;
	worldMat.setToIdentity();

	Math::Vector3d vect2D;
	int viewportTmp[4];
	// TODO: gluMathProject expects an OpenGL viewport,
	// hence the first coordinates specify the lower left corner
	// wme works with a Direct3D viewport, though
	// so check if this does work
	viewportTmp[0] = _drawingViewport.left;
	viewportTmp[1] = _drawingViewport.bottom;
	viewportTmp[2] = _drawingViewport.width();
	viewportTmp[3] = _drawingViewport.height();
	Math::Matrix4 modelViewMatrix = _lastViewMat * worldMat;
	Math::gluMathProject(*pos, modelViewMatrix.getData(), _lastProjMat.getData(), viewportTmp, vect2D);
	*x = vect2D.x() + _lastScrollX;
	*y = vect2D.y() + _lastScrollY;

	return true;
}

} // namespace Wintermute
