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; }
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); }
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; }
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; }
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); } } } }
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; }
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; }
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); }
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); } } } } }
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 }
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; }
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; }
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); }
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; }
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); }
void AreaBullet::main_shape_changed() { CRASH_COND(!get_main_shape()) btGhost->setCollisionShape(get_main_shape()); }
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; }
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); }
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); }
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; }