示例#1
0
void tie_managed_to_unmanaged(MonoObject *managed, Object *unmanaged) {

	// This method should not fail

	CRASH_COND(!unmanaged);

	// All mono objects created from the managed world (e.g.: `new Player()`)
	// need to have a CSharpScript in order for their methods to be callable from the unmanaged side

	Reference *ref = Object::cast_to<Reference>(unmanaged);

	GDMonoClass *klass = GDMonoUtils::get_object_class(managed);

	CRASH_COND(!klass);

	Ref<MonoGCHandle> gchandle = ref ? MonoGCHandle::create_weak(managed) :
									   MonoGCHandle::create_strong(managed);

	Ref<CSharpScript> script = CSharpScript::create_for_managed_type(klass);

	CRASH_COND(script.is_null());

	ScriptInstance *si = CSharpInstance::create_for_managed_type(unmanaged, script.ptr(), gchandle);

	unmanaged->set_script_and_instance(script.get_ref_ptr(), si);

	return;
}
示例#2
0
MonoObject *unmanaged_get_managed(Object *unmanaged) {
	if (unmanaged) {
		if (unmanaged->get_script_instance()) {
			CSharpInstance *cs_instance = CAST_CSHARP_INSTANCE(unmanaged->get_script_instance());

			if (cs_instance) {
				return cs_instance->get_mono_object();
			}
		}

		// If the owner does not have a CSharpInstance...

		void *data = unmanaged->get_script_instance_binding(CSharpLanguage::get_singleton()->get_language_index());

		if (data) {
			CSharpScriptBinding &script_binding = ((Map<Object *, CSharpScriptBinding>::Element *)data)->value();

			Ref<MonoGCHandle> &gchandle = script_binding.gchandle;
			ERR_FAIL_COND_V(gchandle.is_null(), NULL);

			MonoObject *target = gchandle->get_target();

			if (target)
				return target;

			CSharpLanguage::get_singleton()->release_script_gchandle(gchandle);

			// Create a new one

#ifdef DEBUG_ENABLED
			CRASH_COND(script_binding.type_name == StringName());
			CRASH_COND(script_binding.wrapper_class == NULL);
#endif

			MonoObject *mono_object = GDMonoUtils::create_managed_for_godot_object(script_binding.wrapper_class, script_binding.type_name, unmanaged);
			ERR_FAIL_NULL_V(mono_object, NULL);

			gchandle->set_handle(MonoGCHandle::new_strong_handle(mono_object), MonoGCHandle::STRONG_HANDLE);

			// Tie managed to unmanaged
			Reference *ref = Object::cast_to<Reference>(unmanaged);

			if (ref) {
				// Unsafe refcount increment. The managed instance also counts as a reference.
				// This way if the unmanaged world has no references to our owner
				// but the managed instance is alive, the refcount will be 1 instead of 0.
				// See: godot_icall_Reference_Dtor(MonoObject *p_obj, Object *p_ptr)

				ref->reference();
			}

			return mono_object;
		}
	}

	return NULL;
}
VoxelProviderThread::VoxelProviderThread(Ref<VoxelProvider> provider, int block_size_pow2) {

	CRASH_COND(provider.is_null());
	CRASH_COND(block_size_pow2 <= 0);

	_voxel_provider = provider;
	_block_size_pow2 = block_size_pow2;
	_input_mutex = Mutex::create();
	_output_mutex = Mutex::create();
	_semaphore = Semaphore::create();
	_thread_exit = false;
	_thread = Thread::create(_thread_func, this);
}
示例#4
0
文件: gd_mono.cpp 项目: torugok/godot
bool GDMono::_load_assembly(const String &p_name, GDMonoAssembly **r_assembly) {

	CRASH_COND(!r_assembly);

	if (OS::get_singleton()->is_stdout_verbose())
		OS::get_singleton()->print((String() + "Mono: Loading assembly " + p_name + "...\n").utf8());

	MonoImageOpenStatus status = MONO_IMAGE_OK;
	MonoAssemblyName *aname = mono_assembly_name_new(p_name.utf8());
	MonoAssembly *assembly = mono_assembly_load_full(aname, NULL, &status, false);
	mono_assembly_name_free(aname);

	if (!assembly)
		return false;

	uint32_t domain_id = mono_domain_get_id(mono_domain_get());

	GDMonoAssembly **stored_assembly = assemblies[domain_id].getptr(p_name);

	ERR_FAIL_COND_V(status != MONO_IMAGE_OK, false);
	ERR_FAIL_COND_V(stored_assembly == NULL, false);

	ERR_FAIL_COND_V((*stored_assembly)->get_assembly() != assembly, false);
	*r_assembly = *stored_assembly;

	if (OS::get_singleton()->is_stdout_verbose())
		OS::get_singleton()->print(String("Mono: Assembly " + p_name + " loaded from path: " + (*r_assembly)->get_path() + "\n").utf8());

	return true;
}
示例#5
0
bool GDMono::load_assembly(const String &p_name, MonoAssemblyName *p_aname, GDMonoAssembly **r_assembly, bool p_refonly) {

	CRASH_COND(!r_assembly);

	print_verbose("Mono: Loading assembly " + p_name + (p_refonly ? " (refonly)" : "") + "...");

	MonoImageOpenStatus status = MONO_IMAGE_OK;
	MonoAssembly *assembly = mono_assembly_load_full(p_aname, NULL, &status, p_refonly);

	if (!assembly)
		return false;

	ERR_FAIL_COND_V(status != MONO_IMAGE_OK, false);

	uint32_t domain_id = mono_domain_get_id(mono_domain_get());

	GDMonoAssembly **stored_assembly = assemblies[domain_id].getptr(p_name);

	ERR_FAIL_COND_V(stored_assembly == NULL, false);
	ERR_FAIL_COND_V((*stored_assembly)->get_assembly() != assembly, false);

	*r_assembly = *stored_assembly;

	print_verbose("Mono: Assembly " + p_name + (p_refonly ? " (refonly)" : "") + " loaded from path: " + (*r_assembly)->get_path());

	return true;
}
示例#6
0
void godot_icall_Object_Disposed(MonoObject *p_obj, Object *p_ptr) {
#ifdef DEBUG_ENABLED
	CRASH_COND(p_ptr == NULL);
#endif

	if (p_ptr->get_script_instance()) {
		CSharpInstance *cs_instance = CAST_CSHARP_INSTANCE(p_ptr->get_script_instance());
		if (cs_instance) {
			if (!cs_instance->is_destructing_script_instance()) {
				cs_instance->mono_object_disposed(p_obj);
				p_ptr->set_script_instance(NULL);
			}
			return;
		}
	}

	void *data = p_ptr->get_script_instance_binding(CSharpLanguage::get_singleton()->get_language_index());

	if (data) {
		CSharpScriptBinding &script_binding = ((Map<Object *, CSharpScriptBinding>::Element *)data)->get();
		if (script_binding.inited) {
			Ref<MonoGCHandle> &gchandle = script_binding.gchandle;
			if (gchandle.is_valid()) {
				CSharpLanguage::release_script_gchandle(p_obj, gchandle);
			}
		}
	}
}
示例#7
0
MonoObject *create_managed_from(const Dictionary &p_from, GDMonoClass *p_class) {
	MonoObject *mono_object = mono_object_new(SCRIPTS_DOMAIN, p_class->get_mono_ptr());
	ERR_FAIL_NULL_V(mono_object, NULL);

	// Search constructor that takes a pointer as parameter
	MonoMethod *m;
	void *iter = NULL;
	while ((m = mono_class_get_methods(p_class->get_mono_ptr(), &iter))) {
		if (strcmp(mono_method_get_name(m), ".ctor") == 0) {
			MonoMethodSignature *sig = mono_method_signature(m);
			void *front = NULL;
			if (mono_signature_get_param_count(sig) == 1 &&
					mono_class_from_mono_type(mono_signature_get_params(sig, &front)) == CACHED_CLASS(IntPtr)->get_mono_ptr()) {
				break;
			}
		}
	}

	CRASH_COND(m == NULL);

	Dictionary *new_dict = memnew(Dictionary(p_from));
	void *args[1] = { &new_dict };

	MonoException *exc = NULL;
	GDMonoUtils::runtime_invoke(m, mono_object, args, &exc);
	UNLIKELY_UNHANDLED_EXCEPTION(exc);

	return mono_object;
}
示例#8
0
Error GDMono::finalize_and_unload_domain(MonoDomain *p_domain) {

	CRASH_COND(p_domain == NULL);

	String domain_name = mono_domain_get_friendly_name(p_domain);

	print_verbose("Mono: Unloading domain `" + domain_name + "`...");

	if (mono_domain_get() != root_domain)
		mono_domain_set(root_domain, true);

	mono_gc_collect(mono_gc_max_generation());
	mono_domain_finalize(p_domain, 2000);
	mono_gc_collect(mono_gc_max_generation());

	_domain_assemblies_cleanup(mono_domain_get_id(p_domain));

	MonoException *exc = NULL;
	mono_domain_try_unload(p_domain, (MonoObject **)&exc);

	if (exc) {
		ERR_PRINTS("Exception thrown when unloading domain `" + domain_name + "`");
		GDMonoUtils::debug_unhandled_exception(exc);
		return FAILED;
	}

	return OK;
}
示例#9
0
Error ScriptClassParser::_parse_type_full_name(String &r_full_name) {

	Token tk = get_token();

	if (tk != TK_IDENTIFIER) {
		error_str = "Expected " + get_token_name(TK_IDENTIFIER) + ", found: " + get_token_name(tk);
		error = true;
		return ERR_PARSE_ERROR;
	}

	r_full_name += String(value);

	if (code[idx] == '<') {
		idx++;

		// We don't mind if the base is generic, but we skip it any ways since this information is not needed
		Error err = _skip_generic_type_params();
		if (err)
			return err;
	}

	if (code[idx] != '.') // We only want to take the next token if it's a period
		return OK;

	tk = get_token();

	CRASH_COND(tk != TK_PERIOD); // Assertion

	r_full_name += ".";

	return _parse_type_full_name(r_full_name);
}
示例#10
0
void godot_icall_Reference_Disposed(MonoObject *p_obj, Object *p_ptr, MonoBoolean p_is_finalizer) {
#ifdef DEBUG_ENABLED
	CRASH_COND(p_ptr == NULL);
	// This is only called with Reference derived classes
	CRASH_COND(!Object::cast_to<Reference>(p_ptr));
#endif

	Reference *ref = static_cast<Reference *>(p_ptr);

	if (ref->get_script_instance()) {
		CSharpInstance *cs_instance = CAST_CSHARP_INSTANCE(ref->get_script_instance());
		if (cs_instance) {
			if (!cs_instance->is_destructing_script_instance()) {
				bool delete_owner;
				bool remove_script_instance;

				cs_instance->mono_object_disposed_baseref(p_obj, p_is_finalizer, delete_owner, remove_script_instance);

				if (delete_owner) {
					memdelete(ref);
				} else if (remove_script_instance) {
					ref->set_script_instance(NULL);
				}
			}
			return;
		}
	}

	// Unsafe refcount decrement. The managed instance also counts as a reference.
	// See: CSharpLanguage::alloc_instance_binding_data(Object *p_object)
	if (ref->unreference()) {
		memdelete(ref);
	} else {
		void *data = ref->get_script_instance_binding(CSharpLanguage::get_singleton()->get_language_index());

		if (data) {
			CSharpScriptBinding &script_binding = ((Map<Object *, CSharpScriptBinding>::Element *)data)->get();
			if (script_binding.inited) {
				Ref<MonoGCHandle> &gchandle = script_binding.gchandle;
				if (gchandle.is_valid()) {
					CSharpLanguage::release_script_gchandle(p_obj, gchandle);
				}
			}
		}
	}
}
示例#11
0
void debug_send_unhandled_exception_error(MonoException *p_exc) {
#ifdef DEBUG_ENABLED
	if (!ScriptDebugger::get_singleton())
		return;

	_TLS_RECURSION_GUARD_;

	ScriptLanguage::StackInfo separator;
	separator.file = String();
	separator.func = "--- " + RTR("End of inner exception stack trace") + " ---";
	separator.line = 0;

	Vector<ScriptLanguage::StackInfo> si;
	String exc_msg;

	while (p_exc != NULL) {
		GDMonoClass *st_klass = CACHED_CLASS(System_Diagnostics_StackTrace);
		MonoObject *stack_trace = mono_object_new(mono_domain_get(), st_klass->get_mono_ptr());

		MonoBoolean need_file_info = true;
		void *ctor_args[2] = { p_exc, &need_file_info };

		MonoException *unexpected_exc = NULL;
		CACHED_METHOD(System_Diagnostics_StackTrace, ctor_Exception_bool)->invoke_raw(stack_trace, ctor_args, &unexpected_exc);

		if (unexpected_exc) {
			GDMonoInternals::unhandled_exception(unexpected_exc);
			_UNREACHABLE_();
		}

		Vector<ScriptLanguage::StackInfo> _si;
		if (stack_trace != NULL) {
			_si = CSharpLanguage::get_singleton()->stack_trace_get_info(stack_trace);
			for (int i = _si.size() - 1; i >= 0; i--)
				si.insert(0, _si[i]);
		}

		exc_msg += (exc_msg.length() > 0 ? " ---> " : "") + GDMonoUtils::get_exception_name_and_message(p_exc);

		GDMonoClass *exc_class = GDMono::get_singleton()->get_class(mono_get_exception_class());
		GDMonoProperty *inner_exc_prop = exc_class->get_property("InnerException");
		CRASH_COND(inner_exc_prop == NULL);

		MonoObject *inner_exc = inner_exc_prop->get_value((MonoObject *)p_exc);
		if (inner_exc != NULL)
			si.insert(0, separator);

		p_exc = (MonoException *)inner_exc;
	}

	String file = si.size() ? si[0].file : __FILE__;
	String func = si.size() ? si[0].func : FUNCTION_STR;
	int line = si.size() ? si[0].line : __LINE__;
	String error_msg = "Unhandled exception";

	ScriptDebugger::get_singleton()->send_error(func, file, line, error_msg, exc_msg, ERR_HANDLER_ERROR, si);
#endif
}
示例#12
0
void VoxelMeshUpdater::process_block(const InputBlock &block, OutputBlock &output) {

	CRASH_COND(block.voxels.is_null());

	// Build cubic parts of the mesh
	output.model_surfaces = _model_mesher->build(**block.voxels, Voxel::CHANNEL_TYPE, Vector3i(0, 0, 0), block.voxels->get_size() - Vector3(1, 1, 1));
	// Build smooth parts of the mesh
	output.smooth_surfaces = _smooth_mesher->build(**block.voxels, Voxel::CHANNEL_ISOLEVEL);

	output.position = block.position;
}
示例#13
0
bool GDMono::load_assembly(const String &p_name, GDMonoAssembly **r_assembly, bool p_refonly) {

	CRASH_COND(!r_assembly);

	MonoAssemblyName *aname = mono_assembly_name_new(p_name.utf8());
	bool result = load_assembly(p_name, aname, r_assembly, p_refonly);
	mono_assembly_name_free(aname);
	mono_free(aname);

	return result;
}
示例#14
0
MonoObject *godot_icall_Dictionary_GetValue(Dictionary *ptr, MonoObject *key) {
	Variant *ret = ptr->getptr(GDMonoMarshal::mono_object_to_variant(key));
	if (ret == NULL) {
		MonoObject *exc = mono_object_new(mono_domain_get(), CACHED_CLASS(KeyNotFoundException)->get_mono_ptr());
#ifdef DEBUG_ENABLED
		CRASH_COND(!exc);
#endif
		GDMonoUtils::runtime_object_init(exc);
		GDMonoUtils::set_pending_exception((MonoException *)exc);
		return NULL;
	}
	return GDMonoMarshal::variant_to_mono_object(ret);
}
示例#15
0
VoxelMeshUpdater::VoxelMeshUpdater(Ref<VoxelLibrary> library, MeshingParams params) {

	CRASH_COND(library.is_null());
	//CRASH_COND(params.materials.size() == 0);

	_model_mesher.instance();
	_model_mesher->set_library(library);
	_model_mesher->set_occlusion_enabled(params.baked_ao);
	_model_mesher->set_occlusion_darkness(params.baked_ao_darkness);

	_smooth_mesher.instance();

	_input_mutex = Mutex::create();
	_output_mutex = Mutex::create();

	_thread_exit = false;
	_semaphore = Semaphore::create();
	_thread = Thread::create(_thread_func, this);

	_needs_sort = true;
}
示例#16
0
Error ScriptClassParser::_parse_type_full_name(String &r_full_name) {

	Token tk = get_token();

	if (tk != TK_IDENTIFIER) {
		error_str = "Expected " + get_token_name(TK_IDENTIFIER) + ", found: " + get_token_name(tk);
		error = true;
		return ERR_PARSE_ERROR;
	}

	r_full_name += String(value);

	if (code[idx] != '.') // We only want to take the next token if it's a period
		return OK;

	tk = get_token();

	CRASH_COND(tk != TK_PERIOD); // Assertion

	r_full_name += ".";

	return _parse_type_full_name(r_full_name);
}
示例#17
0
void AreaBullet::main_shape_changed() {
	CRASH_COND(!get_main_shape())
	btGhost->setCollisionShape(get_main_shape());
}
示例#18
0
void GDMonoClass::fetch_methods_with_godot_api_checks(GDMonoClass *p_native_base) {

	CRASH_COND(!CACHED_CLASS(GodotObject)->is_assignable_from(this));

	if (methods_fetched)
		return;

	void *iter = NULL;
	MonoMethod *raw_method = NULL;
	while ((raw_method = mono_class_get_methods(get_mono_ptr(), &iter)) != NULL) {
		StringName name = mono_method_get_name(raw_method);

		// get_method implicitly fetches methods and adds them to this->methods
		GDMonoMethod *method = get_method(raw_method, name);
		ERR_CONTINUE(!method);

		if (method->get_name() != name) {

#ifdef DEBUG_ENABLED
			String fullname = method->get_ret_type_full_name() + " " + name + "(" + method->get_signature_desc(true) + ")";
			WARN_PRINTS("Method `" + fullname + "` is hidden by Godot API method. Should be `" +
						method->get_full_name_no_class() + "`. In class `" + namespace_name + "." + class_name + "`.");
#endif
			continue;
		}

#ifdef DEBUG_ENABLED
		// For debug builds, we also fetched from native base classes as well before if this is not a native base class.
		// This allows us to warn the user here if he is using snake_case by mistake.

		if (p_native_base != this) {

			GDMonoClass *native_top = p_native_base;
			while (native_top) {
				GDMonoMethod *m = native_top->get_method(name, method->get_parameters_count());

				if (m && m->get_name() != name) {
					// found
					String fullname = m->get_ret_type_full_name() + " " + name + "(" + m->get_signature_desc(true) + ")";
					WARN_PRINTS("Method `" + fullname + "` should be `" + m->get_full_name_no_class() +
								"`. In class `" + namespace_name + "." + class_name + "`.");
					break;
				}

				if (native_top == CACHED_CLASS(GodotObject))
					break;

				native_top = native_top->get_parent_class();
			}
		}
#endif

		uint32_t flags = mono_method_get_flags(method->mono_method, NULL);

		if (!(flags & MONO_METHOD_ATTR_VIRTUAL))
			continue;

		// Virtual method of Godot Object derived type, let's try to find GodotMethod attribute

		GDMonoClass *top = p_native_base;

		while (top) {
			GDMonoMethod *base_method = top->get_method(name, method->get_parameters_count());

			if (base_method && base_method->has_attribute(CACHED_CLASS(GodotMethodAttribute))) {
				// Found base method with GodotMethod attribute.
				// We get the original API method name from this attribute.
				// This name must point to the virtual method.

				MonoObject *attr = base_method->get_attribute(CACHED_CLASS(GodotMethodAttribute));

				StringName godot_method_name = CACHED_FIELD(GodotMethodAttribute, methodName)->get_string_value(attr);
#ifdef DEBUG_ENABLED
				CRASH_COND(godot_method_name == StringName());
#endif
				MethodKey key = MethodKey(godot_method_name, method->get_parameters_count());
				GDMonoMethod **existing_method = methods.getptr(key);
				if (existing_method)
					memdelete(*existing_method); // Must delete old one
				methods.set(key, method);

				break;
			}

			if (top == CACHED_CLASS(GodotObject))
				break;

			top = top->get_parent_class();
		}
	}

	methods_fetched = true;
}
示例#19
0
int32_t _GodotSharp::get_scripts_domain_id() {

	MonoDomain *domain = SCRIPTS_DOMAIN;
	CRASH_COND(!domain); // User must check if scripts domain is loaded before calling this method
	return mono_domain_get_id(domain);
}
示例#20
0
int32_t _GodotSharp::get_domain_id() {

	MonoDomain *domain = mono_domain_get();
	CRASH_COND(!domain); // User must check if runtime is initialized before calling this method
	return mono_domain_get_id(domain);
}
示例#21
0
Array VoxelMesher::build(const VoxelBuffer &buffer, unsigned int channel, Vector3i min, Vector3i max) {
	uint64_t time_before = OS::get_singleton()->get_ticks_usec();

	ERR_FAIL_COND_V(_library.is_null(), Array());
	ERR_FAIL_COND_V(channel >= VoxelBuffer::MAX_CHANNELS, Array());

	const VoxelLibrary &library = **_library;

	for (unsigned int i = 0; i < MAX_MATERIALS; ++i) {
		Arrays &a = _arrays[i];
		a.positions.clear();
		a.normals.clear();
		a.uvs.clear();
		a.colors.clear();
		a.indices.clear();
	}

	float baked_occlusion_darkness;
	if (_bake_occlusion)
		baked_occlusion_darkness = _baked_occlusion_darkness / 3.0;

	// The technique is Culled faces.
	// Could be improved with greedy meshing: https://0fps.net/2012/06/30/meshing-in-a-minecraft-game/
	// However I don't feel it's worth it yet:
	// - Not so much gain for organic worlds with lots of texture variations
	// - Works well with cubes but not with any shape
	// - Slower
	// => Could be implemented in a separate class?

	// Data must be padded, hence the off-by-one
	Vector3i::sort_min_max(min, max);
	const Vector3i pad(1, 1, 1);
	min.clamp_to(pad, max);
	max.clamp_to(min, buffer.get_size() - pad);

	int index_offset = 0;

	// Iterate 3D padded data to extract voxel faces.
	// This is the most intensive job in this class, so all required data should be as fit as possible.

	// The buffer we receive MUST be dense (i.e not compressed, and channels allocated).
	// That means we can use raw pointers to voxel data inside instead of using the higher-level getters,
	// and then save a lot of time.

	uint8_t *type_buffer = buffer.get_channel_raw(Voxel::CHANNEL_TYPE);
	//       _
	//      | \
	//     /\ \\
	//    / /|\\\
	//    | |\ \\\
	//    | \_\ \\|
	//    |    |  )
	//     \   |  |
	//      \    /
	CRASH_COND(type_buffer == NULL);


	//CRASH_COND(memarr_len(type_buffer) != buffer.get_volume() * sizeof(uint8_t));

	// Build lookup tables so to speed up voxel access.
	// These are values to add to an address in order to get given neighbor.

	int row_size = buffer.get_size().y;
	int deck_size = buffer.get_size().x * row_size;

	int side_neighbor_lut[Cube::SIDE_COUNT];
	side_neighbor_lut[Cube::SIDE_LEFT] = row_size;
	side_neighbor_lut[Cube::SIDE_RIGHT] = -row_size;
	side_neighbor_lut[Cube::SIDE_BACK] = -deck_size;
	side_neighbor_lut[Cube::SIDE_FRONT] = deck_size;
	side_neighbor_lut[Cube::SIDE_BOTTOM] = -1;
	side_neighbor_lut[Cube::SIDE_TOP] = 1;

	int edge_neighbor_lut[Cube::EDGE_COUNT];
	edge_neighbor_lut[Cube::EDGE_BOTTOM_BACK] = side_neighbor_lut[Cube::SIDE_BOTTOM] + side_neighbor_lut[Cube::SIDE_BACK];
	edge_neighbor_lut[Cube::EDGE_BOTTOM_FRONT] = side_neighbor_lut[Cube::SIDE_BOTTOM] + side_neighbor_lut[Cube::SIDE_FRONT];
	edge_neighbor_lut[Cube::EDGE_BOTTOM_LEFT] = side_neighbor_lut[Cube::SIDE_BOTTOM] + side_neighbor_lut[Cube::SIDE_LEFT];
	edge_neighbor_lut[Cube::EDGE_BOTTOM_RIGHT] = side_neighbor_lut[Cube::SIDE_BOTTOM] + side_neighbor_lut[Cube::SIDE_RIGHT];
	edge_neighbor_lut[Cube::EDGE_BACK_LEFT] = side_neighbor_lut[Cube::SIDE_BACK] + side_neighbor_lut[Cube::SIDE_LEFT];
	edge_neighbor_lut[Cube::EDGE_BACK_RIGHT] = side_neighbor_lut[Cube::SIDE_BACK] + side_neighbor_lut[Cube::SIDE_RIGHT];
	edge_neighbor_lut[Cube::EDGE_FRONT_LEFT] = side_neighbor_lut[Cube::SIDE_FRONT] + side_neighbor_lut[Cube::SIDE_LEFT];
	edge_neighbor_lut[Cube::EDGE_FRONT_RIGHT] = side_neighbor_lut[Cube::SIDE_FRONT] + side_neighbor_lut[Cube::SIDE_RIGHT];
	edge_neighbor_lut[Cube::EDGE_TOP_BACK] = side_neighbor_lut[Cube::SIDE_TOP] + side_neighbor_lut[Cube::SIDE_BACK];
	edge_neighbor_lut[Cube::EDGE_TOP_FRONT] = side_neighbor_lut[Cube::SIDE_TOP] + side_neighbor_lut[Cube::SIDE_FRONT];
	edge_neighbor_lut[Cube::EDGE_TOP_LEFT] = side_neighbor_lut[Cube::SIDE_TOP] + side_neighbor_lut[Cube::SIDE_LEFT];
	edge_neighbor_lut[Cube::EDGE_TOP_RIGHT] = side_neighbor_lut[Cube::SIDE_TOP] + side_neighbor_lut[Cube::SIDE_RIGHT];

	int corner_neighbor_lut[Cube::CORNER_COUNT];
	corner_neighbor_lut[Cube::CORNER_BOTTOM_BACK_LEFT] = side_neighbor_lut[Cube::SIDE_BOTTOM] + side_neighbor_lut[Cube::SIDE_BACK] + side_neighbor_lut[Cube::SIDE_LEFT];
	corner_neighbor_lut[Cube::CORNER_BOTTOM_BACK_RIGHT] = side_neighbor_lut[Cube::SIDE_BOTTOM] + side_neighbor_lut[Cube::SIDE_BACK] + side_neighbor_lut[Cube::SIDE_RIGHT];
	corner_neighbor_lut[Cube::CORNER_BOTTOM_FRONT_RIGHT] = side_neighbor_lut[Cube::SIDE_BOTTOM] + side_neighbor_lut[Cube::SIDE_FRONT] + side_neighbor_lut[Cube::SIDE_RIGHT];
	corner_neighbor_lut[Cube::CORNER_BOTTOM_FRONT_LEFT] = side_neighbor_lut[Cube::SIDE_BOTTOM] + side_neighbor_lut[Cube::SIDE_FRONT] + side_neighbor_lut[Cube::SIDE_LEFT];
	corner_neighbor_lut[Cube::CORNER_TOP_BACK_LEFT] = side_neighbor_lut[Cube::SIDE_TOP] + side_neighbor_lut[Cube::SIDE_BACK] + side_neighbor_lut[Cube::SIDE_LEFT];
	corner_neighbor_lut[Cube::CORNER_TOP_BACK_RIGHT] = side_neighbor_lut[Cube::SIDE_TOP] + side_neighbor_lut[Cube::SIDE_BACK] + side_neighbor_lut[Cube::SIDE_RIGHT];
	corner_neighbor_lut[Cube::CORNER_TOP_FRONT_RIGHT] = side_neighbor_lut[Cube::SIDE_TOP] + side_neighbor_lut[Cube::SIDE_FRONT] + side_neighbor_lut[Cube::SIDE_RIGHT];
	corner_neighbor_lut[Cube::CORNER_TOP_FRONT_LEFT] = side_neighbor_lut[Cube::SIDE_TOP] + side_neighbor_lut[Cube::SIDE_FRONT] + side_neighbor_lut[Cube::SIDE_LEFT];

	uint64_t time_prep = OS::get_singleton()->get_ticks_usec() - time_before;
	time_before = OS::get_singleton()->get_ticks_usec();

	for (unsigned int z = min.z; z < max.z; ++z) {
		for (unsigned int x = min.x; x < max.x; ++x) {
			for (unsigned int y = min.y; y < max.y; ++y) {
				// min and max are chosen such that you can visit 1 neighbor away from the current voxel without size check

				// TODO In this intensive routine, there is a way to make voxel access fastest by getting a pointer to the channel,
				// and using offset lookup to get neighbors rather than going through get_voxel validations
				int voxel_index = y + x * row_size + z * deck_size;
				int voxel_id = type_buffer[voxel_index];

				if (voxel_id != 0 && library.has_voxel(voxel_id)) {

					const Voxel &voxel = library.get_voxel_const(voxel_id);

					Arrays &arrays = _arrays[voxel.get_material_id()];

					// Hybrid approach: extract cube faces and decimate those that aren't visible,
					// and still allow voxels to have geometry that is not a cube

					// Sides
					for (unsigned int side = 0; side < Cube::SIDE_COUNT; ++side) {

						const PoolVector<Vector3> &positions = voxel.get_model_side_positions(side);
						int vertex_count = positions.size();

						if (vertex_count != 0) {

							int neighbor_voxel_id = type_buffer[voxel_index + side_neighbor_lut[side]];

							// TODO Better face visibility test
							if (is_face_visible(library, voxel, neighbor_voxel_id)) {

								// The face is visible

								int shaded_corner[8] = { 0 };

								if (_bake_occlusion) {

									// Combinatory solution for https://0fps.net/2013/07/03/ambient-occlusion-for-minecraft-like-worlds/

									for (unsigned int j = 0; j < 4; ++j) {
										unsigned int edge = Cube::g_side_edges[side][j];
										int edge_neighbor_id = type_buffer[voxel_index + edge_neighbor_lut[edge]];
										if (!is_transparent(library, edge_neighbor_id)) {
											shaded_corner[Cube::g_edge_corners[edge][0]] += 1;
											shaded_corner[Cube::g_edge_corners[edge][1]] += 1;
										}
									}
									for (unsigned int j = 0; j < 4; ++j) {
										unsigned int corner = Cube::g_side_corners[side][j];
										if (shaded_corner[corner] == 2) {
											shaded_corner[corner] = 3;
										} else {
											int corner_neigbor_id = type_buffer[voxel_index + corner_neighbor_lut[corner]];
											if (!is_transparent(library, corner_neigbor_id)) {
												shaded_corner[corner] += 1;
											}
										}
									}
								}

								PoolVector<Vector3>::Read rv = positions.read();
								PoolVector<Vector2>::Read rt = voxel.get_model_side_uv(side).read();

								// Subtracting 1 because the data is padded
								Vector3 pos(x - 1, y - 1, z - 1);

								// Append vertices of the faces in one go, don't use push_back

								{
									int append_index = arrays.positions.size();
									arrays.positions.resize(arrays.positions.size() + vertex_count);
									Vector3 *w = arrays.positions.ptrw() + append_index;
									for (unsigned int i = 0; i < vertex_count; ++i) {
										w[i] = rv[i] + pos;
									}
								}

								{
									int append_index = arrays.uvs.size();
									arrays.uvs.resize(arrays.uvs.size() + vertex_count);
									memcpy(arrays.uvs.ptrw() + append_index, rt.ptr(), vertex_count * sizeof(Vector2));
								}

								{
									int append_index = arrays.normals.size();
									arrays.normals.resize(arrays.normals.size() + vertex_count);
									Vector3 *w = arrays.normals.ptrw() + append_index;
									for (unsigned int i = 0; i < vertex_count; ++i) {
										w[i] = Cube::g_side_normals[side].to_vec3();
									}
								}

								if (_bake_occlusion) {
									// Use color array

									int append_index = arrays.colors.size();
									arrays.colors.resize(arrays.colors.size() + vertex_count);
									Color *w = arrays.colors.ptrw() + append_index;

									for (unsigned int i = 0; i < vertex_count; ++i) {
										Vector3 v = rv[i];

										// General purpose occlusion colouring.
										// TODO Optimize for cubes
										// TODO Fix occlusion inconsistency caused by triangles orientation? Not sure if worth it
										float shade = 0;
										for (unsigned int j = 0; j < 4; ++j) {
											unsigned int corner = Cube::g_side_corners[side][j];
											if (shaded_corner[corner]) {
												float s = baked_occlusion_darkness * static_cast<float>(shaded_corner[corner]);
												float k = 1.0 - Cube::g_corner_position[corner].distance_to(v);
												if (k < 0.0)
													k = 0.0;
												s *= k;
												if (s > shade)
													shade = s;
											}
										}
										float gs = 1.0 - shade;
										w[i] = Color(gs, gs, gs);
									}
								}

								const PoolVector<int> &side_indices = voxel.get_model_side_indices(side);
								PoolVector<int>::Read ri = side_indices.read();
								unsigned int index_count = side_indices.size();

								{
									int i = arrays.indices.size();
									arrays.indices.resize(arrays.indices.size() + index_count);
									int *w = arrays.indices.ptrw();
									for(unsigned int j = 0; j < index_count; ++j) {
										w[i++] = index_offset + ri[j];
									}
								}

								index_offset += vertex_count;
							}
						}
					}

					// Inside
					if (voxel.get_model_positions().size() != 0) {
						// TODO Get rid of push_backs

						const PoolVector<Vector3> &vertices = voxel.get_model_positions();
						int vertex_count = vertices.size();

						PoolVector<Vector3>::Read rv = vertices.read();
						PoolVector<Vector3>::Read rn = voxel.get_model_normals().read();
						PoolVector<Vector2>::Read rt = voxel.get_model_uv().read();

						Vector3 pos(x - 1, y - 1, z - 1);

						for (unsigned int i = 0; i < vertex_count; ++i) {
							arrays.normals.push_back(rn[i]);
							arrays.uvs.push_back(rt[i]);
							arrays.positions.push_back(rv[i] + pos);
						}

						if(_bake_occlusion) {
							// TODO handle ambient occlusion on inner parts
							arrays.colors.push_back(Color(1,1,1));
						}

						const PoolVector<int> &indices = voxel.get_model_indices();
						PoolVector<int>::Read ri = indices.read();
						unsigned int index_count = indices.size();

						for(unsigned int i = 0; i < index_count; ++i) {
							arrays.indices.push_back(index_offset + ri[i]);
						}

						index_offset += vertex_count;
					}
				}
			}
		}
	}

	uint64_t time_meshing = OS::get_singleton()->get_ticks_usec() - time_before;
	time_before = OS::get_singleton()->get_ticks_usec();

	// Commit mesh

//	print_line(String("Made mesh v: ") + String::num(_arrays[0].positions.size())
//			+ String(", i: ") + String::num(_arrays[0].indices.size()));

	Array surfaces;

	// TODO We could return a single byte array and use Mesh::add_surface down the line?

	for (int i = 0; i < MAX_MATERIALS; ++i) {

		const Arrays &arrays = _arrays[i];
		if (arrays.positions.size() != 0) {

			/*print_line("Arrays:");
			for(int i = 0; i < arrays.positions.size(); ++i)
				print_line(String("  P {0}").format(varray(arrays.positions[i])));
			for(int i = 0; i < arrays.normals.size(); ++i)
				print_line(String("  N {0}").format(varray(arrays.normals[i])));
			for(int i = 0; i < arrays.uvs.size(); ++i)
				print_line(String("  UV {0}").format(varray(arrays.uvs[i])));*/

			Array mesh_arrays;
			mesh_arrays.resize(Mesh::ARRAY_MAX);

			{
				PoolVector<Vector3> positions;
				PoolVector<Vector2> uvs;
				PoolVector<Vector3> normals;
				PoolVector<Color> colors;
				PoolVector<int> indices;

				raw_copy_to(positions, arrays.positions);
				raw_copy_to(uvs, arrays.uvs);
				raw_copy_to(normals, arrays.normals);
				raw_copy_to(colors, arrays.colors);
				raw_copy_to(indices, arrays.indices);

				mesh_arrays[Mesh::ARRAY_VERTEX] = positions;
				mesh_arrays[Mesh::ARRAY_TEX_UV] = uvs;
				mesh_arrays[Mesh::ARRAY_NORMAL] = normals;
				mesh_arrays[Mesh::ARRAY_COLOR] = colors;
				mesh_arrays[Mesh::ARRAY_INDEX] = indices;
			}

			surfaces.append(mesh_arrays);
		}
	}

	uint64_t time_commit = OS::get_singleton()->get_ticks_usec() - time_before;

	//print_line(String("P: {0}, M: {1}, C: {2}").format(varray(time_prep, time_meshing, time_commit)));

	return surfaces;
}