<commit_before>/*
 * Copyright (c) 2012-2015 Daniele Bartolini and individual contributors.
 * License: https://github.com/taylor001/crown/blob/master/LICENSE
 */

#include "device.h"
#include "array.h"
#include "config.h"
#include "debug_line.h"
#include "input_manager.h"
#include "log.h"
#include "lua_environment.h"
#include "material_manager.h"
#include "memory.h"
#include "os.h"
#include "resource_loader.h"
#include "resource_manager.h"
#include "resource_package.h"
#include "types.h"
#include "world.h"
#include "filesystem.h"
#include "path.h"
#include "disk_filesystem.h"
#include "physics.h"
#include "audio.h"
#include "profiler.h"
#include "console_server.h"
#include "input_device.h"
#include "njson.h"
#include "map.h"

#if CROWN_PLATFORM_ANDROID
	#include "apk_filesystem.h"
#endif // CROWN_PLATFORM_ANDROID

#define MAX_SUBSYSTEMS_HEAP 8 * 1024 * 1024

namespace crown
{

Device::Device(DeviceOptions& opts)
	: _allocator(default_allocator(), MAX_SUBSYSTEMS_HEAP)
	, _width(0)
	, _height(0)
	, _mouse_curr_x(0)
	, _mouse_curr_y(0)
	, _mouse_last_x(0)
	, _mouse_last_y(0)
	, _is_init(false)
	, _is_running(false)
	, _is_paused(false)
	, _frame_count(0)
	, _last_time(0)
	, _current_time(0)
	, _last_delta_time(0.0f)
	, _time_since_start(0.0)
	, _device_options(opts)
	, _bundle_filesystem(NULL)
	, _boot_package_id(uint64_t(0))
	, _boot_script_id(uint64_t(0))
	, _boot_package(NULL)
	, _lua_environment(NULL)
	, _resource_loader(NULL)
	, _resource_manager(NULL)
	, _input_manager(NULL)
	, _worlds(default_allocator())
	, _bgfx_allocator(default_allocator())
{
}

void Device::init()
{
	// Initialize
	CE_LOGI("Initializing Crown Engine %s...", version());

	profiler_globals::init();

#if CROWN_PLATFORM_ANDROID
	_bundle_filesystem = CE_NEW(_allocator, ApkFilesystem)(_device_options.asset_manager());
#else
	_bundle_filesystem = CE_NEW(_allocator, DiskFilesystem)(_device_options.bundle_dir());
#endif // CROWN_PLATFORM_ANDROID

	_resource_loader = CE_NEW(_allocator, ResourceLoader)(*_bundle_filesystem);
	_resource_manager = CE_NEW(_allocator, ResourceManager)(*_resource_loader);

	read_config();

	_input_manager = CE_NEW(_allocator, InputManager)();

	audio_globals::init();
	physics_globals::init();

	bgfx::init(bgfx::RendererType::Count
		, BGFX_PCI_ID_NONE
		, 0
		, &_bgfx_callback
		, &_bgfx_allocator
		);

	material_manager::init();
	debug_line::init();

	_boot_package = create_resource_package(_boot_package_id);
	_boot_package->load();
	_boot_package->flush();

	_lua_environment = CE_NEW(_allocator, LuaEnvironment)();
	_lua_environment->load_libs();
	_lua_environment->execute((LuaResource*)_resource_manager->get(SCRIPT_TYPE, _boot_script_id));
	_lua_environment->call_global("init", 0);

	_is_init = true;
	_is_running = true;
	_last_time = os::clocktime();

	CE_LOGD("Engine initialized");
}

void Device::shutdown()
{
	CE_ASSERT(_is_init, "Engine is not initialized");

	_is_running = false;
	_is_init = false;

	_lua_environment->call_global("shutdown", 0);
	CE_DELETE(_allocator, _lua_environment);

	_boot_package->unload();
	destroy_resource_package(*_boot_package);

	debug_line::shutdown();
	material_manager::shutdown();

	bgfx::shutdown();

	physics_globals::shutdown();
	audio_globals::shutdown();

	CE_DELETE(_allocator, _input_manager);
	CE_DELETE(_allocator, _resource_manager);
	CE_DELETE(_allocator, _resource_loader);
	CE_DELETE(_allocator, _bundle_filesystem);

	profiler_globals::shutdown();

	_allocator.clear();
}

void Device::quit()
{
	_is_running = false;
}

void Device::pause()
{
	_is_paused = true;
	CE_LOGI("Engine paused.");
}

void Device::unpause()
{
	_is_paused = false;
	CE_LOGI("Engine unpaused.");
}

void Device::resolution(uint16_t& width, uint16_t& height)
{
	width = _width;
	height = _height;
}

bool Device::is_running() const
{
	return _is_running;
}

uint64_t Device::frame_count() const
{
	return _frame_count;
}

float Device::last_delta_time() const
{
	return _last_delta_time;
}

double Device::time_since_start() const
{
	return _time_since_start;
}

void Device::update()
{
	while (!process_events() && _is_running)
	{
		_current_time = os::clocktime();
		const int64_t time = _current_time - _last_time;
		_last_time = _current_time;
		const double freq = (double) os::clockfrequency();
		_last_delta_time = time * (1.0 / freq);
		_time_since_start += _last_delta_time;

		profiler_globals::clear();
		console_server_globals::update();

		RECORD_FLOAT("device.dt", _last_delta_time);
		RECORD_FLOAT("device.fps", 1.0f/_last_delta_time);

		if (!_is_paused)
		{
			_resource_manager->complete_requests();
			_lua_environment->call_global("update", 1, ARGUMENT_FLOAT, last_delta_time());
			_lua_environment->call_global("render", 1, ARGUMENT_FLOAT, last_delta_time());
		}

		_input_manager->update();

		const bgfx::Stats* stats = bgfx::getStats();
		RECORD_FLOAT("bgfx.gpu_time", double(stats->gpuTimeEnd - stats->gpuTimeBegin)*1000.0/stats->gpuTimerFreq);
		RECORD_FLOAT("bgfx.cpu_time", double(stats->cpuTimeEnd - stats->cpuTimeBegin)*1000.0/stats->cpuTimerFreq);

		bgfx::frame();
		profiler_globals::flush();

		_lua_environment->clear_temporaries();

		_frame_count++;
	}
}

void Device::render_world(World& world, Camera* camera)
{
	world.render(camera);
}

World* Device::create_world()
{
	World* w = CE_NEW(default_allocator(), World)(*_resource_manager, *_lua_environment);
	array::push_back(_worlds, w);
	return w;
}

void Device::destroy_world(World& w)
{
	for (uint32_t i = 0, n = array::size(_worlds); i < n; ++i)
	{
		if (&w == _worlds[i])
		{
			CE_DELETE(default_allocator(), &w);
			_worlds[i] = _worlds[n - 1];
			array::pop_back(_worlds);
			return;
		}
	}

	CE_ASSERT(false, "Bad world");
}

ResourcePackage* Device::create_resource_package(StringId64 id)
{
	return CE_NEW(default_allocator(), ResourcePackage)(id, *_resource_manager);
}

void Device::destroy_resource_package(ResourcePackage& package)
{
	CE_DELETE(default_allocator(), &package);
}

void Device::reload(StringId64 type, StringId64 name)
{
	const void* old_resource = _resource_manager->get(type, name);
	_resource_manager->reload(type, name);
	const void* new_resource = _resource_manager->get(type, name);

	if (type == SCRIPT_TYPE)
	{
		_lua_environment->execute((const LuaResource*)new_resource);
	}
}

ResourceManager* Device::resource_manager()
{
	return _resource_manager;
}

LuaEnvironment* Device::lua_environment()
{
	return _lua_environment;
}

InputManager* Device::input_manager()
{
	return _input_manager;
}

bool Device::process_events()
{
	OsEvent event;
	bool exit = false;
	InputManager* im = _input_manager;

	const int16_t dt_x = _mouse_curr_x - _mouse_last_x;
	const int16_t dt_y = _mouse_curr_y - _mouse_last_y;
	im->mouse()->set_axis(MouseAxis::CURSOR_DELTA, vector3(dt_x, dt_y, 0.0f));
	_mouse_last_x = _mouse_curr_x;
	_mouse_last_y = _mouse_curr_y;

	while(next_event(event))
	{
		if (event.type == OsEvent::NONE) continue;

		switch (event.type)
		{
			case OsEvent::TOUCH:
			{
				const OsTouchEvent& ev = event.touch;
				switch (ev.type)
				{
					case OsTouchEvent::POINTER:
						im->touch()->set_button_state(ev.pointer_id, ev.pressed);
						break;
					case OsTouchEvent::MOVE:
						im->touch()->set_axis(ev.pointer_id, vector3(ev.x, ev.y, 0.0f));
						break;
					default:
						CE_FATAL("Unknown touch event type");
						break;
				}
				break;
			}
			case OsEvent::MOUSE:
			{
				const OsMouseEvent& ev = event.mouse;
				switch (ev.type)
				{
					case OsMouseEvent::BUTTON:
						im->mouse()->set_button_state(ev.button, ev.pressed);
						break;
					case OsMouseEvent::MOVE:
						_mouse_curr_x = ev.x;
						_mouse_curr_y = ev.y;
						im->mouse()->set_axis(MouseAxis::CURSOR, vector3(ev.x, ev.y, 0.0f));
						break;
					case OsMouseEvent::WHEEL:
						im->mouse()->set_axis(MouseAxis::WHEEL, vector3(0.0f, ev.wheel, 0.0f));
						break;
					default:
						CE_FATAL("Unknown mouse event type");
						break;
				}
				break;
			}
			case OsEvent::KEYBOARD:
			{
				const OsKeyboardEvent& ev = event.keyboard;
				im->keyboard()->set_button_state(ev.button, ev.pressed);
				break;
			}
			case OsEvent::JOYPAD:
			{
				const OsJoypadEvent& ev = event.joypad;
				switch (ev.type)
				{
					case OsJoypadEvent::CONNECTED:
						im->joypad(ev.index)->set_connected(ev.connected);
						break;
					case OsJoypadEvent::BUTTON:
						im->joypad(ev.index)->set_button_state(ev.button, ev.pressed);
						break;
					case OsJoypadEvent::AXIS:
						im->joypad(ev.index)->set_axis(ev.button, vector3(ev.x, ev.y, ev.z));
						break;
					default:
						CE_FATAL("Unknown joypad event");
						break;
				}
				break;
			}
			case OsEvent::METRICS:
			{
				const OsMetricsEvent& ev = event.metrics;
				_width = ev.width;
				_height = ev.height;
				bgfx::reset(ev.width, ev.height, BGFX_RESET_VSYNC);
				break;
			}
			case OsEvent::EXIT:
			{
				exit = true;
				break;
			}
			case OsEvent::PAUSE:
			{
				pause();
				break;
			}
			case OsEvent::RESUME:
			{
				unpause();
				break;
			}
			default:
			{
				CE_FATAL("Unknown Os Event");
				break;
			}
		}
	}

	return exit;
}

void Device::read_config()
{
	TempAllocator4096 ta;
	DynamicString project_path(ta);

	if (_device_options.project() != NULL)
	{
		project_path += _device_options.project();
		project_path += '/';
	}

	project_path += CROWN_BOOT_CONFIG;

	const StringId64 config_name(project_path.c_str());

	_resource_manager->load(CONFIG_TYPE, config_name);
	_resource_manager->flush();
	const char* cfile = (const char*)_resource_manager->get(CONFIG_TYPE, config_name);

	JsonObject config(ta);
	njson::parse(cfile, config);

	_boot_script_id  = njson::parse_resource_id(config["boot_script"]);
	_boot_package_id = njson::parse_resource_id(config["boot_package"]);

	_resource_manager->unload(CONFIG_TYPE, config_name);
}

char _buffer[sizeof(Device)];
Device* _device = NULL;

void init(DeviceOptions& opts)
{
	CE_ASSERT(_device == NULL, "Crown already initialized");
	_device = new (_buffer) Device(opts);
	_device->init();
}

void update()
{
	_device->update();
}

void shutdown()
{
	_device->shutdown();
	_device->~Device();
	_device = NULL;
}

Device* device()
{
	return crown::_device;
}

} // namespace crown
<commit_msg>Fix initialization oreder<commit_after>/*
 * Copyright (c) 2012-2015 Daniele Bartolini and individual contributors.
 * License: https://github.com/taylor001/crown/blob/master/LICENSE
 */

#include "device.h"
#include "array.h"
#include "config.h"
#include "debug_line.h"
#include "input_manager.h"
#include "log.h"
#include "lua_environment.h"
#include "material_manager.h"
#include "memory.h"
#include "os.h"
#include "resource_loader.h"
#include "resource_manager.h"
#include "resource_package.h"
#include "types.h"
#include "world.h"
#include "filesystem.h"
#include "path.h"
#include "disk_filesystem.h"
#include "physics.h"
#include "audio.h"
#include "profiler.h"
#include "console_server.h"
#include "input_device.h"
#include "njson.h"
#include "map.h"

#if CROWN_PLATFORM_ANDROID
	#include "apk_filesystem.h"
#endif // CROWN_PLATFORM_ANDROID

#define MAX_SUBSYSTEMS_HEAP 8 * 1024 * 1024

namespace crown
{

Device::Device(DeviceOptions& opts)
	: _allocator(default_allocator(), MAX_SUBSYSTEMS_HEAP)
	, _width(0)
	, _height(0)
	, _mouse_curr_x(0)
	, _mouse_curr_y(0)
	, _mouse_last_x(0)
	, _mouse_last_y(0)
	, _is_init(false)
	, _is_running(false)
	, _is_paused(false)
	, _frame_count(0)
	, _last_time(0)
	, _current_time(0)
	, _last_delta_time(0.0f)
	, _time_since_start(0.0)
	, _device_options(opts)
	, _bundle_filesystem(NULL)
	, _boot_package_id(uint64_t(0))
	, _boot_script_id(uint64_t(0))
	, _boot_package(NULL)
	, _lua_environment(NULL)
	, _resource_loader(NULL)
	, _resource_manager(NULL)
	, _input_manager(NULL)
	, _worlds(default_allocator())
	, _bgfx_allocator(default_allocator())
{
}

void Device::init()
{
	// Initialize
	CE_LOGI("Initializing Crown Engine %s...", version());

	profiler_globals::init();

#if CROWN_PLATFORM_ANDROID
	_bundle_filesystem = CE_NEW(_allocator, ApkFilesystem)(_device_options.asset_manager());
#else
	_bundle_filesystem = CE_NEW(_allocator, DiskFilesystem)(_device_options.bundle_dir());
#endif // CROWN_PLATFORM_ANDROID

	_resource_loader = CE_NEW(_allocator, ResourceLoader)(*_bundle_filesystem);
	_resource_manager = CE_NEW(_allocator, ResourceManager)(*_resource_loader);

	read_config();

	_input_manager = CE_NEW(_allocator, InputManager)();

	bgfx::init(bgfx::RendererType::Count
		, BGFX_PCI_ID_NONE
		, 0
		, &_bgfx_callback
		, &_bgfx_allocator
		);

	material_manager::init();
	debug_line::init();

	audio_globals::init();
	physics_globals::init();

	_boot_package = create_resource_package(_boot_package_id);
	_boot_package->load();
	_boot_package->flush();

	_lua_environment = CE_NEW(_allocator, LuaEnvironment)();
	_lua_environment->load_libs();
	_lua_environment->execute((LuaResource*)_resource_manager->get(SCRIPT_TYPE, _boot_script_id));
	_lua_environment->call_global("init", 0);

	_is_init = true;
	_is_running = true;
	_last_time = os::clocktime();

	CE_LOGD("Engine initialized");
}

void Device::shutdown()
{
	CE_ASSERT(_is_init, "Engine is not initialized");

	_is_running = false;
	_is_init = false;

	_lua_environment->call_global("shutdown", 0);
	CE_DELETE(_allocator, _lua_environment);

	_boot_package->unload();
	destroy_resource_package(*_boot_package);

	physics_globals::shutdown();
	audio_globals::shutdown();

	debug_line::shutdown();
	material_manager::shutdown();

	CE_DELETE(_allocator, _input_manager);
	CE_DELETE(_allocator, _resource_manager);
	CE_DELETE(_allocator, _resource_loader);
	CE_DELETE(_allocator, _bundle_filesystem);

	bgfx::shutdown();

	profiler_globals::shutdown();

	_allocator.clear();
}

void Device::quit()
{
	_is_running = false;
}

void Device::pause()
{
	_is_paused = true;
	CE_LOGI("Engine paused.");
}

void Device::unpause()
{
	_is_paused = false;
	CE_LOGI("Engine unpaused.");
}

void Device::resolution(uint16_t& width, uint16_t& height)
{
	width = _width;
	height = _height;
}

bool Device::is_running() const
{
	return _is_running;
}

uint64_t Device::frame_count() const
{
	return _frame_count;
}

float Device::last_delta_time() const
{
	return _last_delta_time;
}

double Device::time_since_start() const
{
	return _time_since_start;
}

void Device::update()
{
	while (!process_events() && _is_running)
	{
		_current_time = os::clocktime();
		const int64_t time = _current_time - _last_time;
		_last_time = _current_time;
		const double freq = (double) os::clockfrequency();
		_last_delta_time = time * (1.0 / freq);
		_time_since_start += _last_delta_time;

		profiler_globals::clear();
		console_server_globals::update();

		RECORD_FLOAT("device.dt", _last_delta_time);
		RECORD_FLOAT("device.fps", 1.0f/_last_delta_time);

		if (!_is_paused)
		{
			_resource_manager->complete_requests();
			_lua_environment->call_global("update", 1, ARGUMENT_FLOAT, last_delta_time());
			_lua_environment->call_global("render", 1, ARGUMENT_FLOAT, last_delta_time());
		}

		_input_manager->update();

		const bgfx::Stats* stats = bgfx::getStats();
		RECORD_FLOAT("bgfx.gpu_time", double(stats->gpuTimeEnd - stats->gpuTimeBegin)*1000.0/stats->gpuTimerFreq);
		RECORD_FLOAT("bgfx.cpu_time", double(stats->cpuTimeEnd - stats->cpuTimeBegin)*1000.0/stats->cpuTimerFreq);

		bgfx::frame();
		profiler_globals::flush();

		_lua_environment->clear_temporaries();

		_frame_count++;
	}
}

void Device::render_world(World& world, Camera* camera)
{
	world.render(camera);
}

World* Device::create_world()
{
	World* w = CE_NEW(default_allocator(), World)(*_resource_manager, *_lua_environment);
	array::push_back(_worlds, w);
	return w;
}

void Device::destroy_world(World& w)
{
	for (uint32_t i = 0, n = array::size(_worlds); i < n; ++i)
	{
		if (&w == _worlds[i])
		{
			CE_DELETE(default_allocator(), &w);
			_worlds[i] = _worlds[n - 1];
			array::pop_back(_worlds);
			return;
		}
	}

	CE_ASSERT(false, "Bad world");
}

ResourcePackage* Device::create_resource_package(StringId64 id)
{
	return CE_NEW(default_allocator(), ResourcePackage)(id, *_resource_manager);
}

void Device::destroy_resource_package(ResourcePackage& package)
{
	CE_DELETE(default_allocator(), &package);
}

void Device::reload(StringId64 type, StringId64 name)
{
	const void* old_resource = _resource_manager->get(type, name);
	_resource_manager->reload(type, name);
	const void* new_resource = _resource_manager->get(type, name);

	if (type == SCRIPT_TYPE)
	{
		_lua_environment->execute((const LuaResource*)new_resource);
	}
}

ResourceManager* Device::resource_manager()
{
	return _resource_manager;
}

LuaEnvironment* Device::lua_environment()
{
	return _lua_environment;
}

InputManager* Device::input_manager()
{
	return _input_manager;
}

bool Device::process_events()
{
	OsEvent event;
	bool exit = false;
	InputManager* im = _input_manager;

	const int16_t dt_x = _mouse_curr_x - _mouse_last_x;
	const int16_t dt_y = _mouse_curr_y - _mouse_last_y;
	im->mouse()->set_axis(MouseAxis::CURSOR_DELTA, vector3(dt_x, dt_y, 0.0f));
	_mouse_last_x = _mouse_curr_x;
	_mouse_last_y = _mouse_curr_y;

	while(next_event(event))
	{
		if (event.type == OsEvent::NONE) continue;

		switch (event.type)
		{
			case OsEvent::TOUCH:
			{
				const OsTouchEvent& ev = event.touch;
				switch (ev.type)
				{
					case OsTouchEvent::POINTER:
						im->touch()->set_button_state(ev.pointer_id, ev.pressed);
						break;
					case OsTouchEvent::MOVE:
						im->touch()->set_axis(ev.pointer_id, vector3(ev.x, ev.y, 0.0f));
						break;
					default:
						CE_FATAL("Unknown touch event type");
						break;
				}
				break;
			}
			case OsEvent::MOUSE:
			{
				const OsMouseEvent& ev = event.mouse;
				switch (ev.type)
				{
					case OsMouseEvent::BUTTON:
						im->mouse()->set_button_state(ev.button, ev.pressed);
						break;
					case OsMouseEvent::MOVE:
						_mouse_curr_x = ev.x;
						_mouse_curr_y = ev.y;
						im->mouse()->set_axis(MouseAxis::CURSOR, vector3(ev.x, ev.y, 0.0f));
						break;
					case OsMouseEvent::WHEEL:
						im->mouse()->set_axis(MouseAxis::WHEEL, vector3(0.0f, ev.wheel, 0.0f));
						break;
					default:
						CE_FATAL("Unknown mouse event type");
						break;
				}
				break;
			}
			case OsEvent::KEYBOARD:
			{
				const OsKeyboardEvent& ev = event.keyboard;
				im->keyboard()->set_button_state(ev.button, ev.pressed);
				break;
			}
			case OsEvent::JOYPAD:
			{
				const OsJoypadEvent& ev = event.joypad;
				switch (ev.type)
				{
					case OsJoypadEvent::CONNECTED:
						im->joypad(ev.index)->set_connected(ev.connected);
						break;
					case OsJoypadEvent::BUTTON:
						im->joypad(ev.index)->set_button_state(ev.button, ev.pressed);
						break;
					case OsJoypadEvent::AXIS:
						im->joypad(ev.index)->set_axis(ev.button, vector3(ev.x, ev.y, ev.z));
						break;
					default:
						CE_FATAL("Unknown joypad event");
						break;
				}
				break;
			}
			case OsEvent::METRICS:
			{
				const OsMetricsEvent& ev = event.metrics;
				_width = ev.width;
				_height = ev.height;
				bgfx::reset(ev.width, ev.height, BGFX_RESET_VSYNC);
				break;
			}
			case OsEvent::EXIT:
			{
				exit = true;
				break;
			}
			case OsEvent::PAUSE:
			{
				pause();
				break;
			}
			case OsEvent::RESUME:
			{
				unpause();
				break;
			}
			default:
			{
				CE_FATAL("Unknown Os Event");
				break;
			}
		}
	}

	return exit;
}

void Device::read_config()
{
	TempAllocator4096 ta;
	DynamicString project_path(ta);

	if (_device_options.project() != NULL)
	{
		project_path += _device_options.project();
		project_path += '/';
	}

	project_path += CROWN_BOOT_CONFIG;

	const StringId64 config_name(project_path.c_str());

	_resource_manager->load(CONFIG_TYPE, config_name);
	_resource_manager->flush();
	const char* cfile = (const char*)_resource_manager->get(CONFIG_TYPE, config_name);

	JsonObject config(ta);
	njson::parse(cfile, config);

	_boot_script_id  = njson::parse_resource_id(config["boot_script"]);
	_boot_package_id = njson::parse_resource_id(config["boot_package"]);

	_resource_manager->unload(CONFIG_TYPE, config_name);
}

char _buffer[sizeof(Device)];
Device* _device = NULL;

void init(DeviceOptions& opts)
{
	CE_ASSERT(_device == NULL, "Crown already initialized");
	_device = new (_buffer) Device(opts);
	_device->init();
}

void update()
{
	_device->update();
}

void shutdown()
{
	_device->shutdown();
	_device->~Device();
	_device = NULL;
}

Device* device()
{
	return crown::_device;
}

} // namespace crown
<|endoftext|>


<commit_before>//Copyright (c) 2020 Ultimaker B.V.
//CuraEngine is released under the terms of the AGPLv3 or higher.

#include <algorithm>

#include "ExtrusionLine.h"
#include "linearAlg2D.h"

namespace cura
{

ExtrusionLine::ExtrusionLine(const size_t inset_idx, const bool is_odd)
: inset_idx(inset_idx)
, is_odd(is_odd)
, is_closed(false)
{}

coord_t ExtrusionLine::getLength() const
{
    if (junctions.empty())
    {
        return 0;
    }
    coord_t len = 0;
    ExtrusionJunction prev = junctions.front();
    for (const ExtrusionJunction& next : junctions)
    {
        len += vSize(next.p - prev.p);
        prev = next;
    }
    if (is_closed)
    {
        len += vSize(front().p - back().p);
    }
    return len;
}

coord_t ExtrusionLine::getMinimalWidth() const
{
    return std::min_element(junctions.cbegin(), junctions.cend(),
                            [](const ExtrusionJunction& l, const ExtrusionJunction& r)
                            {
                                return l.w < r.w;
                            })->w;
}

void ExtrusionLine::simplify(const coord_t smallest_line_segment_squared, const coord_t allowed_error_distance_squared, const coord_t maximum_extrusion_area_deviation)
{
    const size_t min_path_size = is_closed ? 3 : 2;
    if (junctions.size() <= min_path_size)
    {
        return;
    }

    // TODO: allow for the first point to be removed in case of simplifying closed Extrusionlines.

    /* ExtrusionLines are treated as (open) polylines, so in case an ExtrusionLine is actually a closed polygon, its
     * starting and ending points will be equal (or almost equal). Therefore, the simplification of the ExtrusionLine
     * should not touch the first and last points. As a result, start simplifying from point at index 1.
     * */
    std::vector<ExtrusionJunction> new_junctions;
    // Starting junction should always exist in the simplified path
    new_junctions.emplace_back(junctions.front());

    /* Initially, previous_previous is always the same as previous because, for open ExtrusionLines the last junction
     * cannot be taken into consideration when checking the points at index 1. For closed ExtrusionLines, the first and
     * last junctions are anyway the same.
     * */
    ExtrusionJunction previous_previous = junctions.front();
    ExtrusionJunction previous = junctions.front();

    /* When removing a vertex, we check the height of the triangle of the area
     being removed from the original polygon by the simplification. However,
     when consecutively removing multiple vertices the height of the previously
     removed vertices w.r.t. the shortcut path changes.
     In order to not recompute the new height value of previously removed
     vertices we compute the height of a representative triangle, which covers
     the same amount of area as the area being cut off. We use the Shoelace
     formula to accumulate the area under the removed segments. This works by
     computing the area in a 'fan' where each of the blades of the fan go from
     the origin to one of the segments. While removing vertices the area in
     this fan accumulates. By subtracting the area of the blade connected to
     the short-cutting segment we obtain the total area of the cutoff region.
     From this area we compute the height of the representative triangle using
     the standard formula for a triangle area: A = .5*b*h
     */
    const ExtrusionJunction& initial = junctions.at(1);
    coord_t accumulated_area_removed = previous.p.X * initial.p.Y - previous.p.Y * initial.p.X; // Twice the Shoelace formula for area of polygon per line segment.

    for (size_t point_idx = 1; point_idx < junctions.size() - 1; point_idx++)
    {
        const ExtrusionJunction& current = junctions[point_idx];

        // Spill over in case of overflow, unless the [next] vertex will then be equal to [previous].
        const bool spill_over = point_idx + 1 == junctions.size() && new_junctions.size() > 1;
        ExtrusionJunction& next = spill_over ? new_junctions[0] : junctions[point_idx + 1];

        const coord_t removed_area_next = current.p.X * next.p.Y - current.p.Y * next.p.X; // Twice the Shoelace formula for area of polygon per line segment.
        const coord_t negative_area_closing = next.p.X * previous.p.Y - next.p.Y * previous.p.X; // Area between the origin and the short-cutting segment
        accumulated_area_removed += removed_area_next;

        const coord_t length2 = vSize2(current - previous);
        if (length2 < 25)
        {
            // We're allowed to always delete segments of less than 5 micron. The width in this case doesn't matter that much.
            continue;
        }

        const coord_t area_removed_so_far = accumulated_area_removed + negative_area_closing; // Close the shortcut area polygon
        const coord_t base_length_2 = vSize2(next - previous);

        if (base_length_2 == 0) // Two line segments form a line back and forth with no area.
        {
            continue; // Remove the junction (vertex).
        }
        //We want to check if the height of the triangle formed by previous, current and next vertices is less than allowed_error_distance_squared.
        //1/2 L = A           [actual area is half of the computed shoelace value] // Shoelace formula is .5*(...) , but we simplify the computation and take out the .5
        //A = 1/2 * b * h     [triangle area formula]
        //L = b * h           [apply above two and take out the 1/2]
        //h = L / b           [divide by b]
        //h^2 = (L / b)^2     [square it]
        //h^2 = L^2 / b^2     [factor the divisor]
        const coord_t height_2 = area_removed_so_far * area_removed_so_far / base_length_2;
        coord_t weighted_average_width;
        const coord_t extrusion_area_error = calculateExtrusionAreaDeviationError(previous, current, next, weighted_average_width);
        if ((height_2 <= 1 //Almost exactly colinear (barring rounding errors).
             && LinearAlg2D::getDistFromLine(current.p, previous.p, next.p) <= 1) // Make sure that height_2 is not small because of cancellation of positive and negative areas
            // We shouldn't remove middle junctions of colinear segments if the area changed for the C-P segment is exceeding the maximum allowed
             && extrusion_area_error <= maximum_extrusion_area_deviation)
        {
            // Adjust the width of the entire P-N line as a weighted average of the widths of the P-C and C-N lines and
            // then remove the current junction (vertex).
            next.w = weighted_average_width;
            continue;
        }

        if (length2 < smallest_line_segment_squared
            && height_2 <= allowed_error_distance_squared) // Removing the junction (vertex) doesn't introduce too much error.
        {
            const coord_t next_length2 = vSize2(current - next);
            if (next_length2 > smallest_line_segment_squared)
            {
                // Special case; The next line is long. If we were to remove this, it could happen that we get quite noticeable artifacts.
                // We should instead move this point to a location where both edges are kept and then remove the previous point that we wanted to keep.
                // By taking the intersection of these two lines, we get a point that preserves the direction (so it makes the corner a bit more pointy).
                // We just need to be sure that the intersection point does not introduce an artifact itself.
                Point intersection_point;
                bool has_intersection = LinearAlg2D::lineLineIntersection(previous_previous.p, previous.p, current.p, next.p, intersection_point);
                if (!has_intersection
                    || LinearAlg2D::getDist2FromLine(intersection_point, previous.p, current.p) > allowed_error_distance_squared
                    || vSize2(intersection_point - previous.p) > smallest_line_segment_squared  // The intersection point is way too far from the 'previous'
                    || vSize2(intersection_point - next.p) > smallest_line_segment_squared)     // and 'next' points, so it shouldn't replace 'current'
                {
                    // We can't find a better spot for it, but the size of the line is more than 5 micron.
                    // So the only thing we can do here is leave it in...
                }
                else
                {
                    // New point seems like a valid one.
                    const ExtrusionJunction new_to_add = ExtrusionJunction(intersection_point, current.w, current.perimeter_index);
                    // If there was a previous point added, remove it.
                    if(!new_junctions.empty())
                    {
                        new_junctions.pop_back();
                        previous = previous_previous;
                    }

                    // The junction (vertex) is replaced by the new one.
                    accumulated_area_removed = removed_area_next; // So that in the next iteration it's the area between the origin, [previous] and [current]
                    previous_previous = previous;
                    previous = new_to_add; // Note that "previous" is only updated if we don't remove the junction (vertex).
                    new_junctions.push_back(new_to_add);
                    continue;
                }
            }
            else
            {
                continue; // Remove the junction (vertex).
            }
        }
        // The junction (vertex) isn't removed.
        accumulated_area_removed = removed_area_next; // So that in the next iteration it's the area between the origin, [previous] and [current]
        previous_previous = previous;
        previous = current; // Note that "previous" is only updated if we don't remove the junction (vertex).
        new_junctions.push_back(current);
    }

    // Ending junction (vertex) should always exist in the simplified path
    new_junctions.emplace_back(junctions.back());

    /* In case this is a closed polygon (instead of a poly-line-segments), the invariant that the first and last points are the same should be enforced.
     * Since one of them didn't move, and the other can't have been moved further than the constraints, if originally equal, they can simply be equated.
     */
    if (vSize2(junctions.front().p - junctions.back().p) == 0)
    {
        new_junctions.back().p = junctions.front().p;
    }

    junctions = new_junctions;
}

coord_t ExtrusionLine::calculateExtrusionAreaDeviationError(ExtrusionJunction A, ExtrusionJunction B, ExtrusionJunction C, coord_t& weighted_average_width)
{
    /*
     * A             B                          C              A                                        C
     * ---------------                                         **************
     * |             |                                         ------------------------------------------
     * |             |--------------------------|  B removed   |            |***************************|
     * |             |                          |  --------->  |            |                           |
     * |             |--------------------------|              |            |***************************|
     * |             |                                         ------------------------------------------
     * ---------------             ^                           **************
     *       ^                    C.w                                             ^
     *      B.w                                                     new_width = weighted_average_width
     *
     *
     * ******** denote the total extrusion area deviation error in the consecutive segments as a result of using the
     * weighted-average width for the entire extrusion line.
     *
     * */
    const coord_t ab_length = vSize(B - A);
    const coord_t bc_length = vSize(C - B);
    const coord_t width_diff = std::abs(B.w - A.w);
    if (width_diff > 1)
    {
        // Adjust the width only if there is a difference, or else the rounding errors may produce the wrong
        // weighted average value.
        weighted_average_width = (ab_length * A.w + bc_length * B.w) / vSize(C - A);
        return std::abs(A.w - weighted_average_width) * ab_length + std::abs(B.w - weighted_average_width) * bc_length;
    }
    else
    {
        // If the width difference is very small, then select the width of the segment that is longer
        weighted_average_width = ab_length > bc_length ? A.w : B.w;
        return ab_length > bc_length ? width_diff * bc_length : width_diff * ab_length;
    }
}

}
<commit_msg>Average width of a line is determined by _both_ junctions.<commit_after>//Copyright (c) 2020 Ultimaker B.V.
//CuraEngine is released under the terms of the AGPLv3 or higher.

#include <algorithm>

#include "ExtrusionLine.h"
#include "linearAlg2D.h"

namespace cura
{

ExtrusionLine::ExtrusionLine(const size_t inset_idx, const bool is_odd)
: inset_idx(inset_idx)
, is_odd(is_odd)
, is_closed(false)
{}

coord_t ExtrusionLine::getLength() const
{
    if (junctions.empty())
    {
        return 0;
    }
    coord_t len = 0;
    ExtrusionJunction prev = junctions.front();
    for (const ExtrusionJunction& next : junctions)
    {
        len += vSize(next.p - prev.p);
        prev = next;
    }
    if (is_closed)
    {
        len += vSize(front().p - back().p);
    }
    return len;
}

coord_t ExtrusionLine::getMinimalWidth() const
{
    return std::min_element(junctions.cbegin(), junctions.cend(),
                            [](const ExtrusionJunction& l, const ExtrusionJunction& r)
                            {
                                return l.w < r.w;
                            })->w;
}

void ExtrusionLine::simplify(const coord_t smallest_line_segment_squared, const coord_t allowed_error_distance_squared, const coord_t maximum_extrusion_area_deviation)
{
    const size_t min_path_size = is_closed ? 3 : 2;
    if (junctions.size() <= min_path_size)
    {
        return;
    }

    // TODO: allow for the first point to be removed in case of simplifying closed Extrusionlines.

    /* ExtrusionLines are treated as (open) polylines, so in case an ExtrusionLine is actually a closed polygon, its
     * starting and ending points will be equal (or almost equal). Therefore, the simplification of the ExtrusionLine
     * should not touch the first and last points. As a result, start simplifying from point at index 1.
     * */
    std::vector<ExtrusionJunction> new_junctions;
    // Starting junction should always exist in the simplified path
    new_junctions.emplace_back(junctions.front());

    /* Initially, previous_previous is always the same as previous because, for open ExtrusionLines the last junction
     * cannot be taken into consideration when checking the points at index 1. For closed ExtrusionLines, the first and
     * last junctions are anyway the same.
     * */
    ExtrusionJunction previous_previous = junctions.front();
    ExtrusionJunction previous = junctions.front();

    /* When removing a vertex, we check the height of the triangle of the area
     being removed from the original polygon by the simplification. However,
     when consecutively removing multiple vertices the height of the previously
     removed vertices w.r.t. the shortcut path changes.
     In order to not recompute the new height value of previously removed
     vertices we compute the height of a representative triangle, which covers
     the same amount of area as the area being cut off. We use the Shoelace
     formula to accumulate the area under the removed segments. This works by
     computing the area in a 'fan' where each of the blades of the fan go from
     the origin to one of the segments. While removing vertices the area in
     this fan accumulates. By subtracting the area of the blade connected to
     the short-cutting segment we obtain the total area of the cutoff region.
     From this area we compute the height of the representative triangle using
     the standard formula for a triangle area: A = .5*b*h
     */
    const ExtrusionJunction& initial = junctions.at(1);
    coord_t accumulated_area_removed = previous.p.X * initial.p.Y - previous.p.Y * initial.p.X; // Twice the Shoelace formula for area of polygon per line segment.

    for (size_t point_idx = 1; point_idx < junctions.size() - 1; point_idx++)
    {
        const ExtrusionJunction& current = junctions[point_idx];

        // Spill over in case of overflow, unless the [next] vertex will then be equal to [previous].
        const bool spill_over = point_idx + 1 == junctions.size() && new_junctions.size() > 1;
        ExtrusionJunction& next = spill_over ? new_junctions[0] : junctions[point_idx + 1];

        const coord_t removed_area_next = current.p.X * next.p.Y - current.p.Y * next.p.X; // Twice the Shoelace formula for area of polygon per line segment.
        const coord_t negative_area_closing = next.p.X * previous.p.Y - next.p.Y * previous.p.X; // Area between the origin and the short-cutting segment
        accumulated_area_removed += removed_area_next;

        const coord_t length2 = vSize2(current - previous);
        if (length2 < 25)
        {
            // We're allowed to always delete segments of less than 5 micron. The width in this case doesn't matter that much.
            continue;
        }

        const coord_t area_removed_so_far = accumulated_area_removed + negative_area_closing; // Close the shortcut area polygon
        const coord_t base_length_2 = vSize2(next - previous);

        if (base_length_2 == 0) // Two line segments form a line back and forth with no area.
        {
            continue; // Remove the junction (vertex).
        }
        //We want to check if the height of the triangle formed by previous, current and next vertices is less than allowed_error_distance_squared.
        //1/2 L = A           [actual area is half of the computed shoelace value] // Shoelace formula is .5*(...) , but we simplify the computation and take out the .5
        //A = 1/2 * b * h     [triangle area formula]
        //L = b * h           [apply above two and take out the 1/2]
        //h = L / b           [divide by b]
        //h^2 = (L / b)^2     [square it]
        //h^2 = L^2 / b^2     [factor the divisor]
        const coord_t height_2 = area_removed_so_far * area_removed_so_far / base_length_2;
        coord_t weighted_average_width;
        const coord_t extrusion_area_error = calculateExtrusionAreaDeviationError(previous, current, next, weighted_average_width);
        if ((height_2 <= 1 //Almost exactly colinear (barring rounding errors).
             && LinearAlg2D::getDistFromLine(current.p, previous.p, next.p) <= 1) // Make sure that height_2 is not small because of cancellation of positive and negative areas
            // We shouldn't remove middle junctions of colinear segments if the area changed for the C-P segment is exceeding the maximum allowed
             && extrusion_area_error <= maximum_extrusion_area_deviation)
        {
            // Adjust the width of the entire P-N line as a weighted average of the widths of the P-C and C-N lines and
            // then remove the current junction (vertex).
            next.w = weighted_average_width;
            continue;
        }

        if (length2 < smallest_line_segment_squared
            && height_2 <= allowed_error_distance_squared) // Removing the junction (vertex) doesn't introduce too much error.
        {
            const coord_t next_length2 = vSize2(current - next);
            if (next_length2 > smallest_line_segment_squared)
            {
                // Special case; The next line is long. If we were to remove this, it could happen that we get quite noticeable artifacts.
                // We should instead move this point to a location where both edges are kept and then remove the previous point that we wanted to keep.
                // By taking the intersection of these two lines, we get a point that preserves the direction (so it makes the corner a bit more pointy).
                // We just need to be sure that the intersection point does not introduce an artifact itself.
                Point intersection_point;
                bool has_intersection = LinearAlg2D::lineLineIntersection(previous_previous.p, previous.p, current.p, next.p, intersection_point);
                if (!has_intersection
                    || LinearAlg2D::getDist2FromLine(intersection_point, previous.p, current.p) > allowed_error_distance_squared
                    || vSize2(intersection_point - previous.p) > smallest_line_segment_squared  // The intersection point is way too far from the 'previous'
                    || vSize2(intersection_point - next.p) > smallest_line_segment_squared)     // and 'next' points, so it shouldn't replace 'current'
                {
                    // We can't find a better spot for it, but the size of the line is more than 5 micron.
                    // So the only thing we can do here is leave it in...
                }
                else
                {
                    // New point seems like a valid one.
                    const ExtrusionJunction new_to_add = ExtrusionJunction(intersection_point, current.w, current.perimeter_index);
                    // If there was a previous point added, remove it.
                    if(!new_junctions.empty())
                    {
                        new_junctions.pop_back();
                        previous = previous_previous;
                    }

                    // The junction (vertex) is replaced by the new one.
                    accumulated_area_removed = removed_area_next; // So that in the next iteration it's the area between the origin, [previous] and [current]
                    previous_previous = previous;
                    previous = new_to_add; // Note that "previous" is only updated if we don't remove the junction (vertex).
                    new_junctions.push_back(new_to_add);
                    continue;
                }
            }
            else
            {
                continue; // Remove the junction (vertex).
            }
        }
        // The junction (vertex) isn't removed.
        accumulated_area_removed = removed_area_next; // So that in the next iteration it's the area between the origin, [previous] and [current]
        previous_previous = previous;
        previous = current; // Note that "previous" is only updated if we don't remove the junction (vertex).
        new_junctions.push_back(current);
    }

    // Ending junction (vertex) should always exist in the simplified path
    new_junctions.emplace_back(junctions.back());

    /* In case this is a closed polygon (instead of a poly-line-segments), the invariant that the first and last points are the same should be enforced.
     * Since one of them didn't move, and the other can't have been moved further than the constraints, if originally equal, they can simply be equated.
     */
    if (vSize2(junctions.front().p - junctions.back().p) == 0)
    {
        new_junctions.back().p = junctions.front().p;
    }

    junctions = new_junctions;
}

coord_t ExtrusionLine::calculateExtrusionAreaDeviationError(ExtrusionJunction A, ExtrusionJunction B, ExtrusionJunction C, coord_t& weighted_average_width)
{
    /*
     * A             B                          C              A                                        C
     * ---------------                                         **************
     * |             |                                         ------------------------------------------
     * |             |--------------------------|  B removed   |            |***************************|
     * |             |                          |  --------->  |            |                           |
     * |             |--------------------------|              |            |***************************|
     * |             |                                         ------------------------------------------
     * ---------------             ^                           **************
     *       ^                B.w + C.w / 2                                       ^
     *  A.w + B.w / 2                                               new_width = weighted_average_width
     *
     *
     * ******** denote the total extrusion area deviation error in the consecutive segments as a result of using the
     * weighted-average width for the entire extrusion line.
     *
     * */
    const coord_t ab_length = vSize(B - A);
    const coord_t bc_length = vSize(C - B);
    const coord_t width_diff = std::abs(B.w - A.w);
    if (width_diff > 1)
    {
        // Adjust the width only if there is a difference, or else the rounding errors may produce the wrong
        // weighted average value.
        weighted_average_width = (ab_length * (A.w + B.w) / 2 + bc_length * (B.w + C.w) / 2) / vSize(C - A);
        return std::abs(A.w - weighted_average_width) * ab_length + std::abs(B.w - weighted_average_width) * bc_length;
    }
    else
    {
        // If the width difference is very small, then select the width of the segment that is longer
        weighted_average_width = ab_length > bc_length ? A.w : B.w;
        return ab_length > bc_length ? width_diff * bc_length : width_diff * ab_length;
    }
}

}
<|endoftext|>


<commit_before>//Copyright (c) 2020 Ultimaker B.V.
//CuraEngine is released under the terms of the AGPLv3 or higher.

#include <algorithm>

#include "ExtrusionLine.h"
#include "linearAlg2D.h"

namespace cura
{

ExtrusionLine::ExtrusionLine(const size_t inset_idx, const bool is_odd)
: inset_idx(inset_idx)
, is_odd(is_odd)
, is_closed(false)
{}

coord_t ExtrusionLine::getLength() const
{
    if (junctions.empty())
    {
        return 0;
    }
    coord_t len = 0;
    ExtrusionJunction prev = junctions.front();
    for (const ExtrusionJunction& next : junctions)
    {
        len += vSize(next.p - prev.p);
        prev = next;
    }
    if (is_closed)
    {
        len += vSize(front().p - back().p);
    }
    return len;
}

coord_t ExtrusionLine::getMinimalWidth() const
{
    return std::min_element(junctions.cbegin(), junctions.cend(),
                            [](const ExtrusionJunction& l, const ExtrusionJunction& r)
                            {
                                return l.w < r.w;
                            })->w;
}

void ExtrusionLine::simplify(const coord_t smallest_line_segment_squared, const coord_t allowed_error_distance_squared, const coord_t maximum_extrusion_area_deviation)
{
    const size_t min_path_size = is_closed ? 3 : 2;
    if (junctions.size() <= min_path_size)
    {
        return;
    }

    // TODO: allow for the first point to be removed in case of simplifying closed Extrusionlines.

    /* ExtrusionLines are treated as (open) polylines, so in case an ExtrusionLine is actually a closed polygon, its
     * starting and ending points will be equal (or almost equal). Therefore, the simplification of the ExtrusionLine
     * should not touch the first and last points. As a result, start simplifying from point at index 1.
     * */
    std::vector<ExtrusionJunction> new_junctions;
    // Starting junction should always exist in the simplified path
    new_junctions.emplace_back(junctions.front());

    /* Initially, previous_previous is always the same as previous because, for open ExtrusionLines the last junction
     * cannot be taken into consideration when checking the points at index 1. For closed ExtrusionLines, the first and
     * last junctions are anyway the same.
     * */
    ExtrusionJunction previous_previous = junctions.front();
    ExtrusionJunction previous = junctions.front();

    /* When removing a vertex, we check the height of the triangle of the area
     being removed from the original polygon by the simplification. However,
     when consecutively removing multiple vertices the height of the previously
     removed vertices w.r.t. the shortcut path changes.
     In order to not recompute the new height value of previously removed
     vertices we compute the height of a representative triangle, which covers
     the same amount of area as the area being cut off. We use the Shoelace
     formula to accumulate the area under the removed segments. This works by
     computing the area in a 'fan' where each of the blades of the fan go from
     the origin to one of the segments. While removing vertices the area in
     this fan accumulates. By subtracting the area of the blade connected to
     the short-cutting segment we obtain the total area of the cutoff region.
     From this area we compute the height of the representative triangle using
     the standard formula for a triangle area: A = .5*b*h
     */
    const ExtrusionJunction& initial = junctions.at(1);
    coord_t accumulated_area_removed = previous.p.X * initial.p.Y - previous.p.Y * initial.p.X; // Twice the Shoelace formula for area of polygon per line segment.

    for (size_t point_idx = 1; point_idx < junctions.size() - 1; point_idx++)
    {
        const ExtrusionJunction& current = junctions[point_idx];

        // Spill over in case of overflow, unless the [next] vertex will then be equal to [previous].
        const bool spill_over = point_idx + 1 == junctions.size() && new_junctions.size() > 1;
        ExtrusionJunction& next = spill_over ? new_junctions[0] : junctions[point_idx + 1];

        const coord_t removed_area_next = current.p.X * next.p.Y - current.p.Y * next.p.X; // Twice the Shoelace formula for area of polygon per line segment.
        const coord_t negative_area_closing = next.p.X * previous.p.Y - next.p.Y * previous.p.X; // Area between the origin and the short-cutting segment
        accumulated_area_removed += removed_area_next;

        const coord_t length2 = vSize2(current - previous);
        if (length2 < 25)
        {
            // We're allowed to always delete segments of less than 5 micron. The width in this case doesn't matter that much.
            continue;
        }

        const coord_t area_removed_so_far = accumulated_area_removed + negative_area_closing; // Close the shortcut area polygon
        const coord_t base_length_2 = vSize2(next - previous);

        if (base_length_2 == 0) // Two line segments form a line back and forth with no area.
        {
            continue; // Remove the junction (vertex).
        }
        //We want to check if the height of the triangle formed by previous, current and next vertices is less than allowed_error_distance_squared.
        //1/2 L = A           [actual area is half of the computed shoelace value] // Shoelace formula is .5*(...) , but we simplify the computation and take out the .5
        //A = 1/2 * b * h     [triangle area formula]
        //L = b * h           [apply above two and take out the 1/2]
        //h = L / b           [divide by b]
        //h^2 = (L / b)^2     [square it]
        //h^2 = L^2 / b^2     [factor the divisor]
        const coord_t height_2 = area_removed_so_far * area_removed_so_far / base_length_2;
        coord_t weighted_average_width;
        const coord_t extrusion_area_error = calculateExtrusionAreaDeviationError(previous, current, next, weighted_average_width);
        if ((height_2 <= 1 //Almost exactly colinear (barring rounding errors).
             && LinearAlg2D::getDistFromLine(current.p, previous.p, next.p) <= 1) // Make sure that height_2 is not small because of cancellation of positive and negative areas
            // We shouldn't remove middle junctions of colinear segments if the area changed for the C-P segment is exceeding the maximum allowed
             && extrusion_area_error <= maximum_extrusion_area_deviation)
        {
            // Adjust the width of the entire P-N line as a weighted average of the widths of the P-C and C-N lines and
            // then remove the current junction (vertex).
            next.w = weighted_average_width;
            continue;
        }

        if (length2 < smallest_line_segment_squared
            && height_2 <= allowed_error_distance_squared) // Removing the junction (vertex) doesn't introduce too much error.
        {
            const coord_t next_length2 = vSize2(current - next);
            if (next_length2 > smallest_line_segment_squared)
            {
                // Special case; The next line is long. If we were to remove this, it could happen that we get quite noticeable artifacts.
                // We should instead move this point to a location where both edges are kept and then remove the previous point that we wanted to keep.
                // By taking the intersection of these two lines, we get a point that preserves the direction (so it makes the corner a bit more pointy).
                // We just need to be sure that the intersection point does not introduce an artifact itself.
                Point intersection_point;
                bool has_intersection = LinearAlg2D::lineLineIntersection(previous_previous.p, previous.p, current.p, next.p, intersection_point);
                if (!has_intersection
                    || LinearAlg2D::getDist2FromLine(intersection_point, previous.p, current.p) > allowed_error_distance_squared
                    || vSize2(intersection_point - previous.p) > smallest_line_segment_squared  // The intersection point is way too far from the 'previous'
                    || vSize2(intersection_point - next.p) > smallest_line_segment_squared)     // and 'next' points, so it shouldn't replace 'current'
                {
                    // We can't find a better spot for it, but the size of the line is more than 5 micron.
                    // So the only thing we can do here is leave it in...
                }
                else
                {
                    // New point seems like a valid one.
                    const ExtrusionJunction new_to_add = ExtrusionJunction(intersection_point, current.w, current.perimeter_index);
                    // If there was a previous point added, remove it.
                    if(!new_junctions.empty())
                    {
                        new_junctions.pop_back();
                        previous = previous_previous;
                    }

                    // The junction (vertex) is replaced by the new one.
                    accumulated_area_removed = removed_area_next; // So that in the next iteration it's the area between the origin, [previous] and [current]
                    previous_previous = previous;
                    previous = new_to_add; // Note that "previous" is only updated if we don't remove the junction (vertex).
                    new_junctions.push_back(new_to_add);
                    continue;
                }
            }
            else
            {
                continue; // Remove the junction (vertex).
            }
        }
        // The junction (vertex) isn't removed.
        accumulated_area_removed = removed_area_next; // So that in the next iteration it's the area between the origin, [previous] and [current]
        previous_previous = previous;
        previous = current; // Note that "previous" is only updated if we don't remove the junction (vertex).
        new_junctions.push_back(current);
    }

    // Ending junction (vertex) should always exist in the simplified path
    new_junctions.emplace_back(junctions.back());

    /* In case this is a closed polygon (instead of a poly-line-segments), the invariant that the first and last points are the same should be enforced.
     * Since one of them didn't move, and the other can't have been moved further than the constraints, if originally equal, they can simply be equated.
     */
    if (vSize2(junctions.front().p - junctions.back().p) == 0)
    {
        new_junctions.back().p = junctions.front().p;
    }

    junctions = new_junctions;
}

coord_t ExtrusionLine::calculateExtrusionAreaDeviationError(ExtrusionJunction A, ExtrusionJunction B, ExtrusionJunction C, coord_t& weighted_average_width)
{
    /*
     * A             B                          C              A                                        C
     * ---------------                                         **************
     * |             |                                         ------------------------------------------
     * |             |--------------------------|  B removed   |            |***************************|
     * |             |                          |  --------->  |            |                           |
     * |             |--------------------------|              |            |***************************|
     * |             |                                         ------------------------------------------
     * ---------------             ^                           **************
     *       ^                    C.w                                             ^
     *      B.w                                                     new_width = weighted_average_width
     *
     *
     * ******** denote the total extrusion area deviation error in the consecutive segments as a result of using the
     * weighted-average width for the entire extrusion line.
     *
     * */
    const coord_t ab_length = vSize(B - A);
    const coord_t bc_length = vSize(C - B);
    const coord_t width_diff = llabs(B.w - C.w);
    if (width_diff > 1)
    {
        // Adjust the width only if there is a difference, or else the rounding errors may produce the wrong
        // weighted average value.
        weighted_average_width = (ab_length * B.w + bc_length * C.w) / vSize(C - A);
        return llabs(B.w - weighted_average_width) * ab_length + llabs(C.w - weighted_average_width) * bc_length;
    }
    else
    {
        // If the width difference is very small, then select the width of the segment that is longer
        weighted_average_width = ab_length > bc_length ? B.w : C.w;
        return ab_length > bc_length ? width_diff * bc_length : width_diff * ab_length;
    }
}

}
<commit_msg>Fix using wrong junctions when calculating areas.<commit_after>//Copyright (c) 2020 Ultimaker B.V.
//CuraEngine is released under the terms of the AGPLv3 or higher.

#include <algorithm>

#include "ExtrusionLine.h"
#include "linearAlg2D.h"

namespace cura
{

ExtrusionLine::ExtrusionLine(const size_t inset_idx, const bool is_odd)
: inset_idx(inset_idx)
, is_odd(is_odd)
, is_closed(false)
{}

coord_t ExtrusionLine::getLength() const
{
    if (junctions.empty())
    {
        return 0;
    }
    coord_t len = 0;
    ExtrusionJunction prev = junctions.front();
    for (const ExtrusionJunction& next : junctions)
    {
        len += vSize(next.p - prev.p);
        prev = next;
    }
    if (is_closed)
    {
        len += vSize(front().p - back().p);
    }
    return len;
}

coord_t ExtrusionLine::getMinimalWidth() const
{
    return std::min_element(junctions.cbegin(), junctions.cend(),
                            [](const ExtrusionJunction& l, const ExtrusionJunction& r)
                            {
                                return l.w < r.w;
                            })->w;
}

void ExtrusionLine::simplify(const coord_t smallest_line_segment_squared, const coord_t allowed_error_distance_squared, const coord_t maximum_extrusion_area_deviation)
{
    const size_t min_path_size = is_closed ? 3 : 2;
    if (junctions.size() <= min_path_size)
    {
        return;
    }

    // TODO: allow for the first point to be removed in case of simplifying closed Extrusionlines.

    /* ExtrusionLines are treated as (open) polylines, so in case an ExtrusionLine is actually a closed polygon, its
     * starting and ending points will be equal (or almost equal). Therefore, the simplification of the ExtrusionLine
     * should not touch the first and last points. As a result, start simplifying from point at index 1.
     * */
    std::vector<ExtrusionJunction> new_junctions;
    // Starting junction should always exist in the simplified path
    new_junctions.emplace_back(junctions.front());

    /* Initially, previous_previous is always the same as previous because, for open ExtrusionLines the last junction
     * cannot be taken into consideration when checking the points at index 1. For closed ExtrusionLines, the first and
     * last junctions are anyway the same.
     * */
    ExtrusionJunction previous_previous = junctions.front();
    ExtrusionJunction previous = junctions.front();

    /* When removing a vertex, we check the height of the triangle of the area
     being removed from the original polygon by the simplification. However,
     when consecutively removing multiple vertices the height of the previously
     removed vertices w.r.t. the shortcut path changes.
     In order to not recompute the new height value of previously removed
     vertices we compute the height of a representative triangle, which covers
     the same amount of area as the area being cut off. We use the Shoelace
     formula to accumulate the area under the removed segments. This works by
     computing the area in a 'fan' where each of the blades of the fan go from
     the origin to one of the segments. While removing vertices the area in
     this fan accumulates. By subtracting the area of the blade connected to
     the short-cutting segment we obtain the total area of the cutoff region.
     From this area we compute the height of the representative triangle using
     the standard formula for a triangle area: A = .5*b*h
     */
    const ExtrusionJunction& initial = junctions.at(1);
    coord_t accumulated_area_removed = previous.p.X * initial.p.Y - previous.p.Y * initial.p.X; // Twice the Shoelace formula for area of polygon per line segment.

    for (size_t point_idx = 1; point_idx < junctions.size() - 1; point_idx++)
    {
        const ExtrusionJunction& current = junctions[point_idx];

        // Spill over in case of overflow, unless the [next] vertex will then be equal to [previous].
        const bool spill_over = point_idx + 1 == junctions.size() && new_junctions.size() > 1;
        ExtrusionJunction& next = spill_over ? new_junctions[0] : junctions[point_idx + 1];

        const coord_t removed_area_next = current.p.X * next.p.Y - current.p.Y * next.p.X; // Twice the Shoelace formula for area of polygon per line segment.
        const coord_t negative_area_closing = next.p.X * previous.p.Y - next.p.Y * previous.p.X; // Area between the origin and the short-cutting segment
        accumulated_area_removed += removed_area_next;

        const coord_t length2 = vSize2(current - previous);
        if (length2 < 25)
        {
            // We're allowed to always delete segments of less than 5 micron. The width in this case doesn't matter that much.
            continue;
        }

        const coord_t area_removed_so_far = accumulated_area_removed + negative_area_closing; // Close the shortcut area polygon
        const coord_t base_length_2 = vSize2(next - previous);

        if (base_length_2 == 0) // Two line segments form a line back and forth with no area.
        {
            continue; // Remove the junction (vertex).
        }
        //We want to check if the height of the triangle formed by previous, current and next vertices is less than allowed_error_distance_squared.
        //1/2 L = A           [actual area is half of the computed shoelace value] // Shoelace formula is .5*(...) , but we simplify the computation and take out the .5
        //A = 1/2 * b * h     [triangle area formula]
        //L = b * h           [apply above two and take out the 1/2]
        //h = L / b           [divide by b]
        //h^2 = (L / b)^2     [square it]
        //h^2 = L^2 / b^2     [factor the divisor]
        const coord_t height_2 = area_removed_so_far * area_removed_so_far / base_length_2;
        coord_t weighted_average_width;
        const coord_t extrusion_area_error = calculateExtrusionAreaDeviationError(previous, current, next, weighted_average_width);
        if ((height_2 <= 1 //Almost exactly colinear (barring rounding errors).
             && LinearAlg2D::getDistFromLine(current.p, previous.p, next.p) <= 1) // Make sure that height_2 is not small because of cancellation of positive and negative areas
            // We shouldn't remove middle junctions of colinear segments if the area changed for the C-P segment is exceeding the maximum allowed
             && extrusion_area_error <= maximum_extrusion_area_deviation)
        {
            // Adjust the width of the entire P-N line as a weighted average of the widths of the P-C and C-N lines and
            // then remove the current junction (vertex).
            next.w = weighted_average_width;
            continue;
        }

        if (length2 < smallest_line_segment_squared
            && height_2 <= allowed_error_distance_squared) // Removing the junction (vertex) doesn't introduce too much error.
        {
            const coord_t next_length2 = vSize2(current - next);
            if (next_length2 > smallest_line_segment_squared)
            {
                // Special case; The next line is long. If we were to remove this, it could happen that we get quite noticeable artifacts.
                // We should instead move this point to a location where both edges are kept and then remove the previous point that we wanted to keep.
                // By taking the intersection of these two lines, we get a point that preserves the direction (so it makes the corner a bit more pointy).
                // We just need to be sure that the intersection point does not introduce an artifact itself.
                Point intersection_point;
                bool has_intersection = LinearAlg2D::lineLineIntersection(previous_previous.p, previous.p, current.p, next.p, intersection_point);
                if (!has_intersection
                    || LinearAlg2D::getDist2FromLine(intersection_point, previous.p, current.p) > allowed_error_distance_squared
                    || vSize2(intersection_point - previous.p) > smallest_line_segment_squared  // The intersection point is way too far from the 'previous'
                    || vSize2(intersection_point - next.p) > smallest_line_segment_squared)     // and 'next' points, so it shouldn't replace 'current'
                {
                    // We can't find a better spot for it, but the size of the line is more than 5 micron.
                    // So the only thing we can do here is leave it in...
                }
                else
                {
                    // New point seems like a valid one.
                    const ExtrusionJunction new_to_add = ExtrusionJunction(intersection_point, current.w, current.perimeter_index);
                    // If there was a previous point added, remove it.
                    if(!new_junctions.empty())
                    {
                        new_junctions.pop_back();
                        previous = previous_previous;
                    }

                    // The junction (vertex) is replaced by the new one.
                    accumulated_area_removed = removed_area_next; // So that in the next iteration it's the area between the origin, [previous] and [current]
                    previous_previous = previous;
                    previous = new_to_add; // Note that "previous" is only updated if we don't remove the junction (vertex).
                    new_junctions.push_back(new_to_add);
                    continue;
                }
            }
            else
            {
                continue; // Remove the junction (vertex).
            }
        }
        // The junction (vertex) isn't removed.
        accumulated_area_removed = removed_area_next; // So that in the next iteration it's the area between the origin, [previous] and [current]
        previous_previous = previous;
        previous = current; // Note that "previous" is only updated if we don't remove the junction (vertex).
        new_junctions.push_back(current);
    }

    // Ending junction (vertex) should always exist in the simplified path
    new_junctions.emplace_back(junctions.back());

    /* In case this is a closed polygon (instead of a poly-line-segments), the invariant that the first and last points are the same should be enforced.
     * Since one of them didn't move, and the other can't have been moved further than the constraints, if originally equal, they can simply be equated.
     */
    if (vSize2(junctions.front().p - junctions.back().p) == 0)
    {
        new_junctions.back().p = junctions.front().p;
    }

    junctions = new_junctions;
}

coord_t ExtrusionLine::calculateExtrusionAreaDeviationError(ExtrusionJunction A, ExtrusionJunction B, ExtrusionJunction C, coord_t& weighted_average_width)
{
    /*
     * A             B                          C              A                                        C
     * ---------------                                         **************
     * |             |                                         ------------------------------------------
     * |             |--------------------------|  B removed   |            |***************************|
     * |             |                          |  --------->  |            |                           |
     * |             |--------------------------|              |            |***************************|
     * |             |                                         ------------------------------------------
     * ---------------             ^                           **************
     *       ^                    C.w                                             ^
     *      B.w                                                     new_width = weighted_average_width
     *
     *
     * ******** denote the total extrusion area deviation error in the consecutive segments as a result of using the
     * weighted-average width for the entire extrusion line.
     *
     * */
    const coord_t ab_length = vSize(B - A);
    const coord_t bc_length = vSize(C - B);
    const coord_t width_diff = llabs(B.w - A.w);
    if (width_diff > 1)
    {
        // Adjust the width only if there is a difference, or else the rounding errors may produce the wrong
        // weighted average value.
        weighted_average_width = (ab_length * A.w + bc_length * B.w) / vSize(C - A);
        return llabs(A.w - weighted_average_width) * ab_length + llabs(B.w - weighted_average_width) * bc_length;
    }
    else
    {
        // If the width difference is very small, then select the width of the segment that is longer
        weighted_average_width = ab_length > bc_length ? A.w : B.w;
        return ab_length > bc_length ? width_diff * bc_length : width_diff * ab_length;
    }
}

}
<|endoftext|>


<commit_before>// Copyright 2015-2019 Elviss Strazdins. All rights reserved.

#include "core/Setup.h"

#if OUZEL_COMPILE_OPENGL

#include "OGLShader.hpp"
#include "OGLRenderDevice.hpp"

namespace ouzel
{
    namespace graphics
    {
        namespace opengl
        {
            Shader::Shader(RenderDevice& initRenderDevice,
                           const std::vector<uint8_t>& initFragmentShader,
                           const std::vector<uint8_t>& initVertexShader,
                           const std::set<Vertex::Attribute::Usage>& initVertexAttributes,
                           const std::vector<std::pair<std::string, DataType>>& initFragmentShaderConstantInfo,
                           const std::vector<std::pair<std::string, DataType>>& initVertexShaderConstantInfo,
                           uint32_t,
                           uint32_t,
                           const std::string&,
                           const std::string&):
                RenderResource(initRenderDevice),
                fragmentShaderData(initFragmentShader),
                vertexShaderData(initVertexShader),
                vertexAttributes(initVertexAttributes),
                fragmentShaderConstantInfo(initFragmentShaderConstantInfo),
                vertexShaderConstantInfo(initVertexShaderConstantInfo)
            {
                compileShader();
            }

            Shader::~Shader()
            {
                if (programId) renderDevice.deleteProgram(programId);
                if (vertexShaderId) renderDevice.glDeleteShaderProc(vertexShaderId);
                if (fragmentShaderId) renderDevice.glDeleteShaderProc(fragmentShaderId);
            }

            void Shader::reload()
            {
                fragmentShaderId = 0;
                vertexShaderId = 0;
                programId = 0;

                compileShader();
            }

            std::string Shader::getShaderMessage(GLuint shaderId) const
            {
                GLint logLength = 0;
                renderDevice.glGetShaderivProc(shaderId, GL_INFO_LOG_LENGTH, &logLength);

                if (logLength > 0)
                {
                    std::vector<char> logMessage(static_cast<size_t>(logLength));
                    renderDevice.glGetShaderInfoLogProc(shaderId, logLength, nullptr, logMessage.data());

                    return std::string(logMessage.data());
                }

                return std::string();
            }

            std::string Shader::getProgramMessage() const
            {
                GLint logLength = 0;
                renderDevice.glGetProgramivProc(programId, GL_INFO_LOG_LENGTH, &logLength);

                if (logLength > 0)
                {
                    std::vector<char> logMessage(static_cast<size_t>(logLength));
                    renderDevice.glGetProgramInfoLogProc(programId, logLength, nullptr, logMessage.data());

                    return std::string("Shader linking error: " + std::string(logMessage.data()));
                }

                return std::string();
            }

            void Shader::compileShader()
            {
                fragmentShaderId = renderDevice.glCreateShaderProc(GL_FRAGMENT_SHADER);

                auto fragmentShaderBuffer = reinterpret_cast<const GLchar*>(fragmentShaderData.data());
                auto fragmentShaderSize = static_cast<GLint>(fragmentShaderData.size());

                renderDevice.glShaderSourceProc(fragmentShaderId, 1, &fragmentShaderBuffer, &fragmentShaderSize);
                renderDevice.glCompileShaderProc(fragmentShaderId);

                GLint status;
                renderDevice.glGetShaderivProc(fragmentShaderId, GL_COMPILE_STATUS, &status);
                if (status == GL_FALSE)
                    throw std::runtime_error("Failed to compile pixel shader, error: " + getShaderMessage(fragmentShaderId));

                GLenum error;

                if ((error = renderDevice.glGetErrorProc()) != GL_NO_ERROR)
                    throw std::system_error(makeErrorCode(error), "Failed to get shader compile status");

                vertexShaderId = renderDevice.glCreateShaderProc(GL_VERTEX_SHADER);

                auto vertexShaderBuffer = reinterpret_cast<const GLchar*>(vertexShaderData.data());
                auto vertexShaderSize = static_cast<GLint>(vertexShaderData.size());

                renderDevice.glShaderSourceProc(vertexShaderId, 1, &vertexShaderBuffer, &vertexShaderSize);
                renderDevice.glCompileShaderProc(vertexShaderId);

                renderDevice.glGetShaderivProc(vertexShaderId, GL_COMPILE_STATUS, &status);
                if (status == GL_FALSE)
                    throw std::runtime_error("Failed to compile vertex shader, error: " + getShaderMessage(vertexShaderId));

                programId = renderDevice.glCreateProgramProc();

                renderDevice.glAttachShaderProc(programId, vertexShaderId);
                renderDevice.glAttachShaderProc(programId, fragmentShaderId);

                GLuint index = 0;

                for (const Vertex::Attribute& vertexAttribute : RenderDevice::VERTEX_ATTRIBUTES)
                {
                    if (vertexAttributes.find(vertexAttribute.usage) != vertexAttributes.end())
                    {
                        const GLchar* name;

                        switch (vertexAttribute.usage)
                        {
                            case Vertex::Attribute::Usage::BINORMAL:
                                name = "binormal0";
                                break;
                            case Vertex::Attribute::Usage::BLEND_INDICES:
                                name = "blendIndices0";
                                break;
                            case Vertex::Attribute::Usage::BLEND_WEIGHT:
                                name = "blendWeight0";
                                break;
                            case Vertex::Attribute::Usage::COLOR:
                                name = "color0";
                                break;
                            case Vertex::Attribute::Usage::NORMAL:
                                name = "normal0";
                                break;
                            case Vertex::Attribute::Usage::POSITION:
                                name = "position0";
                                break;
                            case Vertex::Attribute::Usage::POSITION_TRANSFORMED:
                                name = "positionT0";
                                break;
                            case Vertex::Attribute::Usage::POINT_SIZE:
                                name = "pointSize0";
                                break;
                            case Vertex::Attribute::Usage::TANGENT:
                                name = "tangent0";
                                break;
                            case Vertex::Attribute::Usage::TEXTURE_COORDINATES0:
                                name = "texCoord0";
                                break;
                            case Vertex::Attribute::Usage::TEXTURE_COORDINATES1:
                                name = "texCoord1";
                                break;
                            default:
                                throw std::runtime_error("Invalid vertex attribute usage");
                        }

                        renderDevice.glBindAttribLocationProc(programId, index, name);
                        ++index;
                    }
                }

                renderDevice.glLinkProgramProc(programId);

                renderDevice.glGetProgramivProc(programId, GL_LINK_STATUS, &status);
                if (status == GL_FALSE)
                    throw std::runtime_error("Failed to link shader" + getProgramMessage());

                if ((error = renderDevice.glGetErrorProc()) != GL_NO_ERROR)
                    throw std::system_error(makeErrorCode(error), "Failed to get shader link status");

                renderDevice.glDetachShaderProc(programId, vertexShaderId);
                renderDevice.glDeleteShaderProc(vertexShaderId);
                vertexShaderId = 0;

                renderDevice.glDetachShaderProc(programId, fragmentShaderId);
                renderDevice.glDeleteShaderProc(fragmentShaderId);
                fragmentShaderId = 0;

                if ((error = renderDevice.glGetErrorProc()) != GL_NO_ERROR)
                    throw std::system_error(makeErrorCode(error), "Failed to detach shader");

                renderDevice.useProgram(programId);

                GLint texture0Location = renderDevice.glGetUniformLocationProc(programId, "texture0");
                if (texture0Location != -1) renderDevice.glUniform1iProc(texture0Location, 0);

                GLint texture1Location = renderDevice.glGetUniformLocationProc(programId, "texture1");
                if (texture1Location != -1) renderDevice.glUniform1iProc(texture1Location, 1);

                if ((error = renderDevice.glGetErrorProc()) != GL_NO_ERROR)
                    throw std::system_error(makeErrorCode(error), "Failed to get uniform location");

                if (!fragmentShaderConstantInfo.empty())
                {
                    fragmentShaderConstantLocations.clear();
                    fragmentShaderConstantLocations.reserve(fragmentShaderConstantInfo.size());

                    for (const std::pair<std::string, DataType>& info : fragmentShaderConstantInfo)
                    {
                        GLint location = renderDevice.glGetUniformLocationProc(programId, info.first.c_str());

                        if ((error = renderDevice.glGetErrorProc()) != GL_NO_ERROR)
                            throw std::system_error(makeErrorCode(error), "Failed to get OpenGL uniform location");

                        if (location == -1)
                            throw std::runtime_error("Failed to get OpenGL uniform location");

                        fragmentShaderConstantLocations.emplace_back(location, info.second);
                    }
                }

                if (!vertexShaderConstantInfo.empty())
                {
                    vertexShaderConstantLocations.clear();
                    vertexShaderConstantLocations.reserve(vertexShaderConstantInfo.size());

                    for (const std::pair<std::string, DataType>& info : vertexShaderConstantInfo)
                    {
                        GLint location = renderDevice.glGetUniformLocationProc(programId, info.first.c_str());

                        if ((error = renderDevice.glGetErrorProc()) != GL_NO_ERROR)
                            throw std::system_error(makeErrorCode(error), "Failed to get OpenGL uniform location");

                        if (location == -1)
                            throw std::runtime_error("Failed to get OpenGL uniform location");

                        vertexShaderConstantLocations.emplace_back(location, info.second);
                    }
                }
            }
        } // namespace opengl
    } // namespace graphics
} // namespace ouzel

#endif
<commit_msg>Remove the redundant conversion to std::string<commit_after>// Copyright 2015-2019 Elviss Strazdins. All rights reserved.

#include "core/Setup.h"

#if OUZEL_COMPILE_OPENGL

#include "OGLShader.hpp"
#include "OGLRenderDevice.hpp"

namespace ouzel
{
    namespace graphics
    {
        namespace opengl
        {
            Shader::Shader(RenderDevice& initRenderDevice,
                           const std::vector<uint8_t>& initFragmentShader,
                           const std::vector<uint8_t>& initVertexShader,
                           const std::set<Vertex::Attribute::Usage>& initVertexAttributes,
                           const std::vector<std::pair<std::string, DataType>>& initFragmentShaderConstantInfo,
                           const std::vector<std::pair<std::string, DataType>>& initVertexShaderConstantInfo,
                           uint32_t,
                           uint32_t,
                           const std::string&,
                           const std::string&):
                RenderResource(initRenderDevice),
                fragmentShaderData(initFragmentShader),
                vertexShaderData(initVertexShader),
                vertexAttributes(initVertexAttributes),
                fragmentShaderConstantInfo(initFragmentShaderConstantInfo),
                vertexShaderConstantInfo(initVertexShaderConstantInfo)
            {
                compileShader();
            }

            Shader::~Shader()
            {
                if (programId) renderDevice.deleteProgram(programId);
                if (vertexShaderId) renderDevice.glDeleteShaderProc(vertexShaderId);
                if (fragmentShaderId) renderDevice.glDeleteShaderProc(fragmentShaderId);
            }

            void Shader::reload()
            {
                fragmentShaderId = 0;
                vertexShaderId = 0;
                programId = 0;

                compileShader();
            }

            std::string Shader::getShaderMessage(GLuint shaderId) const
            {
                GLint logLength = 0;
                renderDevice.glGetShaderivProc(shaderId, GL_INFO_LOG_LENGTH, &logLength);

                if (logLength > 0)
                {
                    std::vector<char> logMessage(static_cast<size_t>(logLength));
                    renderDevice.glGetShaderInfoLogProc(shaderId, logLength, nullptr, logMessage.data());

                    return std::string(logMessage.data());
                }

                return std::string();
            }

            std::string Shader::getProgramMessage() const
            {
                GLint logLength = 0;
                renderDevice.glGetProgramivProc(programId, GL_INFO_LOG_LENGTH, &logLength);

                if (logLength > 0)
                {
                    std::vector<char> logMessage(static_cast<size_t>(logLength));
                    renderDevice.glGetProgramInfoLogProc(programId, logLength, nullptr, logMessage.data());

                    return std::string("Shader linking error: ") + logMessage.data();
                }

                return std::string();
            }

            void Shader::compileShader()
            {
                fragmentShaderId = renderDevice.glCreateShaderProc(GL_FRAGMENT_SHADER);

                auto fragmentShaderBuffer = reinterpret_cast<const GLchar*>(fragmentShaderData.data());
                auto fragmentShaderSize = static_cast<GLint>(fragmentShaderData.size());

                renderDevice.glShaderSourceProc(fragmentShaderId, 1, &fragmentShaderBuffer, &fragmentShaderSize);
                renderDevice.glCompileShaderProc(fragmentShaderId);

                GLint status;
                renderDevice.glGetShaderivProc(fragmentShaderId, GL_COMPILE_STATUS, &status);
                if (status == GL_FALSE)
                    throw std::runtime_error("Failed to compile pixel shader, error: " + getShaderMessage(fragmentShaderId));

                GLenum error;

                if ((error = renderDevice.glGetErrorProc()) != GL_NO_ERROR)
                    throw std::system_error(makeErrorCode(error), "Failed to get shader compile status");

                vertexShaderId = renderDevice.glCreateShaderProc(GL_VERTEX_SHADER);

                auto vertexShaderBuffer = reinterpret_cast<const GLchar*>(vertexShaderData.data());
                auto vertexShaderSize = static_cast<GLint>(vertexShaderData.size());

                renderDevice.glShaderSourceProc(vertexShaderId, 1, &vertexShaderBuffer, &vertexShaderSize);
                renderDevice.glCompileShaderProc(vertexShaderId);

                renderDevice.glGetShaderivProc(vertexShaderId, GL_COMPILE_STATUS, &status);
                if (status == GL_FALSE)
                    throw std::runtime_error("Failed to compile vertex shader, error: " + getShaderMessage(vertexShaderId));

                programId = renderDevice.glCreateProgramProc();

                renderDevice.glAttachShaderProc(programId, vertexShaderId);
                renderDevice.glAttachShaderProc(programId, fragmentShaderId);

                GLuint index = 0;

                for (const Vertex::Attribute& vertexAttribute : RenderDevice::VERTEX_ATTRIBUTES)
                {
                    if (vertexAttributes.find(vertexAttribute.usage) != vertexAttributes.end())
                    {
                        const GLchar* name;

                        switch (vertexAttribute.usage)
                        {
                            case Vertex::Attribute::Usage::BINORMAL:
                                name = "binormal0";
                                break;
                            case Vertex::Attribute::Usage::BLEND_INDICES:
                                name = "blendIndices0";
                                break;
                            case Vertex::Attribute::Usage::BLEND_WEIGHT:
                                name = "blendWeight0";
                                break;
                            case Vertex::Attribute::Usage::COLOR:
                                name = "color0";
                                break;
                            case Vertex::Attribute::Usage::NORMAL:
                                name = "normal0";
                                break;
                            case Vertex::Attribute::Usage::POSITION:
                                name = "position0";
                                break;
                            case Vertex::Attribute::Usage::POSITION_TRANSFORMED:
                                name = "positionT0";
                                break;
                            case Vertex::Attribute::Usage::POINT_SIZE:
                                name = "pointSize0";
                                break;
                            case Vertex::Attribute::Usage::TANGENT:
                                name = "tangent0";
                                break;
                            case Vertex::Attribute::Usage::TEXTURE_COORDINATES0:
                                name = "texCoord0";
                                break;
                            case Vertex::Attribute::Usage::TEXTURE_COORDINATES1:
                                name = "texCoord1";
                                break;
                            default:
                                throw std::runtime_error("Invalid vertex attribute usage");
                        }

                        renderDevice.glBindAttribLocationProc(programId, index, name);
                        ++index;
                    }
                }

                renderDevice.glLinkProgramProc(programId);

                renderDevice.glGetProgramivProc(programId, GL_LINK_STATUS, &status);
                if (status == GL_FALSE)
                    throw std::runtime_error("Failed to link shader" + getProgramMessage());

                if ((error = renderDevice.glGetErrorProc()) != GL_NO_ERROR)
                    throw std::system_error(makeErrorCode(error), "Failed to get shader link status");

                renderDevice.glDetachShaderProc(programId, vertexShaderId);
                renderDevice.glDeleteShaderProc(vertexShaderId);
                vertexShaderId = 0;

                renderDevice.glDetachShaderProc(programId, fragmentShaderId);
                renderDevice.glDeleteShaderProc(fragmentShaderId);
                fragmentShaderId = 0;

                if ((error = renderDevice.glGetErrorProc()) != GL_NO_ERROR)
                    throw std::system_error(makeErrorCode(error), "Failed to detach shader");

                renderDevice.useProgram(programId);

                GLint texture0Location = renderDevice.glGetUniformLocationProc(programId, "texture0");
                if (texture0Location != -1) renderDevice.glUniform1iProc(texture0Location, 0);

                GLint texture1Location = renderDevice.glGetUniformLocationProc(programId, "texture1");
                if (texture1Location != -1) renderDevice.glUniform1iProc(texture1Location, 1);

                if ((error = renderDevice.glGetErrorProc()) != GL_NO_ERROR)
                    throw std::system_error(makeErrorCode(error), "Failed to get uniform location");

                if (!fragmentShaderConstantInfo.empty())
                {
                    fragmentShaderConstantLocations.clear();
                    fragmentShaderConstantLocations.reserve(fragmentShaderConstantInfo.size());

                    for (const std::pair<std::string, DataType>& info : fragmentShaderConstantInfo)
                    {
                        GLint location = renderDevice.glGetUniformLocationProc(programId, info.first.c_str());

                        if ((error = renderDevice.glGetErrorProc()) != GL_NO_ERROR)
                            throw std::system_error(makeErrorCode(error), "Failed to get OpenGL uniform location");

                        if (location == -1)
                            throw std::runtime_error("Failed to get OpenGL uniform location");

                        fragmentShaderConstantLocations.emplace_back(location, info.second);
                    }
                }

                if (!vertexShaderConstantInfo.empty())
                {
                    vertexShaderConstantLocations.clear();
                    vertexShaderConstantLocations.reserve(vertexShaderConstantInfo.size());

                    for (const std::pair<std::string, DataType>& info : vertexShaderConstantInfo)
                    {
                        GLint location = renderDevice.glGetUniformLocationProc(programId, info.first.c_str());

                        if ((error = renderDevice.glGetErrorProc()) != GL_NO_ERROR)
                            throw std::system_error(makeErrorCode(error), "Failed to get OpenGL uniform location");

                        if (location == -1)
                            throw std::runtime_error("Failed to get OpenGL uniform location");

                        vertexShaderConstantLocations.emplace_back(location, info.second);
                    }
                }
            }
        } // namespace opengl
    } // namespace graphics
} // namespace ouzel

#endif
<|endoftext|>