"use strict";

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Requires these files to be loaded beforehand: Viewpoint, Util
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
/**
 * Namespace for useful math, color, etc. values, also for default values for camera, definitions of viewpoints,
 * GUI element positions and similar, a lot of stuff.
 *
 * @author smrceant (Antonin Smrcek)
 *
 * @namespace
 */
var Preferences = function(){};

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// SKETCHUP
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

Preferences.Browser =
{
	// whether the browser is Google Chrome; we need to know this because Chrome behavior during computation of position of red dot for minimap and for rendering is a little different
	CHROME: (navigator.userAgent.toLowerCase().indexOf('chrome') > -1),

	// Edge messes up a few things (weird SVG rendering of accented characters in Czech, ...), so we will try to fix those things if we know that wild Edge appeared..
	EDGE: (document.documentMode || /Edge/.test(navigator.userAgent))
};

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// SKETCHUP
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

// prefixes for group names in SKP file with scene in SketchUp
Preferences.Prefixes =
{
	SKETCHUP_PICKABLE_GROUPS_OF_MODELS: "pickable_",

	HEIGHTMAP_FLOOR_LAYER: "Heightmap_floor",
	HEIGHTMAP_CEILING_LAYER: "Heightmap_ceiling", // note: largest value layer has no prefix - no need to, because it is everything other than floor and ceiling

	SVG_AREA_MAP_LOCATION: "area_",

	WATER: 'water_',

	// suffixes for bump and normal textures, example:
	// - we have texture bricks.jpg and decide that we will make a bump map for it (we can do either bump map or normal map, not both)
	// - the bricks.jpg must be renamed to bricks_diffusen.jpg (original name + standard texture suffix + char suffix for normal maps signalizing that the texture has normal map)
	// - the normal map must have name bricks_normal.jpg
	BUMPNORMALMAPS_STANDARD_TEXTURE_SUFFIX: "_diffuse",
	BUMPNORMALMAPS_BUMP_TEXTURE_SUFFIX_CHAR: "b",
	BUMPNORMALMAPS_NORMAL_TEXTURE_SUFFIX_CHAR: "n",
	BUMPNORMALMAPS_BUMP_TEXTURE_SUFFIX: "_bump",
	BUMPNORMALMAPS_NORMAL_TEXTURE_SUFFIX: "_normal"
};

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// LANGUAGE
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

Preferences.Languages =
{
	EN: 0,
	CZ: 1,

	changeLanguageToEN: function()
	{
		Preferences.Application.LANGUAGE = Preferences.Languages.EN;
	},

	changeLanguageToCZ: function()
	{
		Preferences.Application.LANGUAGE = Preferences.Languages.CZ;
	},

	getCurrentLanguage: function()
	{
		return Preferences.Application.LANGUAGE;
	},

	getCurrentLanguageName: function()
	{
		return Preferences.Languages._enumNames[Preferences.Languages.getCurrentLanguage()];
	},

	getLanguageCount: function()
	{
		return Preferences.Languages._enumNames.length;
	}
};
Preferences.Languages._enumNames = Util.getEnumKeys (Preferences.Languages);


////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// APPLICATION
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

Preferences.AppMode =
{
	PRODUCTION: 0,
	DEV: 1
};

Preferences.Application =
{
	MODE: Preferences.AppMode.DEV,
	LANGUAGE: Preferences.Languages.CZ,
	MUTE_CONSOLE: false,

	// (we chose 1000ms since then we can input speed in meters per second, which is natural, and also 1m in application is 1m in the real world)
	// all movement will have this time value as base, makes sure that entities' amount of move will be depending on time, not on number of rendered frames (same travelled distance after e.g. 1000ms regardless of number of rendered frames)
	FRAME_TIME_BASIS_FOR_SPEED_COMPUTATIONS: 1000,

	START_DATE: new Date(),
	MEASURE_FRAME_TIME: !true,
	LAST_USED_THREE_JS_VERSION: 77,

	/**
	 *
	 * @returns {boolean} TRUE if application is in development mode
	 */
	isDevMode: function()
	{
		return (Preferences.Application.MODE === Preferences.AppMode.DEV);
	}
};

var dateTime = Preferences.Application.isDevMode() ? '?' + Date.now() : '';
Preferences.Application.SCENE_DEFINITION_FILE = "data/scene/prague_castle.dae" + dateTime;

// disable AJAX caching for DEV
if(Preferences.Application.isDevMode()){
	$.ajaxSetup({ cache: false });
}

// if we have not forced console settings, then it will be muted in the production mode
if (typeof Preferences.Application.MUTE_CONSOLE === "undefined")
{
	Preferences.Application.MUTE_CONSOLE = (Preferences.Application.MODE === Preferences.AppMode.PRODUCTION);
}


if (Preferences.Application.MUTE_CONSOLE)
{
	console.log = console.error = console.warn = console.info = function() {};
}

if (THREE.REVISION != Preferences.Application.LAST_USED_THREE_JS_VERSION)
{
	console.warn("You are using different version of Three.js (" + THREE.REVISION + ") than version which is marked as last used in the project (" + Preferences.Application.LAST_USED_THREE_JS_VERSION + "). If you have upgraded, make sure than you have included all the 'hack' stuff marked with 'smrceant' from the old Three.js file, otherwise some things will be broken.");
}

Preferences.getCurrentLanguage = function()
{
	return Preferences.Application.LANGUAGE;
};

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// CONTROLS
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

Preferences.Control =
{
	// if TRUE, then by pressing side arrows (both keyboard or HUD), the avatar would strafe like in action games
	STRAFING: false, // note: changes made to this variable actually change anything only after the application is running

	// if TRUE, vertical mouse movement is inverted (like in simulators etc.)
	INVERTED_MOUSE_Y: false,

	// the mouse wheel sensitivity is affected by normal mouse sensitivity, but that is still not enough for the mouse wheel actions, they are still very "small"
	MOUSE_WHEEL_SENSITIVITY_BASE: Preferences.Application.isDevMode() ? 3 : 2,

	// if there is a collision with mouse wheel event, it won't be "fast enough" and the keyboard will revert to its original state right away in the same frame if we don't hold the forbidden style for a few frames
	// (keyboard does not have this problem, since it fires continuously)
	MOUSE_WHEEL_COLLISION_FRAMES_FORBIDDEN_KEYBOARD_COLORING: 6,

	// this is the relative speed of mouse look (also denotes position of the mouse sensitivity marker on the slider in the main menu); the final number which controls the mouse sensitivity, i.e. speed of mouse look, is MOUSE_SENSITIVITY and is computed as:
	// MOUSE_RELATIVE_SENSITIVITY * allowedRangeForSensitivity, i.e. MOUSE_RELATIVE_SENSITIVITY * (_MOUSE_SENSITIVITY_HIGH_MULTIPLICATION_FACTOR-_MOUSE_SENSITIVITY_LOW_MULTIPLICATION_FACTOR). Think of it as linear interpolation.
	// the _MOUSE_SENSITIVITY_LOW_MULTIPLICATION_FACTOR denotes the lower boundary for the range, _MOUSE_SENSITIVITY_HIGH_MULTIPLICATION_FACTOR denotes the upper boundary
	// example:
	// MOUSE_SENSITIVITY = 0.5,
	// _MOUSE_SENSITIVITY_LOW_MULTIPLICATION_FACTOR = 0.2,
	// _MOUSE_SENSITIVITY_HIGH_MULTIPLICATION_FACTOR = 2.2 ... so what will be de final number which controls the mouse sensitivity?
	// ... _MOUSE_SENSITIVITY_MULTIPLICATION_FACTOR_RANGE = 2.2-0.2 = 2.0
	// ... MOUSE_SENSITIVITY = MOUSE_RELATIVE_SENSITIVITY * _MOUSE_SENSITIVITY_MULTIPLICATION_FACTOR_RANGE = 0.5 * 2.0 = 1.0
	// the result is 1, meaning that amount of move applied to the camera will be multiplied by 1 (yes, the values were chosen so that relative sensitivity of 0.5 will yield such result)
	MOUSE_RELATIVE_SENSITIVITY: 0.5,

	_MOUSE_SENSITIVITY_LOW_MULTIPLICATION_FACTOR: 0.2,
	_MOUSE_SENSITIVITY_HIGH_MULTIPLICATION_FACTOR: 2.2,

	MOUSE_SENSITIVITY: 1,

	computeMouseSensitivity: function()
	{
		Preferences.Control.MOUSE_SENSITIVITY = Preferences.Control.MOUSE_RELATIVE_SENSITIVITY * Preferences.Control._MOUSE_SENSITIVITY_MULTIPLICATION_FACTOR_RANGE;
	},


	MOVING_SPEED_RELATIVE: Preferences.Application.isDevMode() ? 0.5 : 0.11,

	_MOVING_SPEED_LOW_MULTIPLICATION_FACTOR: 0.2,
	_MOVING_SPEED_HIGH_MULTIPLICATION_FACTOR: 2.2,

	MOVING_SPEED: 1,

	computeMovingSpeed: function()
	{
		Preferences.Control.MOVING_SPEED = Preferences.Control.MOVING_SPEED_RELATIVE * Preferences.Control._MOVING_SPEED_MULTIPLICATION_FACTOR_RANGE;
	}
};
Preferences.Control._MOUSE_SENSITIVITY_MULTIPLICATION_FACTOR_RANGE = Preferences.Control._MOUSE_SENSITIVITY_HIGH_MULTIPLICATION_FACTOR - Preferences.Control._MOUSE_SENSITIVITY_LOW_MULTIPLICATION_FACTOR;
Preferences.Control._MOVING_SPEED_MULTIPLICATION_FACTOR_RANGE = Preferences.Control._MOVING_SPEED_HIGH_MULTIPLICATION_FACTOR - Preferences.Control._MOVING_SPEED_LOW_MULTIPLICATION_FACTOR;

Util.generatePrivateVariablesForGetters (Preferences.Control);

Object.defineProperty (Preferences.Control, "STRAFING",
{
	get: function(){return Util.getVariable (this, "STRAFING");},
	set: function (value)
	{
		Util.setVariable (this, "STRAFING", value);
		control._turnStrafing (value);
	}
});
Object.defineProperty (Preferences.Control, "INVERTED_MOUSE_Y",
{
	get: function(){return Util.getVariable (this, "INVERTED_MOUSE_Y");},
	set: function (value)
	{
		Util.setVariable (this, "INVERTED_MOUSE_Y", value);
	}
});
Object.defineProperty (Preferences.Control, "MOUSE_RELATIVE_SENSITIVITY",
{
	get: function(){return Util.getVariable (this, "MOUSE_RELATIVE_SENSITIVITY");},
	set: function (value)
	{
		Util.setVariable (this, "MOUSE_RELATIVE_SENSITIVITY", value);
		Preferences.Control.computeMouseSensitivity();
	}
});
Object.defineProperty (Preferences.Control, "MOVING_SPEED_RELATIVE",
{
	get: function(){return Util.getVariable (this, "MOVING_SPEED_RELATIVE");},
	set: function (value)
	{
		Util.setVariable (this, "MOVING_SPEED_RELATIVE", value);

		Preferences.Control.computeMovingSpeed();
	}
});

// get correct value valid for initial value of MOUSE_RELATIVE_SENSITIVITY
Preferences.Control.computeMouseSensitivity();

Preferences.Control.computeMovingSpeed();


////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// DEVELOPER
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

Preferences.Dev =
{
	RUN_APPLICATION: true
};

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// FILE PATHS (TO LIBRARIES ETC.)
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

Preferences.Filepaths =
{
	jQueryUI: "lib/jQueryUI/",

	infoDialogs: "info/",

	imagesForEffects: "data/effects/",

	// TODO actually, the inverse filepaths are not used anywhere :)
	/**
	 * Reconstructs inverse filepath, e.g. "lib/math" will yield "../../".
	 *
	 * @param {string} filepath path to the library
	 * @returns {string} inverse path (from library to the project root)
	 */
	_createPathToRoot: function (filepath)
	{
		var pathChunks = filepath.split ("/");

		// if last chunk is empty string "", do not count it (happens when path is given as "some/path/" instead of "some/path"
		var level = (pathChunks[pathChunks.length-1] === "") ? pathChunks.length - 1 : pathChunks.length;

		var pathToRoot = "";
		for (var i = 0; i < level; i++)
		{
			pathToRoot += "../";
		}

		return pathToRoot;
	},

	/**
	 * Returns inverse filepath for the given library.
	 *
	 * @param {string} libraryName name of the library, e.g. "jQueryUI"
	 * @returns {string} inverse path (from library to the project root, e.g. "../../")
	 */
	getPathToRoot: function (libraryName)
	{
		return Preferences.Filepaths._toRoot[libraryName];
	},

	/**
	 * Reconstructs inverse filepaths (paths to the root) for all defined filepaths.
	 */
	_init: function() {
		Object.keys (Preferences.Filepaths).forEach(
			/**
			 * Key from the Const.Filepaths object (caution - includes all things defined within, so not just strings, but
			 * functions too).
			 *
			 * @param {string} libraryName
			 */
			function (libraryName) {
				// verify that it is really library name, i.e. skip anything which is not string (we will get also names of the functions defined within Preferences.Filepaths)
				if (typeof Preferences.Filepaths[libraryName] === "string") {
					var libraryPath = Preferences.Filepaths[libraryName];
					Preferences.Filepaths._toRoot[libraryName] = Preferences.Filepaths._createPathToRoot (libraryPath);
				}
			}
		);
	},

	// contains paths to the root, indexed by library names as defined in Preferences.Filepaths, example: Preferences.Filepaths._toRoot.jQueryUI
	_toRoot: {}
};
Preferences.Filepaths._init();

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// OPTIMIZATIONS
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

Preferences.Optimizations =
{
	// merging multiple meshes into the one big mesh -> reduced WebGL buffer switches, reduced drawing calls
	// smrceant: If you get an error "groupMaterial is undefined", make sure you have unchecked "Preserve Component Hierarchies" in Sketchup during Collada export!!!
	MESH_MERGING: true,

	// rearranging data inside buffers of the big mesh so that vertices with same material are stored in contiguous areas in memory -> reduced drawing calls
	MATERIAL_DATA_REARRANGEMENT: true,

	// rearranging data from buffers of the big mesh into one big, interleaved buffer -> reduced WebGL buffer switches a little :), and faster performance (.. at least in theory) because of the cache locality of data for each vertex
	INTERLEAVED_BUFFER: true,

	// note: the optimizations above are behaving like a cascade, i.e. if mesh merging is turned off, then it does not make sense to do data rearrangement and buffer interleaving (well, the interleaving may have some sense, but why bother, when mesh merging is the most important optimization)

	// for [DEV] purposes, meshes will be merged into an instance of Geometry class, not a BufferGeometry
	_DEV_USE_NORMAL_GEOMETRY_FOR_MESH_MERGING: false
};

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// WINDOW
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

Preferences.Window =
{
	/**
	 * Finds out size of the browser window without scrollbars included.
	 *
	 * @returns {number[]} array[2], [0] = width, [1] = height
	 */
	detectDimensionWithoutScrollbars: function()
	{
		var $body = $("body");

		// force hide scrollbars
		var originalOverflowAttr = $body.css("overflow");
		$body.css("overflow", "hidden");

		var dimensions = [$(window).width(), $(window).height()];

	// only restore original attribute if not in Edge, Edge shall have it hidden (maybe all browsers shall have it hidden...)
	if (!Preferences.Browser.EDGE)
	{
		$body.css("overflow", originalOverflowAttr);
	}

		return dimensions;
	},

	// without scrollbars, taskbars etc. -- is defined below the enum
	WINDOW_DEFAULT_USER_AVAILABLE_WIDTH: null,
	WINDOW_DEFAULT_USER_AVAILABLE_HEIGHT: null,

	// whole computer screen (does not account for web browser panels, generally do not use this)
	WINDOW_DEFAULT_USER_WIDTH: screen.width,
	WINDOW_DEFAULT_USER_HEIGHT: screen.height
};

(function()
{
	var windowDimensions = Preferences.Window.detectDimensionWithoutScrollbars();

	Preferences.Window.WINDOW_DEFAULT_USER_AVAILABLE_WIDTH = windowDimensions[0];
	Preferences.Window.WINDOW_DEFAULT_USER_AVAILABLE_HEIGHT = windowDimensions[1];
})();

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// MATHEMATICS
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

Preferences.Math =
{
	PI: Math.PI,
	PI_2: 2 * Math.PI,
	PI_HALF: Math.PI / 2,
	RAD_TO_DEG_CONVERSION: 180 / Math.PI, // how much degrees is 1 radian
	DEG_TO_RAD_CONVERSION: Math.PI / 180 // how much radians is 1 degree
};

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// CAMERA
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

Preferences.Camera =
{
	FOV_DEFAULT_DEGREES: 60,

	// 1.0 gives problem during heightmap collision detection - we would see through the wall which is right in front of us
	//Z_NEAR_DEFAULT: 0.03, // 1.0 would cause see-through the building facade textures, since the player is too close to the wall and the texture is before the near plane
	Z_NEAR_DEFAULT: 0.1, // 0.003 ,, 1.0 would cause see-through the building facade textures, since the player is too close to the wall and the texture is before the near plane
	Z_FAR_DEFAULT: 900.0,// our world is about 600x300x600 (X x Y x Z), the biggest distance is between opposite corner, which is 900 // update: nevertheless, it is set to something like 200000 so the sun is visible

	// Orthographic camera which shows entire castle area from top-down view (this camera is also used for generating heightmap)
	ORTHO_Z_NEAR_DEFAULT: 0.1,
	ORTHO_Z_FAR_DEFAULT: 300,
	ORTHO_CLIP: 396,
	ORTHO_POSITION: new THREE.Vector3 (0, 150, 0),//60,300,10
	ORTHO_ROTATION: new THREE.Euler (-Math.PI/2, 0, 0), // top-down

	// how did we came up with these values? a lot of experimenting...
	SHADOW_MAP_ORTHO_LEFT: -396,
	SHADOW_MAP_ORTHO_RIGHT: 396,
	SHADOW_MAP_ORTHO_BOTTOM: -396,
	SHADOW_MAP_ORTHO_TOP: 396,

	CAMERA_MOVE_SPEED_DEFAULT: 30, // meters per second
	CAMERA_ANGLE_CHANGE_AMOUNT_DEFAULT: 0.05,

	VERTICAL_ANGLE_MIN_LIMIT: -Math.PI / 2,
	VERTICAL_ANGLE_MAX_LIMIT: Math.PI / 2,

	// how much will have player his head above camera level (Y direction)
	PLAYER_HEAD_ABOVE_CAMERA_METERS: 1.0
};

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// GUI
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

Preferences.Gui =
{
	MAIN_MENU_HTML: "gui/mainmenu.html",

	LOCATION_SNAPSHOTS_FILEPATH: "data/gui/snapshots/",
	LOCATION_SNAPSHOTS_FILETYPE: ".jpg",

	/**
	 * Constructs a full filepath for the location screenshot given the location name.
	 * Location is either viewpoint or object.
	 *
	 * @param {string} name name of the viewpoint (e.g. "viewpoint_courtyard1") or object (e.g. "courtyard2")
	 * @returns {*} full path for the image, e.g. "data/gui/snapshots/courtyard2.jpg")
	 */
	getLocationSnapshotFilepath: function (name)
	{
		return Preferences.Gui.LOCATION_SNAPSHOTS_FILEPATH + name + Preferences.Gui.LOCATION_SNAPSHOTS_FILETYPE;
	}
};

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// MAP, MINIMAP
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

Preferences.Map =
{
	AREA_MAP_FILENAME: "data/gui/map_area.svg",

	MAP_FILENAME: "data/gui/map.svg",

	VIEWPOINT_ICON_FILENAME: "data/gui/viewpoint_icon.svg",


	// for synchronizing position of {marker vision cone} in the map with {camera view}
	// (value of camera horizontal angle value, which corresponds to vision cone being unrotated, which can be imagined as cone pointing to the 0 or 2PI value on the unit circle)
	// (has to be obtained manually)
	MAP_HORIZONTAL_ANGLE_SYNC_VALUE: Math.PI,

	// where is center (i.e. start, tip) of the vision cone triangle in its image (coordinates relative to image top-left corner, standard image coordinate system where Y increases downward)
	MAP_VISION_CONE_CENTER: new THREE.Vector2 (39,72),

	// if TRUE, underlying map will be initialized to the same dimensions and projection matrix as the world heightmap -> the map image must have same dimensions as world heightmap!!!
	USE_WORLD_HEIGHTMAP_WIDTH_HEIGHT_AND_MATRIX: true,

	// only used when USE_WORLD_HEIGHTMAP_WIDTH_HEIGHT_AND_MATRIX is FALSE
	 // dimensions of the map image - does not have to be same as the dimensions of the heightmap, but likely will be
	 // used just for displaying position of the user in the 2D map image, which was manually corrected to be pretty
	DEFAULT_IMAGE_WIDTH: 1280,
	DEFAULT_IMAGE_HEIGHT: 960,

	//DEFAULT_IMAGE_PROJECTION_MATRIX: new THREE.Matrix4().set (0.003389830468222499, 2.0756726161658981e-19, 0, 0, 0, 0, -0.0010005002841353416, 0, 2.0756726161658981e-19, -0.003389830468222499, 0, 0, 0.548291027545929, -0.6131398677825928, -0.876021146774292, 1),
	// (note: we have 2 options
	// 1) Map image will have same resolution as world heightmap
	// - then map image in GUI can have same width, height and projection matrix as was used for world heightmap generation
	// - we will set USE_WORLD_HEIGHTMAP_WIDTH_HEIGHT_AND_MATRIX to TRUE, GUI will use same underlying map as the world heightmap
	// 2) Map image will have smaller resolution than world heightmap
	// - set USE_WORLD_HEIGHTMAP_WIDTH_HEIGHT_AND_MATRIX to FALSE
	// - generate world heightmap in resolution desired for the map, use the generated image as basis for the map image - and make it look cool and pretty in GIMP or similar program
	// - set DEFAULT_IMAGE_WIDTH and DEFAULT_IMAGE_HEIGHT to desired map resolution, save projection matrix used for generation of the heightmap as DEFAULT_IMAGE_PROJECTION_MATRIX
	// - (possible use case: we can have world heightmap to be 2560x1920 (twice as normal) so the collisions are super-precise, but we certainly do not need such big image of the map)

	// how much is minimap scaled in/out when zooming
	MINIMAP_SCALE_FACTOR: 1.5,

	// smallest allowed minimap scale
	MINIMAP_MIN_SCALE: 0.131,

	// maximum allowed minimap scale
	MINIMAP_MAX_SCALE: 3.38,
};

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// VIEWPOINTS
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

Preferences.Viewpoint =
{
	SNAPSHOT_DIRECTORY_PATH: "data/gui/viewpoint_snapshots/",
	NO_SNAPSHOT_IMAGE_TAKEN_YET: "snaptodo.jpg",

	getPublicViewpoints: function()
	{
		return Preferences.Viewpoint.VIEWPOINTS_IN_DIALOG_LIST;
	}
};

/**
 * Which viewpoints will be displayed in the Viewpoint Dialog Window. The order matters.
 *
 * @type {Viewpoint[]}
 */
Preferences.Viewpoint.VIEWPOINTS_IN_DIALOG_LIST =
[
	new Viewpoint (new THREE.Vector3 (-264.01, 43.28, 75.98), 2.20,0.10, "hradcany_square"),
	new Viewpoint (new THREE.Vector3 (-214.39, 42.79, 56.60), 2.03,0.35, "courtyard1"),
	new Viewpoint (new THREE.Vector3 (-180.43, 42.92, 22.61), 1.03,0.20, "courtyard2"),
	new Viewpoint (new THREE.Vector3 (-155.74, 64.84, 64.22), 3.38, -0.29, "courtyard2_roof1"),
	new Viewpoint (new THREE.Vector3 (-232.78, 42.82,  2.13), 3.18, 0.05, "courtyard4"),
	new Viewpoint (new THREE.Vector3 (-251.19, 44.45, -44.05), 0.73, 0.15, "garden_bastion"),
	new Viewpoint (new THREE.Vector3 (-182.94, 41.49, -80.80), 4.76, -0.15, "stag_moat"),
	new Viewpoint (new THREE.Vector3 (-30.0, 420.0, 90.0), 3.14, -1.4, "castle_from_above", function() { gui.switchWalkFly(true); })
];

Preferences.Viewpoint.VIEWPOINT_DEFAULT = Preferences.Viewpoint.VIEWPOINTS_IN_DIALOG_LIST[0];

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// COLLISIONS
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

Preferences.Collision =
{
	// turning on/off collision detection
	__COLLISION_DETECTION: true,
	COLLISION_DETECTION: null, // (do not manipulate with this there)

	// speed of the falling
	//FALL_ACCELERATION: 1/100000 * 5 // 0.04 for debugging
	// it is so small because it is valid for ms, not s
	FALL_ACCELERATION: 1/100000 * 5,

	// the acceleration is negative so the engine will increase our Y instead of decreasing
	JUMP_ACCELERATION: -(1/100000 * 5) * 2,

	// how long the player's jump will be (in ms), the bigger value, the bigger jump
	MAX_JUMP_DURATION: 900,

	// for detecting the fact that avatar should be falling
	IN_AIR_THRESHOLD_METERS: 0.8, // 0.8

	// for prohibiting avatar to enter walls etc. (heightmap)
	COLLISION_THRESHOLD_METERS: 1.0,// 0.8 gives problems with stairs in the garden

	// if we collide with something during the fly mode, but the difference is less than this value (i.e. there is just 0.5
	COLLISION_THRESHOLD_FLY_METERS: 0.5,

	// this is just a very high number which is assigned to diff variable when player is higher than above limit (i.e. height of the tunnel or world)
	COLLISION_FLY_MODE_HEIGHT_DIFF_TOO_MUCH_CONSTANT: 777777,

	// when checking for collision detection, we move the player (for collision checking) a little bit further
	 // this distance can be thought of limit how close are we allowed to get to the walls
	 // (the problem is, if we don't do that, we may end up exactly in the front of the wall, like 1mm in front of - as you may guess, the wall would be actually culled away by the camera near plane, which is not good!)
	FORWARD_DIRECTION_DISTANCE_LIMIT: 0.775,

	// similar to RIGHT_DIRECTION_DISTANCE_LIMIT_NORMAL
	// during collision detection, we do not check only player's (new position + forward distance limit), but also another other positions
	// one is slightly to the right of the (new position), another is slightly to the left of (new position)
	 // the "slightly to the right of (new position)" is obtained by displacing in by the right vector (and similarly, displacing by -rightVector for "slightly left" case)
	// WHY WE ARE DOING THIS:
	// this is important, because if we only check in the direction of move (direction) vector, then if the direction vector is almost parallel to the wall (i.e. trying to walk into the wall "from side", i.e. the angle between us and the wall is like 5Â°), then guess what?
	// we get "no collision", which is technically true, but since we are close to the wall, by moving forward, part of the wall would get culled away, just like in the previous case
	// so the solution is to check also slightly to the right and slightly to the left of the direction vector, which fires collisions if we are really close to the wall, thus
	// prevents us from moving, thus prevents the part of the wall to be culled
	// note: this is not actually a distance limit, since I forgot to modify
	RIGHT_DIRECTION_DISTANCE_LIMIT: 0.7, // set automatically to RIGHT_DIRECTION_DISTANCE_LIMIT_NORMAL

	// high right (left) distance limit would make moving in tunnels problematic, so we keep it down (yes, it means part of the tunnel walls can be culled away, which is bothersome, but better than unability to fully move)
	RIGHT_DIRECTION_DISTANCE_LIMIT_NORMAL: 0.7,
	RIGHT_DIRECTION_DISTANCE_LIMIT_TUNNEL: 0.1,

	// I have originally intended to revert to normal right direction distance limit after some time, but it's not a good idea
	//RIGHT_DIRECTION_DISTANCE_TUNNEL_TIME_DURATION: 3000
};
Preferences.Collision.RIGHT_DIRECTION_DISTANCE_LIMIT = Preferences.Collision.RIGHT_DIRECTION_DISTANCE_LIMIT_NORMAL;

Object.defineProperty (Preferences.Collision, "COLLISION_DETECTION", {
	get: function(){return Util.getVariable (this, "COLLISION_DETECTION");},
	set: function (value)
	{
		Util.setVariable (this, "COLLISION_DETECTION", value);
		// possibly some other action...
	}
});

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// PICKING
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

Preferences.Picking =
{
	PICKING_ALLOWED: true,
	PICKING_UPDATE_INTERVAL: 500, // how many ms until application performs picking when entering new view

	INFO_CUBE_SIZE_METERS: 1,

	INFO_CUBE_TEXTURE: "data/gui/info_icon.png",

	SHOW_GROUP_CAPTIONS: true, // TRUE - we can either show caption of groups (e.g. "Cathedral" for all models which cathedral consists of) or FALSE - we can show individual name of each model

	BACKGROUND_COLOR: new THREE.Vector4 (1.0, 0.0, 0.0, 1.0),
	NOTHING_PICKED_COLOR_RGBA: new THREE.Vector4 (0, 0, 0, 0),
	PICKING_CAPTION_OFFSET_FROM_MOUSE: new THREE.Vector2 (0,-20),

	// our picking algorithm uses packing into RGB color (A portion is left unused) = 24 bits, so we have 2^24 possible picking ids
	MAXIMUM_PICKING_ID: Math.pow (2,24),

	// if TRUE, we will assign picking ids that will lead to distinguishable colors by a naked eye
	ASSIGN_PICKING_COLORS_DISTINGUISHABLE_BY_EYES: true,

	// turning on/off info cubes
	__INFO_CUBES: true,
	INFO_CUBES: null, // (do not manipulate with this there)

	/**
	 * Used picking algorithm for packing colors actually packs floats into colors, not integers, so we have to convert
	 * out integer ids from integer range [0,2^available bits for color packing] to the float range [0,1].
	 *
	 * Always use this function while assigning picking id to a mesh.
	 *
	 * @param	 {number} integerPickingId integer number in range [0,2^24], e.g. 1, 265, 67896, 15541236
	 * @returns {number} float number in range [0,1] which corresponds to the given integer, this number will be used by color packing algorithm (shader)
	 */
	getPickingIdInRange0to1: function (integerPickingId)
	{
		return (integerPickingId / Preferences.Picking.MAXIMUM_PICKING_ID);
	},

	getPickingIdInIntegerFullRange: function (floatPickingId0to1)
	{
		return Math.floor (floatPickingId0to1 * Preferences.Picking.MAXIMUM_PICKING_ID); // floor because sometimes we get stuff like 200.000456
	}
};

Object.defineProperty (Preferences.Picking, "INFO_CUBES",
	{
		get: function(){return Util.getVariable (this, "INFO_CUBES");},
		set: function (value) {
		 Util.setVariable (this, "INFO_CUBES", value);

		if (value)
		{
			castleScene.showInfoCubes();
		}
		else
		{
			castleScene.hideInfoCubes();
		}

		// possibly some other action...
	}
	});

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// HEIGHTMAP
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

Preferences.Heightmap =
{
	////////////////////////////////////////////////////
	// HEIGHTMAP IMAGES FILEPATHS
	////////////////////////////////////////////////////

	LARGEST_VALUE_LAYER_FILENAME: "data/heightmap/heightmap_largest_layer.png",
	FLOOR_LAYER_FILENAME: "data/heightmap/heightmap_floor_layer.png",
	CEILING_LAYER_FILENAME: "data/heightmap/heightmap_ceiling_layer.png",

	////////////////////////////////////////////////////
	// ON-FLY GENERATION
	////////////////////////////////////////////////////

	// whether the heightmap will be generated on-the-fly at the start (in the current application resolution); warning: this slows the application start, so use with caution (only in development)
	GENERATE_HEIGHTMAP: false,

	// whether the generated heightmap will be exported as an image
	EXPORT_HEIGHTMAP: true,

	// whether we will test if the heightmap snapshot was successfull and possible retry - watch out, if you turn this off, then is is highly probable that heightmap will not be generated
	TEST_FOR_HEIGHTMAP_SUCCESSFULL_SNAPSHOT: true,

	TERMINATE_ON_FAILED_SNAPSHOT: false,

	// how many times we will try to generate heightmap if not successfull (there is a problem with rendering and taking snapshot, look to the WorldHeightmap.js for explanation)
	RETRY_COUNT_MAX_LIMIT_FOR_GENERATION: 10,

	// delay between attempts to generate heightmap
	RETRY_DELAY_FOR_GENERATION_MS: 500,

	////////////////////////////////////////////////////
	// GENERAL
	////////////////////////////////////////////////////

	// background color = nothing in the heightmap at that position
	BACKGROUND_COLOR_RGB: new THREE.Vector3 (0, 0, 0),

	// (fastest way to find this: compute projection of 3D player coordinates to heightmap coordinates and wait for the moment you get out of the image with those coordinates)
	WORLD_3D_BOUNDING_BOX:
	{
		MIN_X: -392,
		MAX_X:	392,
		MIN_Y: -9999, // this value is actually not used
		MAX_Y: 900,	// maximum allowable height for the player
		MIN_Z: -200,
		MAX_Z:	200
	}

	// Note - the orientation in 3D is following:
	//
	//	-Z
	//	/\		----------------------
	//	 |		|										|
	//	 |		|	 Heightmap image	|
	//	 |		|										|
	//	 |		| ortho view of scene|
	//	 |		----------------------
	//	\/	 -X <-------------------> +X
	//	+Z
	//
	// And +Y is going vertically up, -Y is going vertically down.
};

// for more comfortable access
Object.defineProperty(Preferences.Heightmap, "MAXIMUM_ALLOWABLE_HEIGHT",
		{
			get: function()
			{
				return Preferences.Heightmap.WORLD_3D_BOUNDING_BOX.MAX_Y;
			},

			set: function(value)
			{
				Preferences.Heightmap.WORLD_3D_BOUNDING_BOX.MAX_Y = value;
			}
		});

/**
 * Orthographic camera. Currently used only for generating the heightmap.
 *
 * @type {THREE.OrthographicCamera}
 */
Preferences.Heightmap.ORTHO_CAMERA = new THREE.OrthographicCamera (-Preferences.Camera.ORTHO_CLIP, Preferences.Camera.ORTHO_CLIP, Preferences.Camera.ORTHO_CLIP, -Preferences.Camera.ORTHO_CLIP, Preferences.Camera.ORTHO_Z_NEAR_DEFAULT, Preferences.Camera.ORTHO_Z_FAR_DEFAULT);
Preferences.Heightmap.ORTHO_CAMERA.setRotationFromEuler (Preferences.Camera.ORTHO_ROTATION);
Preferences.Heightmap.ORTHO_CAMERA.position.copy (Preferences.Camera.ORTHO_POSITION);
Preferences.Heightmap.ORTHO_CAMERA.updateMatrix();
Preferences.Heightmap.ORTHO_CAMERA.updateMatrixWorld();
Preferences.Heightmap.ORTHO_CAMERA.matrixWorldInverse.getInverse( Preferences.Heightmap.ORTHO_CAMERA.matrixWorld ); // this is super important! the inverse matrix does not update itself, it gets updated in Three.js render loop, but that is too late for us

Preferences.Heightmap.WORLD_HEIGHTMAP_DEFAULT_PROPERTIES = new WorldHeightmapProperties
(
		// it would be pain to detect heightmap image dimensions runtime, so we have to specify them now
		1280, // HEIGHTMAP WIDTH
		1280, // HEIGHTMAP HEIGHT
		//4096, // HEIGHTMAP WIDTH
		//4096, // HEIGHTMAP HEIGHT
		Preferences.Heightmap.ORTHO_CAMERA

		// IMPORTANT: When you change the resolution of heightmap, you also have to change resolution (size) of the minimap image.

		// Note: Keep in mind that in case of very small/thin objects (e.g. iron bars), you have to edit the heightmap manually
		//			 and draw a few pixels in some "higher" color around it.
);

Preferences.Heightmap.LayerType =
{
	LARGEST_VALUE: 0,
	FLOOR:				 1,
	CEILING:			 2
};

Preferences.Heightmap.LayerNames = {};
Preferences.Heightmap.LayerNames[Preferences.Heightmap.LayerType.LARGEST_VALUE] = "largest value layer";
Preferences.Heightmap.LayerNames[Preferences.Heightmap.LayerType.FLOOR]				 = "floor layer";
Preferences.Heightmap.LayerNames[Preferences.Heightmap.LayerType.CEILING]			 = "ceiling layer";

if (Preferences.Heightmap.GENERATE_HEIGHTMAP)
{
	Preferences.Window.WINDOW_DEFAULT_USER_AVAILABLE_WIDTH	= Preferences.Heightmap.WORLD_HEIGHTMAP_DEFAULT_PROPERTIES.width;
	Preferences.Window.WINDOW_DEFAULT_USER_AVAILABLE_HEIGHT = Preferences.Heightmap.WORLD_HEIGHTMAP_DEFAULT_PROPERTIES.height;
}

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// RENDERER
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

Preferences.RenderingContent =
{
	STANDARD:			 0,
	PICKING_BUFFER: 1,
	HEIGHTMAP:			2,
	DEPTH_BUFFER:	 3,
	NO_EFFECTS:		 4
};

Preferences.Renderer =
{
	RENDERING_CONTENT: Preferences.RenderingContent.STANDARD,
	//RENDERING_CONTENT: Preferences.RenderingContent.NO_EFFECTS,

	RENDER_WIDTH:	Preferences.Window.WINDOW_DEFAULT_USER_AVAILABLE_WIDTH,
	RENDER_HEIGHT: Preferences.Window.WINDOW_DEFAULT_USER_AVAILABLE_HEIGHT,

	NATIVE_GL_RENDERING: false,

	// UPDATE: with newer versions of Firefox (e.g. 50), this is no longer true, so this must be always set to true
	//SOURCE_AND_DESTINATION_TEXTURE_CANNOT_BE_SAME: Preferences.Browser.CHROME,
	SOURCE_AND_DESTINATION_TEXTURE_CANNOT_BE_SAME: true,

	__SHOW_FPS: Preferences.Application.isDevMode(),
	SHOW_FPS: null, // (do not modify this there)

	// special flag used for SSAO pass in modified three.js, see VisualEffects.js for explanation
	doNotRenderSpritesWithOverrideMaterial: false
};

Object.defineProperty (Preferences.Renderer, "SHOW_FPS",
		{
			get: function(){return Util.getVariable (this, "SHOW_FPS");},
			set: function (value)
			{
				Util.setVariable (this, "SHOW_FPS", value);

				if (value)
				{
					$(statsThree.domElement).show();
				}
				else
				{
					$(statsThree.domElement).hide();
				}
			}
		});

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// EFFECTS
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

Preferences.VisualEffects =
{
	// Browser antialiasing (request for WebGL)
	MSAA: false,

	__BUMP_AND_NORMAL_MAPS: true,
	BUMP_AND_NORMAL_MAPS: null, // (do not set this variable manually there)


	__SHADOWS: true,
	SHADOWS: null, // (do not set this variable manually there)


	FORCE_PHONG_SHADING: false,

	// sky shader + shadow mapping
	//SUN_AND_SKY: true, // note: we actually don't render shadow maps each frame, only when lights change (i.e. sun), which does not happen often

	__NIGHT_LIGHTS: true,
	NIGHT_LIGHTS: true,

	// type of shadow mapping
	SHADOW_MAP_TYPE: THREE.PCFSoftShadowMap,
	SHADOW_MAP_TEXTURE_SIZE_SUN: new THREE.Vector2 (4096, 4096),
	//SHADOW_MAP_TEXTURE_SIZE_SUN: new THREE.Vector2 (8192, 8192), // too much
	SHADOW_MAP_SHADOW_DARKNESS: 0.5,
	SHADOW_MAP_SHADOW_BIAS: -0.00000030, // -0.00000045

	SKY: true,

	POST_PROCESSING:
	{
		// Fast Approximation Anti Aliasing
		FXAA: true,

		// Screen Space Ambient Occlusion
		SSAO: !true,
			__SSAO_AO_CLAMP: 0.7, // default 0.3
			__SSAO_LUM_INFLUENCE: 1.0, // default 0.5

		SSAO_setClamp: function (aoClamp)
									 {
										 if (VisualEffects.passes["SSAO"])
										 {
											 Preferences.VisualEffects.POST_PROCESSING.__SSAO_AO_CLAMP = aoClamp;
											 VisualEffects.passes["SSAO"].uniforms["aoClamp"].value			= Preferences.VisualEffects.POST_PROCESSING.__SSAO_AO_CLAMP;
										 }
									 },

		SSAO_setInfluence: function (influence)
									 {
										 if (VisualEffects.passes["SSAO"])
										 {
											 Preferences.VisualEffects.POST_PROCESSING.__SSAO_LUM_INFLUENCE = influence;
											 VisualEffects.passes["SSAO"].uniforms["lumInfluence"].value = Preferences.VisualEffects.POST_PROCESSING.__SSAO_LUM_INFLUENCE;
										 }
									 },

		// pickable objects will be rendered normally + semi-transparently in their picking color; you can then immediately see pickable objects in the scene
		PICKING_VISUALIZATION: !true,

		LENS_FLARE: !true,
			__LENS_FLARE_SHOW_GUI: true // will show GUI for changing parameters of the lens flare
	}
};
//Preferences.VisualEffects.POST_PROCESSING._enumNames = Util.getEnumKeys (Preferences.VisualEffects.POST_PROCESSING);
Util.generatePrivateVariablesForGetters(Preferences.VisualEffects.POST_PROCESSING);

// hook setters that will automatically update post-processing passes configuration if any if effect is turned on/off

// we have to define getters and setter manually like idiots :(
Object.defineProperty (Preferences.VisualEffects.POST_PROCESSING, "FXAA",
		{
			get: function(){return Util.getVariable (this, "FXAA");},
			set: function (value)
			{
				Util.setVariable (this, "FXAA", value);

				VisualEffects.updatePostprocessingPasses();
			}
		});
Object.defineProperty (Preferences.VisualEffects.POST_PROCESSING, "SSAO",
		{
			get: function(){return Util.getVariable (this, "SSAO");},
			set: function (value)
			{
				Util.setVariable (this, "SSAO", value);

				VisualEffects.updatePostprocessingPasses();
			}
		});
Object.defineProperty (Preferences.VisualEffects.POST_PROCESSING, "PICKING_VISUALIZATION",
		{
			get: function(){return Util.getVariable (this, "PICKING_VISUALIZATION");},
			set: function (value)
			{
				Util.setVariable (this, "PICKING_VISUALIZATION", value);

				VisualEffects.updatePostprocessingPasses();
			}
		});
Object.defineProperty (Preferences.VisualEffects.POST_PROCESSING, "LENS_FLARE",
		{
			get: function(){return Util.getVariable (this, "LENS_FLARE");},
			set: function (value)
			{
				Util.setVariable (this, "LENS_FLARE", value);

				VisualEffects.updatePostprocessingPasses();
			}
		});

Object.defineProperty (Preferences.VisualEffects, "BUMP_AND_NORMAL_MAPS",
		{
			get: function(){return Util.getVariable (this, "BUMP_AND_NORMAL_MAPS");},
			set: function (value)
			{
				Util.setVariable (this, "BUMP_AND_NORMAL_MAPS", value);

				castleScene.setNormalAndBumpMapsVisibility (value);

				console.log("Normal mapping set to: " + value);
			}
		});

Object.defineProperty (Preferences.VisualEffects, "SHADOWS",
		{
			get: function(){return Util.getVariable (this, "SHADOWS");},
			set: function (value)
			{
				Util.setVariable (this, "SHADOWS", value);

				renderer.initShadows();

				// recompile shaders, otherwise they would not take into the account our new value for shadowmaps
				castleScene.updateMaterials();
			}
		});

Object.defineProperty (Preferences.VisualEffects, "NIGHT_LIGHTS",
		{
			get: function(){return Util.getVariable (this, "NIGHT_LIGHTS");},
			set: function (value)
			{
				Util.setVariable (this, "NIGHT_LIGHTS", value);

				if (value)
				{
					// also switch lights on, but only if the sun is down

					if (!castleScene.sun.visible)
					{
						castleScene.turnNightLights (true);
					}
					else
					{
						castleScene.turnNightLights (false);
					}
				}
				// switch lights off
				else
				{
					castleScene.turnNightLights (false);
				}
			}
		});


if (Preferences.VisualEffects.MSAA && Preferences.VisualEffects.POST_PROCESSING.FXAA)
{
	console.warn("Both MSAA and FXAA options are turned on. I don't think you really wanted this.");
}

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

Preferences.Lights =
{
	////////////////////////////////////////////
	// SUN AND DAY/NIGHT SIMULATION
	////////////////////////////////////////////

	TIME_OF_THE_DAY: 7,
	TIME_SIMULATION: true,
	TIME_SIMULATION_SPEED_INC: 180, // (3 / 60) 3mins increase each 1000ms

	SUN_UPDATE_INTERVAL_FRAMES: 15,
	SUN_MESH_COLOR: new THREE.Color ("white"),
	SUN_MESH_SIZE: 20000,
	SUN_MESH_SEGMENTS: new THREE.Vector2 (32, 16),
	SHOW_GUI_FOR_SUN: true,
	SUN_SHADER_PARAMETERS:
	{
		sun: true,
		turbidity: 10,
		reileigh: 2,
		mieCoefficient: 0.005,
		mieDirectionalG: 0.8,
		luminance: 1,

		// !!!!!!!!!!
		inclination: 0.87, // how high on the sky the sun is (i.e. time of the year), you can adjust this
		azimuth: 0.67 // this is horizontal movement of the sun, i.e. movement from dusk till dawn (time of the day); you don't adjust this, this is computed based on time

		// note: there are 2 situations when it is day
		// A:
		// Inc is [0,0.5]
		// Azi is [0,0.5] and goes from	0.5 -> 0.
		//
		// B:
		// Inc is [0.51,1]
		// Azi is [0.51,1] and goes from 0.51 -> 1.
		//
		// The noon (highest sun position) happens when Azi == Inc.
	},

	// aka sun's top position
	UP_VECTOR: new THREE.Vector3 (0,1,0),

	// see THREE.Sky for the cutoffAngle /though we had to modify it to 1.93 in JS computations/, the problem is that the spherical computations which are computing positions often end up their interval e.g. at 90 deg, but we need to end it at something like 92 deg so it looks better and the sun is really over the horizon
	SUN_AZIMUTH_ANGLE_CORRECTION: (Math.PI/1.93) - Math.PI/2,

	SUN_DISTANCE: 400000,
	SUN_DISPLAY_BIG_MESH: true,
	SUN_SHADOW_CAMERA_FAR: 2000000,
	SUN_SHADOW_CAMERA_NEAR: 5000,

	SCENE_DEFAULT_AMBIENT_LIGHT:		 new THREE.AmbientLight (new THREE.Color("hsl(0, 0%, 27%)").getHex()), // rgb(68, 68, 68), hsl(0, 0, 27), 0x444444

	// this initial color doesn't matter, intensity is computed automatically based on time (06:00 = lowest, 12:00 = highest, 18:00 = lowest, and 18:00 - 06:00 is sun not visible)
	SCENE_DEFAULT_DIRECTIONAL_LIGHT: new THREE.DirectionalLight (new THREE.Color("hsl(0, 0%, 100%)").getHex()),
	SCENE_DEFAULT_DIRECTIONAL_LIGHT_POSITION: new THREE.Vector3 (0, 1, 0).normalize(),

	////////////////////////////////////////////
	// TERRAIN AMBIENT LIGHTING
	////////////////////////////////////////////

	//SCENE_TERRAIN_AMBIENT_LIGHT_AT_DAY: new THREE.AmbientLight (new THREE.Color(0.2, 0.2, 0.2).getHex()),//
	SCENE_TERRAIN_AMBIENT_LIGHT_AT_DAY: new THREE.AmbientLight (new THREE.Color(0.4, 0.4, 0.4).getHex()),//
	SCENE_TERRAIN_AMBIENT_LIGHT_AT_DUSK_OR_DAWN: new THREE.AmbientLight (new THREE.Color(0.2, 0.1, 0.05).getHex()),//
	SCENE_TERRAIN_AMBIENT_LIGHT_AT_NIGHT: new THREE.AmbientLight (new THREE.Color(0.012,0.012,0.012).getHex()),

	// 3 phases for terrain's ambient light: day (zenith angle, i.e. sun altitude complement to 90 deg, is from 0 to 81.2deg), dusk or dawn (81.2deg - 90deg), night (90deg+)
	STARTING_DAY_ZENITH_ANGLE: 0, //
	STARTING_DUSK_OR_DAWN_ZENITH_ANGLE: 1.4173509033725709, // 81.2 deg
	STARTING_NIGHT_ZENITH_ANGLE: Math.PI/1.95 // 90 deg - no need to specify but left here for clarity reasons, we compute with hardcoded version of this value in the sky computations, because that's just how the sun cycle works
};
Preferences.Lights.SUN_INCLINATION = Preferences.Lights.SUN_SHADER_PARAMETERS.inclination;
Preferences.Lights.SUN_AZIMUTH = Preferences.Lights.SUN_SHADER_PARAMETERS.azimuth;

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

Preferences.Multiplayer = {
	SHOW_USERS: true,
}
