Browse Source

-Add lightmapper
-Fixes to unwrapper (remove degenerates), makes Thekla not crash
-Added optional cancel button in EditorProgress
-Added function to force processing of events (needed for cancel button)

Juan Linietsky 7 years ago
parent
commit
f3ad14224e
44 changed files with 4584 additions and 1136 deletions
  1. 1 0
      SConstruct
  2. 1 0
      core/os/os.h
  3. 28 0
      drivers/gles3/rasterizer_scene_gles3.cpp
  4. 15 13
      drivers/gles3/rasterizer_scene_gles3.h
  5. 117 0
      drivers/gles3/rasterizer_storage_gles3.cpp
  6. 32 0
      drivers/gles3/rasterizer_storage_gles3.h
  7. 64 12
      drivers/gles3/shaders/scene.glsl
  8. 6 4
      editor/editor_node.cpp
  9. 5 5
      editor/editor_node.h
  10. 2 2
      editor/import/resource_importer_scene.cpp
  11. 95 0
      editor/plugins/baked_lightmap_editor_plugin.cpp
  12. 39 0
      editor/plugins/baked_lightmap_editor_plugin.h
  13. 32 4
      editor/progress_dialog.cpp
  14. 9 2
      editor/progress_dialog.h
  15. 121 0
      editor/spatial_editor_gizmos.cpp
  16. 17 0
      editor/spatial_editor_gizmos.h
  17. 4 3
      modules/thekla_unwrap/register_types.cpp
  18. 2 0
      platform/osx/os_osx.h
  19. 7 0
      platform/osx/os_osx.mm
  20. 7 0
      platform/windows/detect.py
  21. 4 0
      platform/windows/os_windows.cpp
  22. 2 0
      platform/windows/os_windows.h
  23. 6 0
      platform/x11/detect.py
  24. 7 0
      platform/x11/os_x11.cpp
  25. 1 0
      platform/x11/os_x11.h
  26. 717 0
      scene/3d/baked_lightmap.cpp
  27. 189 0
      scene/3d/baked_lightmap.h
  28. 31 995
      scene/3d/gi_probe.cpp
  29. 7 73
      scene/3d/gi_probe.h
  30. 17 0
      scene/3d/light.cpp
  31. 11 0
      scene/3d/light.h
  32. 2373 0
      scene/3d/voxel_light_baker.cpp
  33. 148 0
      scene/3d/voxel_light_baker.h
  34. 1 1
      scene/main/node.cpp
  35. 3 0
      scene/register_scene_types.cpp
  36. 15 4
      scene/resources/material.cpp
  37. 2 1
      scene/resources/material.h
  38. 15 13
      scene/resources/mesh.cpp
  39. 31 0
      servers/visual/rasterizer.h
  40. 19 0
      servers/visual/visual_server_raster.h
  41. 328 4
      servers/visual/visual_server_scene.cpp
  42. 18 0
      servers/visual/visual_server_scene.h
  43. 18 0
      servers/visual/visual_server_wrap_mt.h
  44. 17 0
      servers/visual_server.h

+ 1 - 0
SConstruct

@@ -168,6 +168,7 @@ opts.Add(BoolVariable('vsproj', "Generate Visual Studio Project.", False))
 opts.Add(EnumVariable('warnings', "Set the level of warnings emitted during compilation", 'no', ('extra', 'all', 'moderate', 'no')))
 opts.Add(EnumVariable('warnings', "Set the level of warnings emitted during compilation", 'no', ('extra', 'all', 'moderate', 'no')))
 opts.Add(BoolVariable('progress', "Show a progress indicator during build", True))
 opts.Add(BoolVariable('progress', "Show a progress indicator during build", True))
 opts.Add(BoolVariable('dev', "If yes, alias for verbose=yes warnings=all", False))
 opts.Add(BoolVariable('dev', "If yes, alias for verbose=yes warnings=all", False))
+opts.Add(BoolVariable('openmp', "If yes, enable OpenMP", True))
 
 
 # Thirdparty libraries
 # Thirdparty libraries
 opts.Add(BoolVariable('builtin_enet', "Use the builtin enet library", True))
 opts.Add(BoolVariable('builtin_enet', "Use the builtin enet library", True))

+ 1 - 0
core/os/os.h

@@ -442,6 +442,7 @@ public:
 	virtual int get_power_seconds_left();
 	virtual int get_power_seconds_left();
 	virtual int get_power_percent_left();
 	virtual int get_power_percent_left();
 
 
+	virtual void force_process_input(){};
 	bool has_feature(const String &p_feature);
 	bool has_feature(const String &p_feature);
 
 
 	/**
 	/**

+ 28 - 0
drivers/gles3/rasterizer_scene_gles3.cpp

@@ -1849,6 +1849,20 @@ void RasterizerSceneGLES3::_setup_light(RenderList::Element *e, const Transform
 
 
 			state.scene_shader.set_uniform(SceneShaderGLES3::GI_PROBE2_ENABLED, false);
 			state.scene_shader.set_uniform(SceneShaderGLES3::GI_PROBE2_ENABLED, false);
 		}
 		}
+	} else if (!e->instance->lightmap_capture_data.empty()) {
+
+		glUniform4fv(state.scene_shader.get_uniform_location(SceneShaderGLES3::LIGHTMAP_CAPTURES), 12, (const GLfloat *)e->instance->lightmap_capture_data.ptr());
+		state.scene_shader.set_uniform(SceneShaderGLES3::LIGHTMAP_CAPTURE_SKY, false);
+
+	} else if (e->instance->lightmap.is_valid()) {
+		RasterizerStorageGLES3::Texture *lightmap = storage->texture_owner.getornull(e->instance->lightmap);
+		RasterizerStorageGLES3::LightmapCapture *capture = storage->lightmap_capture_data_owner.getornull(e->instance->lightmap_capture->base);
+
+		if (lightmap && capture) {
+			glActiveTexture(GL_TEXTURE0 + storage->config.max_texture_image_units - 9);
+			glBindTexture(GL_TEXTURE_2D, lightmap->tex_id);
+			state.scene_shader.set_uniform(SceneShaderGLES3::LIGHTMAP_ENERGY, capture->energy);
+		}
 	}
 	}
 }
 }
 
 
@@ -1971,6 +1985,8 @@ void RasterizerSceneGLES3::_render_list(RenderList::Element **p_elements, int p_
 					state.scene_shader.set_conditional(SceneShaderGLES3::SHADOW_MODE_PCF_5, false);
 					state.scene_shader.set_conditional(SceneShaderGLES3::SHADOW_MODE_PCF_5, false);
 					state.scene_shader.set_conditional(SceneShaderGLES3::SHADOW_MODE_PCF_13, false);
 					state.scene_shader.set_conditional(SceneShaderGLES3::SHADOW_MODE_PCF_13, false);
 					state.scene_shader.set_conditional(SceneShaderGLES3::USE_GI_PROBES, false);
 					state.scene_shader.set_conditional(SceneShaderGLES3::USE_GI_PROBES, false);
+					state.scene_shader.set_conditional(SceneShaderGLES3::USE_LIGHTMAP_CAPTURE, false);
+					state.scene_shader.set_conditional(SceneShaderGLES3::USE_LIGHTMAP, false);
 					state.scene_shader.set_conditional(SceneShaderGLES3::USE_RADIANCE_MAP, false);
 					state.scene_shader.set_conditional(SceneShaderGLES3::USE_RADIANCE_MAP, false);
 					state.scene_shader.set_conditional(SceneShaderGLES3::USE_CONTACT_SHADOWS, false);
 					state.scene_shader.set_conditional(SceneShaderGLES3::USE_CONTACT_SHADOWS, false);
 
 
@@ -1978,6 +1994,8 @@ void RasterizerSceneGLES3::_render_list(RenderList::Element **p_elements, int p_
 				} else {
 				} else {
 
 
 					state.scene_shader.set_conditional(SceneShaderGLES3::USE_GI_PROBES, e->instance->gi_probe_instances.size() > 0);
 					state.scene_shader.set_conditional(SceneShaderGLES3::USE_GI_PROBES, e->instance->gi_probe_instances.size() > 0);
+					state.scene_shader.set_conditional(SceneShaderGLES3::USE_LIGHTMAP, e->instance->lightmap.is_valid() && e->instance->gi_probe_instances.size() == 0);
+					state.scene_shader.set_conditional(SceneShaderGLES3::USE_LIGHTMAP_CAPTURE, !e->instance->lightmap_capture_data.empty() && !e->instance->lightmap.is_valid() && e->instance->gi_probe_instances.size() == 0);
 
 
 					state.scene_shader.set_conditional(SceneShaderGLES3::SHADELESS, false);
 					state.scene_shader.set_conditional(SceneShaderGLES3::SHADELESS, false);
 
 
@@ -2148,6 +2166,8 @@ void RasterizerSceneGLES3::_render_list(RenderList::Element **p_elements, int p_
 	state.scene_shader.set_conditional(SceneShaderGLES3::SHADOW_MODE_PCF_5, false);
 	state.scene_shader.set_conditional(SceneShaderGLES3::SHADOW_MODE_PCF_5, false);
 	state.scene_shader.set_conditional(SceneShaderGLES3::SHADOW_MODE_PCF_13, false);
 	state.scene_shader.set_conditional(SceneShaderGLES3::SHADOW_MODE_PCF_13, false);
 	state.scene_shader.set_conditional(SceneShaderGLES3::USE_GI_PROBES, false);
 	state.scene_shader.set_conditional(SceneShaderGLES3::USE_GI_PROBES, false);
+	state.scene_shader.set_conditional(SceneShaderGLES3::USE_LIGHTMAP, false);
+	state.scene_shader.set_conditional(SceneShaderGLES3::USE_LIGHTMAP_CAPTURE, false);
 	state.scene_shader.set_conditional(SceneShaderGLES3::USE_CONTACT_SHADOWS, false);
 	state.scene_shader.set_conditional(SceneShaderGLES3::USE_CONTACT_SHADOWS, false);
 	state.scene_shader.set_conditional(SceneShaderGLES3::USE_VERTEX_LIGHTING, false);
 	state.scene_shader.set_conditional(SceneShaderGLES3::USE_VERTEX_LIGHTING, false);
 	state.scene_shader.set_conditional(SceneShaderGLES3::USE_OPAQUE_PREPASS, false);
 	state.scene_shader.set_conditional(SceneShaderGLES3::USE_OPAQUE_PREPASS, false);
@@ -2274,6 +2294,14 @@ void RasterizerSceneGLES3::_add_geometry_with_material(RasterizerStorageGLES3::G
 			e->sort_key |= SORT_KEY_GI_PROBES_FLAG;
 			e->sort_key |= SORT_KEY_GI_PROBES_FLAG;
 		}
 		}
 
 
+		if (e->instance->lightmap.is_valid()) {
+			e->sort_key |= SORT_KEY_LIGHTMAP_FLAG;
+		}
+
+		if (!e->instance->lightmap_capture_data.empty()) {
+			e->sort_key |= SORT_KEY_LIGHTMAP_CAPTURE_FLAG;
+		}
+
 		e->sort_key |= uint64_t(p_material->render_priority + 128) << RenderList::SORT_KEY_PRIORITY_SHIFT;
 		e->sort_key |= uint64_t(p_material->render_priority + 128) << RenderList::SORT_KEY_PRIORITY_SHIFT;
 	} else {
 	} else {
 		e->sort_key |= uint64_t(e->instance->depth_layer) << RenderList::SORT_KEY_OPAQUE_DEPTH_LAYER_SHIFT;
 		e->sort_key |= uint64_t(e->instance->depth_layer) << RenderList::SORT_KEY_OPAQUE_DEPTH_LAYER_SHIFT;

+ 15 - 13
drivers/gles3/rasterizer_scene_gles3.h

@@ -662,19 +662,21 @@ public:
 			SORT_KEY_OPAQUE_DEPTH_LAYER_SHIFT = 52,
 			SORT_KEY_OPAQUE_DEPTH_LAYER_SHIFT = 52,
 			SORT_KEY_OPAQUE_DEPTH_LAYER_MASK = 0xF,
 			SORT_KEY_OPAQUE_DEPTH_LAYER_MASK = 0xF,
 //64 bits unsupported in MSVC
 //64 bits unsupported in MSVC
-#define SORT_KEY_UNSHADED_FLAG (uint64_t(1) << 51)
-#define SORT_KEY_NO_DIRECTIONAL_FLAG (uint64_t(1) << 50)
-#define SORT_KEY_GI_PROBES_FLAG (uint64_t(1) << 49)
-#define SORT_KEY_VERTEX_LIT_FLAG (uint64_t(1) << 48)
-			SORT_KEY_SHADING_SHIFT = 48,
-			SORT_KEY_SHADING_MASK = 15,
-			//48-32 material index
-			SORT_KEY_MATERIAL_INDEX_SHIFT = 32,
-			//32-12 geometry index
-			SORT_KEY_GEOMETRY_INDEX_SHIFT = 12,
-			//bits 12-8 geometry type
-			SORT_KEY_GEOMETRY_TYPE_SHIFT = 8,
-			//bits 0-7 for flags
+#define SORT_KEY_UNSHADED_FLAG (uint64_t(1) << 49)
+#define SORT_KEY_NO_DIRECTIONAL_FLAG (uint64_t(1) << 48)
+#define SORT_KEY_LIGHTMAP_CAPTURE_FLAG (uint64_t(1) << 47)
+#define SORT_KEY_LIGHTMAP_FLAG (uint64_t(1) << 46)
+#define SORT_KEY_GI_PROBES_FLAG (uint64_t(1) << 45)
+#define SORT_KEY_VERTEX_LIT_FLAG (uint64_t(1) << 44)
+			SORT_KEY_SHADING_SHIFT = 44,
+			SORT_KEY_SHADING_MASK = 63,
+			//44-28 material index
+			SORT_KEY_MATERIAL_INDEX_SHIFT = 28,
+			//28-8 geometry index
+			SORT_KEY_GEOMETRY_INDEX_SHIFT = 8,
+			//bits 5-7 geometry type
+			SORT_KEY_GEOMETRY_TYPE_SHIFT = 5,
+			//bits 0-5 for flags
 			SORT_KEY_OPAQUE_PRE_PASS = 8,
 			SORT_KEY_OPAQUE_PRE_PASS = 8,
 			SORT_KEY_CULL_DISABLED_FLAG = 4,
 			SORT_KEY_CULL_DISABLED_FLAG = 4,
 			SORT_KEY_SKELETON_FLAG = 2,
 			SORT_KEY_SKELETON_FLAG = 2,

+ 117 - 0
drivers/gles3/rasterizer_storage_gles3.cpp

@@ -5216,6 +5216,104 @@ void RasterizerStorageGLES3::gi_probe_dynamic_data_update(RID p_gi_probe_data, i
 	//glTexImage3D(GL_TEXTURE_3D,p_mipmap,GL_RGBA8,gipd->width>>p_mipmap,gipd->height>>p_mipmap,gipd->depth>>p_mipmap,0,GL_RGBA,GL_UNSIGNED_BYTE,p_data);
 	//glTexImage3D(GL_TEXTURE_3D,p_mipmap,GL_RGBA8,gipd->width>>p_mipmap,gipd->height>>p_mipmap,gipd->depth>>p_mipmap,0,GL_RGBA,GL_UNSIGNED_BYTE,p_data);
 	//glTexImage3D(GL_TEXTURE_3D,p_mipmap,GL_RGBA8,gipd->width>>p_mipmap,gipd->height>>p_mipmap,gipd->depth>>p_mipmap,0,GL_RGBA,GL_UNSIGNED_BYTE,data.ptr());
 	//glTexImage3D(GL_TEXTURE_3D,p_mipmap,GL_RGBA8,gipd->width>>p_mipmap,gipd->height>>p_mipmap,gipd->depth>>p_mipmap,0,GL_RGBA,GL_UNSIGNED_BYTE,data.ptr());
 }
 }
+/////////////////////////////
+
+RID RasterizerStorageGLES3::lightmap_capture_create() {
+
+	LightmapCapture *capture = memnew(LightmapCapture);
+	return lightmap_capture_data_owner.make_rid(capture);
+}
+
+void RasterizerStorageGLES3::lightmap_capture_set_bounds(RID p_capture, const AABB &p_bounds) {
+
+	LightmapCapture *capture = lightmap_capture_data_owner.getornull(p_capture);
+	ERR_FAIL_COND(!capture);
+	capture->bounds = p_bounds;
+	capture->instance_change_notify();
+}
+AABB RasterizerStorageGLES3::lightmap_capture_get_bounds(RID p_capture) const {
+
+	const LightmapCapture *capture = lightmap_capture_data_owner.getornull(p_capture);
+	ERR_FAIL_COND_V(!capture, AABB());
+	return capture->bounds;
+}
+void RasterizerStorageGLES3::lightmap_capture_set_octree(RID p_capture, const PoolVector<uint8_t> &p_octree) {
+
+	LightmapCapture *capture = lightmap_capture_data_owner.getornull(p_capture);
+	ERR_FAIL_COND(!capture);
+
+	ERR_FAIL_COND(p_octree.size() == 0 || (p_octree.size() % sizeof(LightmapCaptureOctree)) != 0);
+
+	capture->octree.resize(p_octree.size() / sizeof(LightmapCaptureOctree));
+	if (p_octree.size()) {
+		PoolVector<LightmapCaptureOctree>::Write w = capture->octree.write();
+		PoolVector<uint8_t>::Read r = p_octree.read();
+		copymem(w.ptr(), r.ptr(), p_octree.size());
+	}
+	capture->instance_change_notify();
+}
+PoolVector<uint8_t> RasterizerStorageGLES3::lightmap_capture_get_octree(RID p_capture) const {
+
+	const LightmapCapture *capture = lightmap_capture_data_owner.getornull(p_capture);
+	ERR_FAIL_COND_V(!capture, PoolVector<uint8_t>());
+
+	if (capture->octree.size() == 0)
+		return PoolVector<uint8_t>();
+
+	PoolVector<uint8_t> ret;
+	ret.resize(capture->octree.size() * sizeof(LightmapCaptureOctree));
+	{
+		PoolVector<LightmapCaptureOctree>::Read r = capture->octree.read();
+		PoolVector<uint8_t>::Write w = ret.write();
+		copymem(w.ptr(), r.ptr(), ret.size());
+	}
+
+	return ret;
+}
+
+void RasterizerStorageGLES3::lightmap_capture_set_octree_cell_transform(RID p_capture, const Transform &p_xform) {
+	LightmapCapture *capture = lightmap_capture_data_owner.getornull(p_capture);
+	ERR_FAIL_COND(!capture);
+	capture->cell_xform = p_xform;
+}
+
+Transform RasterizerStorageGLES3::lightmap_capture_get_octree_cell_transform(RID p_capture) const {
+	const LightmapCapture *capture = lightmap_capture_data_owner.getornull(p_capture);
+	ERR_FAIL_COND_V(!capture, Transform());
+	return capture->cell_xform;
+}
+
+void RasterizerStorageGLES3::lightmap_capture_set_octree_cell_subdiv(RID p_capture, int p_subdiv) {
+	LightmapCapture *capture = lightmap_capture_data_owner.getornull(p_capture);
+	ERR_FAIL_COND(!capture);
+	capture->cell_subdiv = p_subdiv;
+}
+
+int RasterizerStorageGLES3::lightmap_capture_get_octree_cell_subdiv(RID p_capture) const {
+	const LightmapCapture *capture = lightmap_capture_data_owner.getornull(p_capture);
+	ERR_FAIL_COND_V(!capture, 0);
+	return capture->cell_subdiv;
+}
+
+void RasterizerStorageGLES3::lightmap_capture_set_energy(RID p_capture, float p_energy) {
+
+	LightmapCapture *capture = lightmap_capture_data_owner.getornull(p_capture);
+	ERR_FAIL_COND(!capture);
+	capture->energy = p_energy;
+}
+
+float RasterizerStorageGLES3::lightmap_capture_get_energy(RID p_capture) const {
+
+	const LightmapCapture *capture = lightmap_capture_data_owner.getornull(p_capture);
+	ERR_FAIL_COND_V(!capture, 0);
+	return capture->energy;
+}
+
+const PoolVector<RasterizerStorage::LightmapCaptureOctree> *RasterizerStorageGLES3::lightmap_capture_get_octree_ptr(RID p_capture) const {
+	const LightmapCapture *capture = lightmap_capture_data_owner.getornull(p_capture);
+	ERR_FAIL_COND_V(!capture, NULL);
+	return &capture->octree;
+}
 
 
 ///////
 ///////
 
 
@@ -5817,6 +5915,10 @@ void RasterizerStorageGLES3::instance_add_dependency(RID p_base, RasterizerScene
 			inst = gi_probe_owner.getornull(p_base);
 			inst = gi_probe_owner.getornull(p_base);
 			ERR_FAIL_COND(!inst);
 			ERR_FAIL_COND(!inst);
 		} break;
 		} break;
+		case VS::INSTANCE_LIGHTMAP_CAPTURE: {
+			inst = lightmap_capture_data_owner.getornull(p_base);
+			ERR_FAIL_COND(!inst);
+		} break;
 		default: {
 		default: {
 			if (!inst) {
 			if (!inst) {
 				ERR_FAIL();
 				ERR_FAIL();
@@ -5860,6 +5962,10 @@ void RasterizerStorageGLES3::instance_remove_dependency(RID p_base, RasterizerSc
 			inst = gi_probe_owner.getornull(p_base);
 			inst = gi_probe_owner.getornull(p_base);
 			ERR_FAIL_COND(!inst);
 			ERR_FAIL_COND(!inst);
 		} break;
 		} break;
+		case VS::INSTANCE_LIGHTMAP_CAPTURE: {
+			inst = lightmap_capture_data_owner.getornull(p_base);
+			ERR_FAIL_COND(!inst);
+		} break;
 		default: {
 		default: {
 
 
 			if (!inst) {
 			if (!inst) {
@@ -6609,6 +6715,10 @@ VS::InstanceType RasterizerStorageGLES3::get_base_type(RID p_rid) const {
 		return VS::INSTANCE_GI_PROBE;
 		return VS::INSTANCE_GI_PROBE;
 	}
 	}
 
 
+	if (lightmap_capture_data_owner.owns(p_rid)) {
+		return VS::INSTANCE_LIGHTMAP_CAPTURE;
+	}
+
 	return VS::INSTANCE_NONE;
 	return VS::INSTANCE_NONE;
 }
 }
 
 
@@ -6795,6 +6905,13 @@ bool RasterizerStorageGLES3::free(RID p_rid) {
 		glDeleteTextures(1, &gi_probe_data->tex_id);
 		glDeleteTextures(1, &gi_probe_data->tex_id);
 		gi_probe_owner.free(p_rid);
 		gi_probe_owner.free(p_rid);
 		memdelete(gi_probe_data);
 		memdelete(gi_probe_data);
+	} else if (lightmap_capture_data_owner.owns(p_rid)) {
+
+		// delete the texture
+		LightmapCapture *lightmap_capture = lightmap_capture_data_owner.get(p_rid);
+
+		gi_probe_owner.free(p_rid);
+		memdelete(lightmap_capture);
 
 
 	} else if (canvas_occluder_owner.owns(p_rid)) {
 	} else if (canvas_occluder_owner.owns(p_rid)) {
 
 

+ 32 - 0
drivers/gles3/rasterizer_storage_gles3.h

@@ -1069,6 +1069,38 @@ public:
 	virtual RID gi_probe_dynamic_data_create(int p_width, int p_height, int p_depth, GIProbeCompression p_compression);
 	virtual RID gi_probe_dynamic_data_create(int p_width, int p_height, int p_depth, GIProbeCompression p_compression);
 	virtual void gi_probe_dynamic_data_update(RID p_gi_probe_data, int p_depth_slice, int p_slice_count, int p_mipmap, const void *p_data);
 	virtual void gi_probe_dynamic_data_update(RID p_gi_probe_data, int p_depth_slice, int p_slice_count, int p_mipmap, const void *p_data);
 
 
+	/* LIGHTMAP CAPTURE */
+
+	virtual RID lightmap_capture_create();
+	virtual void lightmap_capture_set_bounds(RID p_capture, const AABB &p_bounds);
+	virtual AABB lightmap_capture_get_bounds(RID p_capture) const;
+	virtual void lightmap_capture_set_octree(RID p_capture, const PoolVector<uint8_t> &p_octree);
+	virtual PoolVector<uint8_t> lightmap_capture_get_octree(RID p_capture) const;
+	virtual void lightmap_capture_set_octree_cell_transform(RID p_capture, const Transform &p_xform);
+	virtual Transform lightmap_capture_get_octree_cell_transform(RID p_capture) const;
+	virtual void lightmap_capture_set_octree_cell_subdiv(RID p_capture, int p_subdiv);
+	virtual int lightmap_capture_get_octree_cell_subdiv(RID p_capture) const;
+
+	virtual void lightmap_capture_set_energy(RID p_capture, float p_energy);
+	virtual float lightmap_capture_get_energy(RID p_capture) const;
+
+	virtual const PoolVector<LightmapCaptureOctree> *lightmap_capture_get_octree_ptr(RID p_capture) const;
+
+	struct LightmapCapture : public Instantiable {
+
+		PoolVector<LightmapCaptureOctree> octree;
+		AABB bounds;
+		Transform cell_xform;
+		int cell_subdiv;
+		float energy;
+		LightmapCapture() {
+			energy = 1.0;
+			cell_subdiv = 1;
+		}
+	};
+
+	mutable RID_Owner<LightmapCapture> lightmap_capture_data_owner;
+
 	/* PARTICLES */
 	/* PARTICLES */
 
 
 	struct Particles : public GeometryOwner {
 	struct Particles : public GeometryOwner {

+ 64 - 12
drivers/gles3/shaders/scene.glsl

@@ -35,7 +35,7 @@ layout(location=3) in vec4 color_attrib;
 layout(location=4) in vec2 uv_attrib;
 layout(location=4) in vec2 uv_attrib;
 #endif
 #endif
 
 
-#if defined(ENABLE_UV2_INTERP)
+#if defined(ENABLE_UV2_INTERP) || defined(USE_LIGHTMAP)
 layout(location=5) in vec2 uv2_attrib;
 layout(location=5) in vec2 uv2_attrib;
 #endif
 #endif
 
 
@@ -223,7 +223,7 @@ out vec4 color_interp;
 out vec2 uv_interp;
 out vec2 uv_interp;
 #endif
 #endif
 
 
-#if defined(ENABLE_UV2_INTERP)
+#if defined(ENABLE_UV2_INTERP) || defined (USE_LIGHTMAP)
 out vec2 uv2_interp;
 out vec2 uv2_interp;
 #endif
 #endif
 
 
@@ -234,9 +234,6 @@ out vec3 binormal_interp;
 #endif
 #endif
 
 
 
 
-
-
-
 #if defined(USE_MATERIAL)
 #if defined(USE_MATERIAL)
 
 
 layout(std140) uniform UniformData { //ubo:1
 layout(std140) uniform UniformData { //ubo:1
@@ -356,7 +353,7 @@ void main() {
 	uv_interp = uv_attrib;
 	uv_interp = uv_attrib;
 #endif
 #endif
 
 
-#if defined(ENABLE_UV2_INTERP)
+#if defined(ENABLE_UV2_INTERP) || defined(USE_LIGHTMAP)
 	uv2_interp = uv2_attrib;
 	uv2_interp = uv2_attrib;
 #endif
 #endif
 
 
@@ -549,7 +546,7 @@ in vec4 color_interp;
 in vec2 uv_interp;
 in vec2 uv_interp;
 #endif
 #endif
 
 
-#if defined(ENABLE_UV2_INTERP)
+#if defined(ENABLE_UV2_INTERP) || defined(USE_LIGHTMAP)
 in vec2 uv2_interp;
 in vec2 uv2_interp;
 #endif
 #endif
 
 
@@ -1357,7 +1354,7 @@ void reflection_process(int idx, vec3 vertex, vec3 normal,vec3 binormal, vec3 ta
 
 
 		reflection_accum+=reflection;
 		reflection_accum+=reflection;
 	}
 	}
-
+#ifndef USE_LIGHTMAP
 	if (reflections[idx].ambient.a>0.0) { //compute ambient using skybox
 	if (reflections[idx].ambient.a>0.0) { //compute ambient using skybox
 
 
 
 
@@ -1403,8 +1400,20 @@ void reflection_process(int idx, vec3 vertex, vec3 normal,vec3 binormal, vec3 ta
 		ambient_accum+=ambient_out;
 		ambient_accum+=ambient_out;
 
 
 	}
 	}
+#endif
 }
 }
 
 
+#ifdef USE_LIGHTMAP
+uniform mediump sampler2D lightmap; //texunit:-9
+uniform mediump float lightmap_energy;
+#endif
+
+#ifdef USE_LIGHTMAP_CAPTURE
+uniform mediump vec4[12] lightmap_captures;
+uniform bool lightmap_capture_sky;
+
+#endif
+
 #ifdef USE_GI_PROBES
 #ifdef USE_GI_PROBES
 
 
 uniform mediump sampler3D gi_probe1; //texunit:-9
 uniform mediump sampler3D gi_probe1; //texunit:-9
@@ -1632,7 +1641,7 @@ void main() {
 	vec2 uv = uv_interp;
 	vec2 uv = uv_interp;
 #endif
 #endif
 
 
-#if defined(ENABLE_UV2_INTERP)
+#if defined(ENABLE_UV2_INTERP) || defined (USE_LIGHTMAP)
 	vec2 uv2 = uv2_interp;
 	vec2 uv2 = uv2_interp;
 #endif
 #endif
 
 
@@ -1745,7 +1754,7 @@ FRAGMENT_SHADER_CODE
 			//vec3 radiance = textureLod(radiance_cube, r, lod).xyz * ( brdf.x + brdf.y);
 			//vec3 radiance = textureLod(radiance_cube, r, lod).xyz * ( brdf.x + brdf.y);
 
 
 		}
 		}
-
+#ifndef USE_LIGHTMAP
 		{
 		{
 
 
 			vec3 ambient_dir=normalize((radiance_inverse_xform * vec4(normal,0.0)).xyz);
 			vec3 ambient_dir=normalize((radiance_inverse_xform * vec4(normal,0.0)).xyz);
@@ -1754,6 +1763,7 @@ FRAGMENT_SHADER_CODE
 			ambient_light=mix(ambient_light_color.rgb,env_ambient,radiance_ambient_contribution);
 			ambient_light=mix(ambient_light_color.rgb,env_ambient,radiance_ambient_contribution);
 			//ambient_light=vec3(0.0,0.0,0.0);
 			//ambient_light=vec3(0.0,0.0,0.0);
 		}
 		}
+#endif
 	}
 	}
 
 
 #else
 #else
@@ -1938,6 +1948,48 @@ FRAGMENT_SHADER_CODE
 
 
 #endif
 #endif
 
 
+#ifdef USE_LIGHTMAP
+	ambient_light = texture(lightmap,uv2).rgb * lightmap_energy;
+#endif
+
+#ifdef USE_LIGHTMAP_CAPTURE
+	{
+		vec3 cone_dirs[12] = vec3[] (
+			vec3(0, 0, 1),
+			vec3(0.866025, 0, 0.5),
+			vec3(0.267617, 0.823639, 0.5),
+			vec3(-0.700629, 0.509037, 0.5),
+			vec3(-0.700629, -0.509037, 0.5),
+			vec3(0.267617, -0.823639, 0.5),
+			vec3(0, 0, -1),
+			vec3(0.866025, 0, -0.5),
+			vec3(0.267617, 0.823639, -0.5),
+			vec3(-0.700629, 0.509037, -0.5),
+			vec3(-0.700629, -0.509037, -0.5),
+			vec3(0.267617, -0.823639, -0.5)
+		);
+
+
+		vec3 local_normal = normalize(camera_matrix * vec4(normal,0.0)).xyz;
+		vec4 captured = vec4(0.0);
+		float sum = 0.0;
+		for(int i=0;i<12;i++) {
+			float amount = max(0.0,dot(local_normal,cone_dirs[i])); //not correct, but creates a nice wrap around effect
+			captured += lightmap_captures[i]*amount;
+			sum+=amount;
+		}
+
+		captured/=sum;
+
+		if (lightmap_capture_sky) {
+			ambient_light = mix( ambient_light, captured.rgb, captured.a);
+		} else {
+			ambient_light = captured.rgb;
+		}
+
+	}
+#endif
+
 #ifdef USE_FORWARD_LIGHTING
 #ifdef USE_FORWARD_LIGHTING
 
 
 
 
@@ -1952,11 +2004,11 @@ FRAGMENT_SHADER_CODE
 	} else {
 	} else {
 		specular_light+=env_reflection_light;
 		specular_light+=env_reflection_light;
 	}
 	}
-
+#ifndef USE_LIGHTMAP
 	if (ambient_accum.a>0.0) {
 	if (ambient_accum.a>0.0) {
 		ambient_light+=ambient_accum.rgb/ambient_accum.a;
 		ambient_light+=ambient_accum.rgb/ambient_accum.a;
 	}
 	}
-
+#endif
 
 
 
 
 #ifdef USE_VERTEX_LIGHTING
 #ifdef USE_VERTEX_LIGHTING

+ 6 - 4
editor/editor_node.cpp

@@ -67,6 +67,7 @@
 #include "editor/plugins/animation_player_editor_plugin.h"
 #include "editor/plugins/animation_player_editor_plugin.h"
 #include "editor/plugins/animation_tree_editor_plugin.h"
 #include "editor/plugins/animation_tree_editor_plugin.h"
 #include "editor/plugins/asset_library_editor_plugin.h"
 #include "editor/plugins/asset_library_editor_plugin.h"
+#include "editor/plugins/baked_lightmap_editor_plugin.h"
 #include "editor/plugins/camera_editor_plugin.h"
 #include "editor/plugins/camera_editor_plugin.h"
 #include "editor/plugins/canvas_item_editor_plugin.h"
 #include "editor/plugins/canvas_item_editor_plugin.h"
 #include "editor/plugins/collision_polygon_2d_editor_plugin.h"
 #include "editor/plugins/collision_polygon_2d_editor_plugin.h"
@@ -3384,14 +3385,14 @@ void EditorNode::stop_child_process() {
 	_menu_option_confirm(RUN_STOP, false);
 	_menu_option_confirm(RUN_STOP, false);
 }
 }
 
 
-void EditorNode::progress_add_task(const String &p_task, const String &p_label, int p_steps) {
+void EditorNode::progress_add_task(const String &p_task, const String &p_label, int p_steps, bool p_can_cancel) {
 
 
-	singleton->progress_dialog->add_task(p_task, p_label, p_steps);
+	singleton->progress_dialog->add_task(p_task, p_label, p_steps, p_can_cancel);
 }
 }
 
 
-void EditorNode::progress_task_step(const String &p_task, const String &p_state, int p_step, bool p_force_refresh) {
+bool EditorNode::progress_task_step(const String &p_task, const String &p_state, int p_step, bool p_force_refresh) {
 
 
-	singleton->progress_dialog->task_step(p_task, p_state, p_step, p_force_refresh);
+	return singleton->progress_dialog->task_step(p_task, p_state, p_step, p_force_refresh);
 }
 }
 
 
 void EditorNode::progress_end_task(const String &p_task) {
 void EditorNode::progress_end_task(const String &p_task) {
@@ -5659,6 +5660,7 @@ EditorNode::EditorNode() {
 	add_editor_plugin(memnew(TextureRegionEditorPlugin(this)));
 	add_editor_plugin(memnew(TextureRegionEditorPlugin(this)));
 	add_editor_plugin(memnew(Particles2DEditorPlugin(this)));
 	add_editor_plugin(memnew(Particles2DEditorPlugin(this)));
 	add_editor_plugin(memnew(GIProbeEditorPlugin(this)));
 	add_editor_plugin(memnew(GIProbeEditorPlugin(this)));
+	add_editor_plugin(memnew(BakedLightmapEditorPlugin(this)));
 	add_editor_plugin(memnew(Path2DEditorPlugin(this)));
 	add_editor_plugin(memnew(Path2DEditorPlugin(this)));
 	add_editor_plugin(memnew(PathEditorPlugin(this)));
 	add_editor_plugin(memnew(PathEditorPlugin(this)));
 	add_editor_plugin(memnew(Line2DEditorPlugin(this)));
 	add_editor_plugin(memnew(Line2DEditorPlugin(this)));

+ 5 - 5
editor/editor_node.h

@@ -745,8 +745,8 @@ public:
 
 
 	static void add_io_error(const String &p_error);
 	static void add_io_error(const String &p_error);
 
 
-	static void progress_add_task(const String &p_task, const String &p_label, int p_steps);
-	static void progress_task_step(const String &p_task, const String &p_state, int p_step = -1, bool p_force_refresh = true);
+	static void progress_add_task(const String &p_task, const String &p_label, int p_steps, bool p_can_cancel = false);
+	static bool progress_task_step(const String &p_task, const String &p_state, int p_step = -1, bool p_force_refresh = true);
 	static void progress_end_task(const String &p_task);
 	static void progress_end_task(const String &p_task);
 
 
 	static void progress_add_task_bg(const String &p_task, const String &p_label, int p_steps);
 	static void progress_add_task_bg(const String &p_task, const String &p_label, int p_steps);
@@ -807,9 +807,9 @@ public:
 struct EditorProgress {
 struct EditorProgress {
 
 
 	String task;
 	String task;
-	void step(const String &p_state, int p_step = -1, bool p_force_refresh = true) { EditorNode::progress_task_step(task, p_state, p_step, p_force_refresh); }
-	EditorProgress(const String &p_task, const String &p_label, int p_amount) {
-		EditorNode::progress_add_task(p_task, p_label, p_amount);
+	bool step(const String &p_state, int p_step = -1, bool p_force_refresh = true) { return EditorNode::progress_task_step(task, p_state, p_step, p_force_refresh); }
+	EditorProgress(const String &p_task, const String &p_label, int p_amount, bool p_can_cancel = false) {
+		EditorNode::progress_add_task(p_task, p_label, p_amount, p_can_cancel);
 		task = p_task;
 		task = p_task;
 	}
 	}
 	~EditorProgress() { EditorNode::progress_end_task(task); }
 	~EditorProgress() { EditorNode::progress_end_task(task); }

+ 2 - 2
editor/import/resource_importer_scene.cpp

@@ -1165,8 +1165,8 @@ void ResourceImporterScene::get_import_options(List<ImportOption> *r_options, in
 	r_options->push_back(ImportOption(PropertyInfo(Variant::BOOL, "meshes/compress"), true));
 	r_options->push_back(ImportOption(PropertyInfo(Variant::BOOL, "meshes/compress"), true));
 	r_options->push_back(ImportOption(PropertyInfo(Variant::BOOL, "meshes/ensure_tangents"), true));
 	r_options->push_back(ImportOption(PropertyInfo(Variant::BOOL, "meshes/ensure_tangents"), true));
 	r_options->push_back(ImportOption(PropertyInfo(Variant::INT, "meshes/storage", PROPERTY_HINT_ENUM, "Built-In,Files"), meshes_out ? 1 : 0));
 	r_options->push_back(ImportOption(PropertyInfo(Variant::INT, "meshes/storage", PROPERTY_HINT_ENUM, "Built-In,Files"), meshes_out ? 1 : 0));
-	r_options->push_back(ImportOption(PropertyInfo(Variant::INT, "meshes/light_baking", PROPERTY_HINT_ENUM, "Disabled,Enable,Gen Lightmaps"), 0));
-	r_options->push_back(ImportOption(PropertyInfo(Variant::REAL, "meshes/lightmap_texel_size", PROPERTY_HINT_RANGE, "0.001,100,0.001"), 0.05));
+	r_options->push_back(ImportOption(PropertyInfo(Variant::INT, "meshes/light_baking", PROPERTY_HINT_ENUM, "Disabled,Enable,Gen Lightmaps", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_UPDATE_ALL_IF_MODIFIED), 0));
+	r_options->push_back(ImportOption(PropertyInfo(Variant::REAL, "meshes/lightmap_texel_size", PROPERTY_HINT_RANGE, "0.001,100,0.001"), 0.1));
 	r_options->push_back(ImportOption(PropertyInfo(Variant::BOOL, "external_files/store_in_subdir"), false));
 	r_options->push_back(ImportOption(PropertyInfo(Variant::BOOL, "external_files/store_in_subdir"), false));
 	r_options->push_back(ImportOption(PropertyInfo(Variant::BOOL, "animation/import", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_UPDATE_ALL_IF_MODIFIED), true));
 	r_options->push_back(ImportOption(PropertyInfo(Variant::BOOL, "animation/import", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_UPDATE_ALL_IF_MODIFIED), true));
 	r_options->push_back(ImportOption(PropertyInfo(Variant::REAL, "animation/fps", PROPERTY_HINT_RANGE, "1,120,1"), 15));
 	r_options->push_back(ImportOption(PropertyInfo(Variant::REAL, "animation/fps", PROPERTY_HINT_RANGE, "1,120,1"), 15));

+ 95 - 0
editor/plugins/baked_lightmap_editor_plugin.cpp

@@ -0,0 +1,95 @@
+#include "baked_lightmap_editor_plugin.h"
+
+void BakedLightmapEditorPlugin::_bake() {
+
+	if (lightmap) {
+		BakedLightmap::BakeError err;
+		if (get_tree()->get_edited_scene_root() && get_tree()->get_edited_scene_root() == lightmap) {
+			err = lightmap->bake(lightmap);
+		} else {
+			err = lightmap->bake(lightmap->get_parent());
+		}
+
+		switch (err) {
+			case BakedLightmap::BAKE_ERROR_NO_SAVE_PATH:
+				EditorNode::get_singleton()->show_warning(TTR("Can't determine a save path for lightmap images.\nSave your scene (for images to be saved in the same dir), or pick a save path from the BakedLightmap properties."));
+				break;
+			case BakedLightmap::BAKE_ERROR_NO_MESHES:
+				EditorNode::get_singleton()->show_warning(TTR("No meshes to bake. Make sure they contain an UV2 channel and that the 'Bake Light' flag is on."));
+				break;
+			case BakedLightmap::BAKE_ERROR_CANT_CREATE_IMAGE:
+				EditorNode::get_singleton()->show_warning(TTR("Failed creating lightmap images, make sure path is writable."));
+				break;
+			defaut : {}
+		}
+	}
+}
+
+void BakedLightmapEditorPlugin::edit(Object *p_object) {
+
+	BakedLightmap *s = Object::cast_to<BakedLightmap>(p_object);
+	if (!s)
+		return;
+
+	lightmap = s;
+}
+
+bool BakedLightmapEditorPlugin::handles(Object *p_object) const {
+
+	return p_object->is_class("BakedLightmap");
+}
+
+void BakedLightmapEditorPlugin::make_visible(bool p_visible) {
+
+	if (p_visible) {
+		bake->show();
+	} else {
+
+		bake->hide();
+	}
+}
+
+EditorProgress *BakedLightmapEditorPlugin::tmp_progress = NULL;
+
+void BakedLightmapEditorPlugin::bake_func_begin(int p_steps) {
+
+	ERR_FAIL_COND(tmp_progress != NULL);
+
+	tmp_progress = memnew(EditorProgress("bake_lightmaps", TTR("Bake Lightmaps"), p_steps, true));
+}
+
+bool BakedLightmapEditorPlugin::bake_func_step(int p_step, const String &p_description) {
+
+	ERR_FAIL_COND_V(tmp_progress == NULL, false);
+	return tmp_progress->step(p_description, p_step);
+}
+
+void BakedLightmapEditorPlugin::bake_func_end() {
+	ERR_FAIL_COND(tmp_progress == NULL);
+	memdelete(tmp_progress);
+	tmp_progress = NULL;
+}
+
+void BakedLightmapEditorPlugin::_bind_methods() {
+
+	ClassDB::bind_method("_bake", &BakedLightmapEditorPlugin::_bake);
+}
+
+BakedLightmapEditorPlugin::BakedLightmapEditorPlugin(EditorNode *p_node) {
+
+	editor = p_node;
+	bake = memnew(Button);
+	bake->set_icon(editor->get_gui_base()->get_icon("BakedLight", "EditorIcons"));
+	bake->set_text(TTR("Bake Lightmaps"));
+	bake->hide();
+	bake->connect("pressed", this, "_bake");
+	add_control_to_container(CONTAINER_SPATIAL_EDITOR_MENU, bake);
+	lightmap = NULL;
+
+	BakedLightmap::bake_begin_function = bake_func_begin;
+	BakedLightmap::bake_step_function = bake_func_step;
+	BakedLightmap::bake_end_function = bake_func_end;
+}
+
+BakedLightmapEditorPlugin::~BakedLightmapEditorPlugin() {
+}

+ 39 - 0
editor/plugins/baked_lightmap_editor_plugin.h

@@ -0,0 +1,39 @@
+#ifndef BAKED_LIGHTMAP_EDITOR_PLUGIN_H
+#define BAKED_LIGHTMAP_EDITOR_PLUGIN_H
+
+#include "editor/editor_node.h"
+#include "editor/editor_plugin.h"
+#include "scene/3d/baked_lightmap.h"
+#include "scene/resources/material.h"
+
+class BakedLightmapEditorPlugin : public EditorPlugin {
+
+	GDCLASS(BakedLightmapEditorPlugin, EditorPlugin);
+
+	BakedLightmap *lightmap;
+
+	Button *bake;
+	EditorNode *editor;
+
+	static EditorProgress *tmp_progress;
+	static void bake_func_begin(int p_steps);
+	static bool bake_func_step(int p_step, const String &p_description);
+	static void bake_func_end();
+
+	void _bake();
+
+protected:
+	static void _bind_methods();
+
+public:
+	virtual String get_name() const { return "BakedLightmap"; }
+	bool has_main_screen() const { return false; }
+	virtual void edit(Object *p_object);
+	virtual bool handles(Object *p_object) const;
+	virtual void make_visible(bool p_visible);
+
+	BakedLightmapEditorPlugin(EditorNode *p_node);
+	~BakedLightmapEditorPlugin();
+};
+
+#endif // BAKED_LIGHTMAP_EDITOR_PLUGIN_H

+ 32 - 4
editor/progress_dialog.cpp

@@ -163,7 +163,7 @@ void ProgressDialog::_popup() {
 	popup_centered(ms);
 	popup_centered(ms);
 }
 }
 
 
-void ProgressDialog::add_task(const String &p_task, const String &p_label, int p_steps) {
+void ProgressDialog::add_task(const String &p_task, const String &p_label, int p_steps, bool p_can_cancel) {
 
 
 	ERR_FAIL_COND(tasks.has(p_task));
 	ERR_FAIL_COND(tasks.has(p_task));
 	Task t;
 	Task t;
@@ -180,17 +180,24 @@ void ProgressDialog::add_task(const String &p_task, const String &p_label, int p
 	main->add_child(t.vb);
 	main->add_child(t.vb);
 
 
 	tasks[p_task] = t;
 	tasks[p_task] = t;
+	if (p_can_cancel) {
+		cancel_hb->show();
+	} else {
+		cancel_hb->hide();
+	}
+	cancel_hb->raise();
+	cancelled = false;
 	_popup();
 	_popup();
 }
 }
 
 
-void ProgressDialog::task_step(const String &p_task, const String &p_state, int p_step, bool p_force_redraw) {
+bool ProgressDialog::task_step(const String &p_task, const String &p_state, int p_step, bool p_force_redraw) {
 
 
-	ERR_FAIL_COND(!tasks.has(p_task));
+	ERR_FAIL_COND_V(!tasks.has(p_task), cancelled);
 
 
 	if (!p_force_redraw) {
 	if (!p_force_redraw) {
 		uint64_t tus = OS::get_singleton()->get_ticks_usec();
 		uint64_t tus = OS::get_singleton()->get_ticks_usec();
 		if (tus - last_progress_tick < 50000) //50ms
 		if (tus - last_progress_tick < 50000) //50ms
-			return;
+			return cancelled;
 	}
 	}
 
 
 	Task &t = tasks[p_task];
 	Task &t = tasks[p_task];
@@ -201,7 +208,11 @@ void ProgressDialog::task_step(const String &p_task, const String &p_state, int
 
 
 	t.state->set_text(p_state);
 	t.state->set_text(p_state);
 	last_progress_tick = OS::get_singleton()->get_ticks_usec();
 	last_progress_tick = OS::get_singleton()->get_ticks_usec();
+	if (cancel_hb->is_visible()) {
+		OS::get_singleton()->force_process_input();
+	}
 	Main::iteration(); // this will not work on a lot of platforms, so it's only meant for the editor
 	Main::iteration(); // this will not work on a lot of platforms, so it's only meant for the editor
+	return cancelled;
 }
 }
 
 
 void ProgressDialog::end_task(const String &p_task) {
 void ProgressDialog::end_task(const String &p_task) {
@@ -218,6 +229,14 @@ void ProgressDialog::end_task(const String &p_task) {
 		_popup();
 		_popup();
 }
 }
 
 
+void ProgressDialog::_cancel_pressed() {
+	cancelled = true;
+}
+
+void ProgressDialog::_bind_methods() {
+	ClassDB::bind_method("_cancel_pressed", &ProgressDialog::_cancel_pressed);
+}
+
 ProgressDialog::ProgressDialog() {
 ProgressDialog::ProgressDialog() {
 
 
 	main = memnew(VBoxContainer);
 	main = memnew(VBoxContainer);
@@ -226,4 +245,13 @@ ProgressDialog::ProgressDialog() {
 	set_exclusive(true);
 	set_exclusive(true);
 	last_progress_tick = 0;
 	last_progress_tick = 0;
 	singleton = this;
 	singleton = this;
+	cancel_hb = memnew(HBoxContainer);
+	main->add_child(cancel_hb);
+	cancel_hb->hide();
+	cancel = memnew(Button);
+	cancel_hb->add_spacer();
+	cancel_hb->add_child(cancel);
+	cancel->set_text(TTR("Cancel"));
+	cancel_hb->add_spacer();
+	cancel->connect("pressed", this, "_cancel_pressed");
 }
 }

+ 9 - 2
editor/progress_dialog.h

@@ -31,6 +31,7 @@
 #define PROGRESS_DIALOG_H
 #define PROGRESS_DIALOG_H
 
 
 #include "scene/gui/box_container.h"
 #include "scene/gui/box_container.h"
+#include "scene/gui/button.h"
 #include "scene/gui/label.h"
 #include "scene/gui/label.h"
 #include "scene/gui/popup.h"
 #include "scene/gui/popup.h"
 #include "scene/gui/progress_bar.h"
 #include "scene/gui/progress_bar.h"
@@ -76,6 +77,8 @@ class ProgressDialog : public Popup {
 		ProgressBar *progress;
 		ProgressBar *progress;
 		Label *state;
 		Label *state;
 	};
 	};
+	HBoxContainer *cancel_hb;
+	Button *cancel;
 
 
 	Map<String, Task> tasks;
 	Map<String, Task> tasks;
 	VBoxContainer *main;
 	VBoxContainer *main;
@@ -84,13 +87,17 @@ class ProgressDialog : public Popup {
 	static ProgressDialog *singleton;
 	static ProgressDialog *singleton;
 	void _popup();
 	void _popup();
 
 
+	void _cancel_pressed();
+	bool cancelled;
+
 protected:
 protected:
 	void _notification(int p_what);
 	void _notification(int p_what);
+	static void _bind_methods();
 
 
 public:
 public:
 	static ProgressDialog *get_singleton() { return singleton; }
 	static ProgressDialog *get_singleton() { return singleton; }
-	void add_task(const String &p_task, const String &p_label, int p_steps);
-	void task_step(const String &p_task, const String &p_state, int p_step = -1, bool p_force_redraw = true);
+	void add_task(const String &p_task, const String &p_label, int p_steps, bool p_can_cancel = false);
+	bool task_step(const String &p_task, const String &p_state, int p_step = -1, bool p_force_redraw = true);
 	void end_task(const String &p_task);
 	void end_task(const String &p_task);
 
 
 	ProgressDialog();
 	ProgressDialog();

+ 121 - 0
editor/spatial_editor_gizmos.cpp

@@ -2753,7 +2753,122 @@ GIProbeGizmo::GIProbeGizmo(GIProbe *p_probe) {
 }
 }
 
 
 ////////
 ////////
+////////
+
+///
+
+String BakedIndirectLightGizmo::get_handle_name(int p_idx) const {
+
+	switch (p_idx) {
+		case 0: return "Extents X";
+		case 1: return "Extents Y";
+		case 2: return "Extents Z";
+	}
+
+	return "";
+}
+Variant BakedIndirectLightGizmo::get_handle_value(int p_idx) const {
+
+	return baker->get_extents();
+}
+void BakedIndirectLightGizmo::set_handle(int p_idx, Camera *p_camera, const Point2 &p_point) {
+
+	Transform gt = baker->get_global_transform();
+	//gt.orthonormalize();
+	Transform gi = gt.affine_inverse();
+
+	Vector3 extents = baker->get_extents();
+
+	Vector3 ray_from = p_camera->project_ray_origin(p_point);
+	Vector3 ray_dir = p_camera->project_ray_normal(p_point);
+
+	Vector3 sg[2] = { gi.xform(ray_from), gi.xform(ray_from + ray_dir * 16384) };
+
+	Vector3 axis;
+	axis[p_idx] = 1.0;
+
+	Vector3 ra, rb;
+	Geometry::get_closest_points_between_segments(Vector3(), axis * 16384, sg[0], sg[1], ra, rb);
+	float d = ra[p_idx];
+	if (d < 0.001)
+		d = 0.001;
+
+	extents[p_idx] = d;
+	baker->set_extents(extents);
+}
+
+void BakedIndirectLightGizmo::commit_handle(int p_idx, const Variant &p_restore, bool p_cancel) {
+
+	Vector3 restore = p_restore;
+
+	if (p_cancel) {
+		baker->set_extents(restore);
+		return;
+	}
+
+	UndoRedo *ur = SpatialEditor::get_singleton()->get_undo_redo();
+	ur->create_action(TTR("Change Probe Extents"));
+	ur->add_do_method(baker, "set_extents", baker->get_extents());
+	ur->add_undo_method(baker, "set_extents", restore);
+	ur->commit_action();
+}
 
 
+void BakedIndirectLightGizmo::redraw() {
+
+	Color gizmo_color = EDITOR_GET("editors/3d_gizmos/gizmo_colors/baked_indirect_light");
+	Ref<Material> material = create_material("baked_indirect_light_material", gizmo_color);
+	Ref<Material> icon = create_icon_material("baked_indirect_light_icon", SpatialEditor::get_singleton()->get_icon("GizmoGIProbe", "EditorIcons"));
+	Color gizmo_color_internal = gizmo_color;
+	gizmo_color_internal.a = 0.1;
+	Ref<Material> material_internal = create_material("baked_indirect_light_internal_material", gizmo_color_internal);
+
+	clear();
+
+	Vector<Vector3> lines;
+	Vector3 extents = baker->get_extents();
+
+	static const int subdivs[BakedLightmap::SUBDIV_MAX] = { 64, 128, 256, 512 };
+
+	AABB aabb = AABB(-extents, extents * 2);
+	int subdiv = subdivs[baker->get_bake_subdiv()];
+	float cell_size = aabb.get_longest_axis_size() / subdiv;
+
+	for (int i = 0; i < 12; i++) {
+		Vector3 a, b;
+		aabb.get_edge(i, a, b);
+		lines.push_back(a);
+		lines.push_back(b);
+	}
+
+	add_lines(lines, material);
+	add_collision_segments(lines);
+
+	Vector<Vector3> handles;
+
+	for (int i = 0; i < 3; i++) {
+
+		Vector3 ax;
+		ax[i] = aabb.position[i] + aabb.size[i];
+		handles.push_back(ax);
+	}
+
+	if (is_selected()) {
+
+		gizmo_color.a = 0.1;
+		Ref<Material> solid_material = create_material("baked_indirect_light_solid_material", gizmo_color);
+		add_solid_box(solid_material, aabb.get_size());
+	}
+
+	add_unscaled_billboard(icon, 0.05);
+	add_handles(handles);
+}
+BakedIndirectLightGizmo::BakedIndirectLightGizmo(BakedLightmap *p_baker) {
+
+	baker = p_baker;
+	set_spatial_node(p_baker);
+}
+
+////////
 void NavigationMeshSpatialGizmo::redraw() {
 void NavigationMeshSpatialGizmo::redraw() {
 
 
 	Ref<Material> edge_material = create_material("navigation_material", EDITOR_GET("editors/3d_gizmos/gizmo_colors/navigation_edge"));
 	Ref<Material> edge_material = create_material("navigation_material", EDITOR_GET("editors/3d_gizmos/gizmo_colors/navigation_edge"));
@@ -3409,6 +3524,11 @@ Ref<SpatialEditorGizmo> SpatialEditorGizmos::get_gizmo(Spatial *p_spatial) {
 		Ref<GIProbeGizmo> misg = memnew(GIProbeGizmo(Object::cast_to<GIProbe>(p_spatial)));
 		Ref<GIProbeGizmo> misg = memnew(GIProbeGizmo(Object::cast_to<GIProbe>(p_spatial)));
 		return misg;
 		return misg;
 	}
 	}
+	if (Object::cast_to<BakedLightmap>(p_spatial)) {
+
+		Ref<BakedIndirectLightGizmo> misg = memnew(BakedIndirectLightGizmo(Object::cast_to<BakedLightmap>(p_spatial)));
+		return misg;
+	}
 
 
 	if (Object::cast_to<VehicleWheel>(p_spatial)) {
 	if (Object::cast_to<VehicleWheel>(p_spatial)) {
 
 
@@ -3495,6 +3615,7 @@ SpatialEditorGizmos::SpatialEditorGizmos() {
 	EDITOR_DEF("editors/3d_gizmos/gizmo_colors/particles", Color(0.8, 0.7, 0.4));
 	EDITOR_DEF("editors/3d_gizmos/gizmo_colors/particles", Color(0.8, 0.7, 0.4));
 	EDITOR_DEF("editors/3d_gizmos/gizmo_colors/reflection_probe", Color(0.6, 1, 0.5));
 	EDITOR_DEF("editors/3d_gizmos/gizmo_colors/reflection_probe", Color(0.6, 1, 0.5));
 	EDITOR_DEF("editors/3d_gizmos/gizmo_colors/gi_probe", Color(0.5, 1, 0.6));
 	EDITOR_DEF("editors/3d_gizmos/gizmo_colors/gi_probe", Color(0.5, 1, 0.6));
+	EDITOR_DEF("editors/3d_gizmos/gizmo_colors/baked_indirect_light", Color(0.5, 0.6, 1));
 	EDITOR_DEF("editors/3d_gizmos/gizmo_colors/shape", Color(0.5, 0.7, 1));
 	EDITOR_DEF("editors/3d_gizmos/gizmo_colors/shape", Color(0.5, 0.7, 1));
 	EDITOR_DEF("editors/3d_gizmos/gizmo_colors/joint", Color(0.5, 0.8, 1));
 	EDITOR_DEF("editors/3d_gizmos/gizmo_colors/joint", Color(0.5, 0.8, 1));
 	EDITOR_DEF("editors/3d_gizmos/gizmo_colors/navigation_edge", Color(0.5, 1, 1));
 	EDITOR_DEF("editors/3d_gizmos/gizmo_colors/navigation_edge", Color(0.5, 1, 1));

+ 17 - 0
editor/spatial_editor_gizmos.h

@@ -32,6 +32,7 @@
 
 
 #include "editor/plugins/spatial_editor_plugin.h"
 #include "editor/plugins/spatial_editor_plugin.h"
 #include "scene/3d/audio_stream_player_3d.h"
 #include "scene/3d/audio_stream_player_3d.h"
+#include "scene/3d/baked_lightmap.h"
 #include "scene/3d/camera.h"
 #include "scene/3d/camera.h"
 #include "scene/3d/collision_polygon.h"
 #include "scene/3d/collision_polygon.h"
 #include "scene/3d/collision_shape.h"
 #include "scene/3d/collision_shape.h"
@@ -288,6 +289,22 @@ public:
 	GIProbeGizmo(GIProbe *p_probe = NULL);
 	GIProbeGizmo(GIProbe *p_probe = NULL);
 };
 };
 
 
+class BakedIndirectLightGizmo : public EditorSpatialGizmo {
+
+	GDCLASS(BakedIndirectLightGizmo, EditorSpatialGizmo);
+
+	BakedLightmap *baker;
+
+public:
+	virtual String get_handle_name(int p_idx) const;
+	virtual Variant get_handle_value(int p_idx) const;
+	virtual void set_handle(int p_idx, Camera *p_camera, const Point2 &p_point);
+	virtual void commit_handle(int p_idx, const Variant &p_restore, bool p_cancel = false);
+
+	void redraw();
+	BakedIndirectLightGizmo(BakedLightmap *p_baker = NULL);
+};
+
 class CollisionShapeSpatialGizmo : public EditorSpatialGizmo {
 class CollisionShapeSpatialGizmo : public EditorSpatialGizmo {
 
 
 	GDCLASS(CollisionShapeSpatialGizmo, EditorSpatialGizmo);
 	GDCLASS(CollisionShapeSpatialGizmo, EditorSpatialGizmo);

+ 4 - 3
modules/thekla_unwrap/register_types.cpp

@@ -42,7 +42,7 @@ bool thekla_mesh_lightmap_unwrap_callback(float p_texel_size, const float *p_ver
 		input_mesh.face_array[i].vertex_index[0] = p_indices[i * 3 + 0];
 		input_mesh.face_array[i].vertex_index[0] = p_indices[i * 3 + 0];
 		input_mesh.face_array[i].vertex_index[1] = p_indices[i * 3 + 1];
 		input_mesh.face_array[i].vertex_index[1] = p_indices[i * 3 + 1];
 		input_mesh.face_array[i].vertex_index[2] = p_indices[i * 3 + 2];
 		input_mesh.face_array[i].vertex_index[2] = p_indices[i * 3 + 2];
-		printf("face %i - %i, %i, %i - mat %i\n", i, input_mesh.face_array[i].vertex_index[0], input_mesh.face_array[i].vertex_index[1], input_mesh.face_array[i].vertex_index[2], p_face_materials[i]);
+		//printf("face %i - %i, %i, %i - mat %i\n", i, input_mesh.face_array[i].vertex_index[0], input_mesh.face_array[i].vertex_index[1], input_mesh.face_array[i].vertex_index[2], p_face_materials[i]);
 		input_mesh.face_array[i].material_index = p_face_materials[i];
 		input_mesh.face_array[i].material_index = p_face_materials[i];
 	}
 	}
 	input_mesh.vertex_array = new Thekla::Atlas_Input_Vertex[p_vertex_count];
 	input_mesh.vertex_array = new Thekla::Atlas_Input_Vertex[p_vertex_count];
@@ -54,8 +54,8 @@ bool thekla_mesh_lightmap_unwrap_callback(float p_texel_size, const float *p_ver
 		}
 		}
 		input_mesh.vertex_array[i].uv[0] = 0;
 		input_mesh.vertex_array[i].uv[0] = 0;
 		input_mesh.vertex_array[i].uv[1] = 0;
 		input_mesh.vertex_array[i].uv[1] = 0;
-		printf("vertex %i - %f, %f, %f\n", i, input_mesh.vertex_array[i].position[0], input_mesh.vertex_array[i].position[1], input_mesh.vertex_array[i].position[2]);
-		printf("normal %i - %f, %f, %f\n", i, input_mesh.vertex_array[i].normal[0], input_mesh.vertex_array[i].normal[1], input_mesh.vertex_array[i].normal[2]);
+		//printf("vertex %i - %f, %f, %f\n", i, input_mesh.vertex_array[i].position[0], input_mesh.vertex_array[i].position[1], input_mesh.vertex_array[i].position[2]);
+		//printf("normal %i - %f, %f, %f\n", i, input_mesh.vertex_array[i].normal[0], input_mesh.vertex_array[i].normal[1], input_mesh.vertex_array[i].normal[2]);
 	}
 	}
 	input_mesh.face_count = p_index_count / 3;
 	input_mesh.face_count = p_index_count / 3;
 	input_mesh.vertex_count = p_vertex_count;
 	input_mesh.vertex_count = p_vertex_count;
@@ -65,6 +65,7 @@ bool thekla_mesh_lightmap_unwrap_callback(float p_texel_size, const float *p_ver
 	Thekla::atlas_set_default_options(&options);
 	Thekla::atlas_set_default_options(&options);
 	options.packer_options.witness.packing_quality = 1;
 	options.packer_options.witness.packing_quality = 1;
 	options.packer_options.witness.texel_area = 1.0 / p_texel_size;
 	options.packer_options.witness.texel_area = 1.0 / p_texel_size;
+	options.packer_options.witness.conservative = true;
 
 
 	//generate
 	//generate
 	Thekla::Atlas_Error err;
 	Thekla::Atlas_Error err;

+ 2 - 0
platform/osx/os_osx.h

@@ -228,6 +228,8 @@ public:
 
 
 	virtual Error move_to_trash(const String &p_path);
 	virtual Error move_to_trash(const String &p_path);
 
 
+	void force_process_input();
+
 	OS_OSX();
 	OS_OSX();
 
 
 private:
 private:

+ 7 - 0
platform/osx/os_osx.mm

@@ -1971,6 +1971,13 @@ void OS_OSX::push_input(const Ref<InputEvent> &p_event) {
 	input->parse_input_event(ev);
 	input->parse_input_event(ev);
 }
 }
 
 
+void OS_OSX::force_process_input() {
+
+	process_events(); // get rid of pending events
+	joypad_osx->process_joypads();
+
+}
+
 void OS_OSX::run() {
 void OS_OSX::run() {
 
 
 	force_quit = false;
 	force_quit = false;

+ 7 - 0
platform/windows/detect.py

@@ -188,6 +188,9 @@ def configure(env):
         else:
         else:
             VC_PATH = ""
             VC_PATH = ""
 
 
+	if (env["openmp"]):
+	    env.Append(CPPFLAGS=['/openmp'])
+
         env.Append(CCFLAGS=["/I" + p for p in os.getenv("INCLUDE").split(";")])
         env.Append(CCFLAGS=["/I" + p for p in os.getenv("INCLUDE").split(";")])
         env.Append(LIBPATH=[p for p in os.getenv("LIB").split(";")])
         env.Append(LIBPATH=[p for p in os.getenv("LIB").split(";")])
 
 
@@ -264,6 +267,10 @@ def configure(env):
             env.Append(CCFLAGS=['-flto'])
             env.Append(CCFLAGS=['-flto'])
             env.Append(LINKFLAGS=['-flto=' + str(env.GetOption("num_jobs"))])
             env.Append(LINKFLAGS=['-flto=' + str(env.GetOption("num_jobs"))])
 
 
+	if (env["openmp"]):
+	    env.Append(CPPFLAGS=['-fopenmp'])
+	    env.Append(LIBS=['gomp'])
+
         ## Compile flags
         ## Compile flags
 
 
         env.Append(CCFLAGS=['-DWINDOWS_ENABLED', '-mwindows'])
         env.Append(CCFLAGS=['-DWINDOWS_ENABLED', '-mwindows'])

+ 4 - 0
platform/windows/os_windows.cpp

@@ -2156,6 +2156,10 @@ void OS_Windows::swap_buffers() {
 	gl_context->swap_buffers();
 	gl_context->swap_buffers();
 }
 }
 
 
+void OS_Windows::force_process_input() {
+	process_events(); // get rid of pending events
+}
+
 void OS_Windows::run() {
 void OS_Windows::run() {
 
 
 	if (!main_loop)
 	if (!main_loop)

+ 2 - 0
platform/windows/os_windows.h

@@ -287,6 +287,8 @@ public:
 	void disable_crash_handler();
 	void disable_crash_handler();
 	bool is_disable_crash_handler() const;
 	bool is_disable_crash_handler() const;
 
 
+	void force_process_input();
+
 	virtual Error move_to_trash(const String &p_path);
 	virtual Error move_to_trash(const String &p_path);
 
 
 	OS_Windows(HINSTANCE _hInstance);
 	OS_Windows(HINSTANCE _hInstance);

+ 6 - 0
platform/x11/detect.py

@@ -158,6 +158,7 @@ def configure(env):
     if not env['builtin_libwebp']:
     if not env['builtin_libwebp']:
         env.ParseConfig('pkg-config libwebp --cflags --libs')
         env.ParseConfig('pkg-config libwebp --cflags --libs')
 
 
+
     # freetype depends on libpng and zlib, so bundling one of them while keeping others
     # freetype depends on libpng and zlib, so bundling one of them while keeping others
     # as shared libraries leads to weird issues
     # as shared libraries leads to weird issues
     if env['builtin_freetype'] or env['builtin_libpng'] or env['builtin_zlib']:
     if env['builtin_freetype'] or env['builtin_libpng'] or env['builtin_zlib']:
@@ -263,5 +264,10 @@ def configure(env):
         env.Append(CPPFLAGS=['-m64'])
         env.Append(CPPFLAGS=['-m64'])
         env.Append(LINKFLAGS=['-m64', '-L/usr/lib/i686-linux-gnu'])
         env.Append(LINKFLAGS=['-m64', '-L/usr/lib/i686-linux-gnu'])
 
 
+
+    if (env["openmp"]):
+	env.Append(CPPFLAGS=['-fopenmp'])
+	env.Append(LIBS=['gomp'])
+
     if env['use_static_cpp']:
     if env['use_static_cpp']:
         env.Append(LINKFLAGS=['-static-libstdc++'])
         env.Append(LINKFLAGS=['-static-libstdc++'])

+ 7 - 0
platform/x11/os_x11.cpp

@@ -2264,6 +2264,13 @@ void OS_X11::set_icon(const Ref<Image> &p_icon) {
 	XFlush(x11_display);
 	XFlush(x11_display);
 }
 }
 
 
+void OS_X11::force_process_input() {
+	process_xevents(); // get rid of pending events
+#ifdef JOYDEV_ENABLED
+	joypad->process_joypads();
+#endif
+}
+
 void OS_X11::run() {
 void OS_X11::run() {
 
 
 	force_quit = false;
 	force_quit = false;

+ 1 - 0
platform/x11/os_x11.h

@@ -279,6 +279,7 @@ public:
 
 
 	virtual bool _check_internal_feature_support(const String &p_feature);
 	virtual bool _check_internal_feature_support(const String &p_feature);
 
 
+	virtual void force_process_input();
 	void run();
 	void run();
 
 
 	void disable_crash_handler();
 	void disable_crash_handler();

+ 717 - 0
scene/3d/baked_lightmap.cpp

@@ -0,0 +1,717 @@
+#include "baked_lightmap.h"
+#include "io/resource_saver.h"
+#include "os/dir_access.h"
+#include "os/os.h"
+#include "voxel_light_baker.h"
+
+void BakedLightmapData::set_bounds(const AABB &p_bounds) {
+
+	bounds = p_bounds;
+	VS::get_singleton()->lightmap_capture_set_bounds(baked_light, p_bounds);
+}
+
+AABB BakedLightmapData::get_bounds() const {
+
+	return bounds;
+}
+
+void BakedLightmapData::set_octree(const PoolVector<uint8_t> &p_octree) {
+
+	VS::get_singleton()->lightmap_capture_set_octree(baked_light, p_octree);
+}
+
+PoolVector<uint8_t> BakedLightmapData::get_octree() const {
+
+	return VS::get_singleton()->lightmap_capture_get_octree(baked_light);
+}
+
+void BakedLightmapData::set_cell_space_transform(const Transform &p_xform) {
+
+	cell_space_xform = p_xform;
+	VS::get_singleton()->lightmap_capture_set_octree_cell_transform(baked_light, p_xform);
+}
+
+Transform BakedLightmapData::get_cell_space_transform() const {
+	return cell_space_xform;
+}
+
+void BakedLightmapData::set_cell_subdiv(int p_cell_subdiv) {
+	cell_subdiv = p_cell_subdiv;
+	VS::get_singleton()->lightmap_capture_set_octree_cell_subdiv(baked_light, p_cell_subdiv);
+}
+
+int BakedLightmapData::get_cell_subdiv() const {
+	return cell_subdiv;
+}
+
+void BakedLightmapData::set_energy(float p_energy) {
+
+	energy = p_energy;
+	VS::get_singleton()->lightmap_capture_set_energy(baked_light, energy);
+}
+
+float BakedLightmapData::get_energy() const {
+
+	return energy;
+}
+
+void BakedLightmapData::add_user(const NodePath &p_path, const Ref<Texture> &p_lightmap) {
+
+	ERR_FAIL_COND(p_lightmap.is_null());
+	User user;
+	user.path = p_path;
+	user.lightmap = p_lightmap;
+	users.push_back(user);
+}
+
+int BakedLightmapData::get_user_count() const {
+
+	return users.size();
+}
+NodePath BakedLightmapData::get_user_path(int p_user) const {
+
+	ERR_FAIL_INDEX_V(p_user, users.size(), NodePath());
+	return users[p_user].path;
+}
+Ref<Texture> BakedLightmapData::get_user_lightmap(int p_user) const {
+
+	ERR_FAIL_INDEX_V(p_user, users.size(), Ref<Texture>());
+	return users[p_user].lightmap;
+}
+
+void BakedLightmapData::clear_users() {
+	users.clear();
+}
+
+void BakedLightmapData::_set_user_data(const Array &p_data) {
+
+	ERR_FAIL_COND(p_data.size() & 1);
+
+	for (int i = 0; i < p_data.size(); i += 2) {
+		add_user(p_data[i], p_data[i + 1]);
+	}
+}
+
+Array BakedLightmapData::_get_user_data() const {
+
+	Array ret;
+	for (int i = 0; i < users.size(); i++) {
+		ret.push_back(users[i].path);
+		ret.push_back(users[i].lightmap);
+	}
+	return ret;
+}
+
+RID BakedLightmapData::get_rid() const {
+	return baked_light;
+}
+void BakedLightmapData::_bind_methods() {
+
+	ClassDB::bind_method(D_METHOD("_set_user_data", "data"), &BakedLightmapData::_set_user_data);
+	ClassDB::bind_method(D_METHOD("_get_user_data"), &BakedLightmapData::_get_user_data);
+
+	ClassDB::bind_method(D_METHOD("set_bounds", "bounds"), &BakedLightmapData::set_bounds);
+	ClassDB::bind_method(D_METHOD("get_bounds"), &BakedLightmapData::get_bounds);
+
+	ClassDB::bind_method(D_METHOD("set_cell_space_transform", "xform"), &BakedLightmapData::set_cell_space_transform);
+	ClassDB::bind_method(D_METHOD("get_cell_space_transform"), &BakedLightmapData::get_cell_space_transform);
+
+	ClassDB::bind_method(D_METHOD("set_cell_subdiv", "cell_subdiv"), &BakedLightmapData::set_cell_subdiv);
+	ClassDB::bind_method(D_METHOD("get_cell_subdiv"), &BakedLightmapData::get_cell_subdiv);
+
+	ClassDB::bind_method(D_METHOD("set_octree", "octree"), &BakedLightmapData::set_octree);
+	ClassDB::bind_method(D_METHOD("get_octree"), &BakedLightmapData::get_octree);
+
+	ClassDB::bind_method(D_METHOD("set_energy", "energy"), &BakedLightmapData::set_energy);
+	ClassDB::bind_method(D_METHOD("get_energy"), &BakedLightmapData::get_energy);
+
+	ClassDB::bind_method(D_METHOD("add_user", "path", "lightmap"), &BakedLightmapData::add_user);
+	ClassDB::bind_method(D_METHOD("get_user_count"), &BakedLightmapData::get_user_count);
+	ClassDB::bind_method(D_METHOD("get_user_path", "user_idx"), &BakedLightmapData::get_user_path);
+	ClassDB::bind_method(D_METHOD("get_user_lightmap", "user_idx"), &BakedLightmapData::get_user_lightmap);
+	ClassDB::bind_method(D_METHOD("clear_users"), &BakedLightmapData::clear_users);
+
+	ADD_PROPERTY(PropertyInfo(Variant::AABB, "bounds", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NOEDITOR), "set_bounds", "get_bounds");
+	ADD_PROPERTY(PropertyInfo(Variant::POOL_BYTE_ARRAY, "octree", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NOEDITOR), "set_octree", "get_octree");
+	ADD_PROPERTY(PropertyInfo(Variant::TRANSFORM, "cell_space_transform", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NOEDITOR), "set_cell_space_transform", "get_cell_space_transform");
+	ADD_PROPERTY(PropertyInfo(Variant::INT, "cell_subdiv", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NOEDITOR), "set_cell_subdiv", "get_cell_subdiv");
+	ADD_PROPERTY(PropertyInfo(Variant::REAL, "energy", PROPERTY_HINT_RANGE, "0,16,0.01"), "set_energy", "get_energy");
+	ADD_PROPERTY(PropertyInfo(Variant::ARRAY, "user_data", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NOEDITOR), "_set_user_data", "_get_user_data");
+}
+
+BakedLightmapData::BakedLightmapData() {
+
+	baked_light = VS::get_singleton()->lightmap_capture_create();
+	energy = 1;
+	cell_subdiv = 1;
+}
+
+BakedLightmapData::~BakedLightmapData() {
+
+	VS::get_singleton()->free(baked_light);
+}
+
+///////////////////////////
+
+BakedLightmap::BakeBeginFunc BakedLightmap::bake_begin_function = NULL;
+BakedLightmap::BakeStepFunc BakedLightmap::bake_step_function = NULL;
+BakedLightmap::BakeEndFunc BakedLightmap::bake_end_function = NULL;
+
+void BakedLightmap::set_bake_subdiv(Subdiv p_subdiv) {
+	bake_subdiv = p_subdiv;
+}
+
+BakedLightmap::Subdiv BakedLightmap::get_bake_subdiv() const {
+	return bake_subdiv;
+}
+
+void BakedLightmap::set_capture_subdiv(Subdiv p_subdiv) {
+	capture_subdiv = p_subdiv;
+}
+
+BakedLightmap::Subdiv BakedLightmap::get_capture_subdiv() const {
+	return capture_subdiv;
+}
+
+void BakedLightmap::set_extents(const Vector3 &p_extents) {
+	extents = p_extents;
+}
+
+Vector3 BakedLightmap::get_extents() const {
+	return extents;
+}
+
+void BakedLightmap::_find_meshes_and_lights(Node *p_at_node, List<PlotMesh> &plot_meshes, List<PlotLight> &plot_lights) {
+
+	MeshInstance *mi = Object::cast_to<MeshInstance>(p_at_node);
+	if (mi && mi->get_flag(GeometryInstance::FLAG_USE_BAKED_LIGHT) && mi->is_visible_in_tree()) {
+		Ref<Mesh> mesh = mi->get_mesh();
+		if (mesh.is_valid()) {
+
+			bool all_have_uv2 = true;
+			for (int i = 0; i < mesh->get_surface_count(); i++) {
+				if (!(mesh->surface_get_format(i) & Mesh::ARRAY_FORMAT_TEX_UV2)) {
+					all_have_uv2 = false;
+					break;
+				}
+			}
+
+			if (all_have_uv2 && mesh->get_lightmap_size_hint() != Size2()) {
+				//READY TO BAKE! size hint could be computed if not found, actually..
+
+				AABB aabb = mesh->get_aabb();
+
+				Transform xf = get_global_transform().affine_inverse() * mi->get_global_transform();
+
+				if (AABB(-extents, extents * 2).intersects(xf.xform(aabb))) {
+					PlotMesh pm;
+					pm.local_xform = xf;
+					pm.mesh = mesh;
+					pm.path = get_path_to(mi);
+					for (int i = 0; i < mesh->get_surface_count(); i++) {
+						pm.instance_materials.push_back(mi->get_surface_material(i));
+					}
+					pm.override_material = mi->get_material_override();
+					plot_meshes.push_back(pm);
+				}
+			}
+		}
+	}
+
+	Light *light = Object::cast_to<Light>(p_at_node);
+
+	if (light && light->get_bake_mode() != Light::BAKE_DISABLED) {
+		PlotLight pl;
+		Transform xf = get_global_transform().affine_inverse() * light->get_global_transform();
+
+		pl.local_xform = xf;
+		pl.light = light;
+		plot_lights.push_back(pl);
+	}
+	for (int i = 0; i < p_at_node->get_child_count(); i++) {
+
+		Node *child = p_at_node->get_child(i);
+		if (!child->get_owner())
+			continue; //maybe a helper
+
+		_find_meshes_and_lights(child, plot_meshes, plot_lights);
+	}
+}
+
+void BakedLightmap::set_hdr(bool p_enable) {
+	hdr = p_enable;
+}
+
+bool BakedLightmap::is_hdr() const {
+	return hdr;
+}
+
+bool BakedLightmap::_bake_time(void *ud, float p_secs, float p_progress) {
+
+	uint64_t time = OS::get_singleton()->get_ticks_usec();
+	BakeTimeData *btd = (BakeTimeData *)ud;
+
+	if (time - btd->last_step > 1000000) {
+
+		int mins_left = p_secs / 60;
+		int secs_left = Math::fmod(p_secs, 60.0);
+		int percent = p_progress * 100;
+		bool abort = bake_step_function(btd->pass + percent, btd->text + " " + itos(percent) + "% (Time Left: " + itos(mins_left) + ":" + itos(secs_left) + "s)");
+		btd->last_step = time;
+		if (abort)
+			return true;
+	}
+
+	return false;
+}
+
+BakedLightmap::BakeError BakedLightmap::bake(Node *p_from_node, bool p_create_visual_debug) {
+
+	String save_path;
+
+	if (image_path.begins_with("res://")) {
+		save_path = image_path;
+	} else {
+		if (get_filename() != "") {
+			save_path = get_filename().get_base_dir();
+		} else if (get_owner() && get_owner()->get_filename() != "") {
+			save_path = get_owner()->get_filename().get_base_dir();
+		}
+
+		if (save_path == "") {
+			return BAKE_ERROR_NO_SAVE_PATH;
+		}
+		if (image_path != "") {
+			save_path.plus_file(image_path);
+		}
+	}
+	{
+		//check for valid save path
+		DirAccessRef d = DirAccess::open(save_path);
+		if (!d) {
+			ERR_PRINTS("Invalid Save Path: " + save_path);
+			return BAKE_ERROR_NO_SAVE_PATH;
+		}
+	}
+
+	Ref<BakedLightmapData> new_light_data;
+	new_light_data.instance();
+
+	static const int subdiv_value[SUBDIV_MAX] = { 8, 9, 10, 11, 12, 13 };
+
+	VoxelLightBaker baker;
+
+	baker.begin_bake(subdiv_value[bake_subdiv], AABB(-extents, extents * 2.0));
+
+	List<PlotMesh> mesh_list;
+	List<PlotLight> light_list;
+
+	_find_meshes_and_lights(p_from_node ? p_from_node : get_parent(), mesh_list, light_list);
+
+	if (bake_begin_function) {
+		bake_begin_function(mesh_list.size() + light_list.size() + 1 + mesh_list.size() * 100);
+	}
+
+	int step = 0;
+
+	int pmc = 0;
+
+	for (List<PlotMesh>::Element *E = mesh_list.front(); E; E = E->next()) {
+
+		if (bake_step_function) {
+			bake_step_function(step++, RTR("Plotting Meshes: ") + " (" + itos(pmc + 1) + "/" + itos(mesh_list.size()) + ")");
+		}
+
+		pmc++;
+		baker.plot_mesh(E->get().local_xform, E->get().mesh, E->get().instance_materials, E->get().override_material);
+	}
+
+	pmc = 0;
+	baker.begin_bake_light(VoxelLightBaker::BakeQuality(bake_quality), VoxelLightBaker::BakeMode(bake_mode), propagation, energy);
+
+	for (List<PlotLight>::Element *E = light_list.front(); E; E = E->next()) {
+
+		if (bake_step_function) {
+			bake_step_function(step++, RTR("Plotting Lights:") + " (" + itos(pmc + 1) + "/" + itos(light_list.size()) + ")");
+		}
+
+		pmc++;
+		PlotLight pl = E->get();
+		switch (pl.light->get_light_type()) {
+			case VS::LIGHT_DIRECTIONAL: {
+				baker.plot_light_directional(-pl.local_xform.basis.get_axis(2), pl.light->get_color(), pl.light->get_param(Light::PARAM_ENERGY), pl.light->get_param(Light::PARAM_INDIRECT_ENERGY), pl.light->get_bake_mode() == Light::BAKE_ALL);
+			} break;
+			case VS::LIGHT_OMNI: {
+				baker.plot_light_omni(pl.local_xform.origin, pl.light->get_color(), pl.light->get_param(Light::PARAM_ENERGY), pl.light->get_param(Light::PARAM_INDIRECT_ENERGY), pl.light->get_param(Light::PARAM_RANGE), pl.light->get_param(Light::PARAM_ATTENUATION), pl.light->get_bake_mode() == Light::BAKE_ALL);
+			} break;
+			case VS::LIGHT_SPOT: {
+				baker.plot_light_spot(pl.local_xform.origin, pl.local_xform.basis.get_axis(2), pl.light->get_color(), pl.light->get_param(Light::PARAM_ENERGY), pl.light->get_param(Light::PARAM_INDIRECT_ENERGY), pl.light->get_param(Light::PARAM_RANGE), pl.light->get_param(Light::PARAM_ATTENUATION), pl.light->get_param(Light::PARAM_SPOT_ANGLE), pl.light->get_param(Light::PARAM_SPOT_ATTENUATION), pl.light->get_bake_mode() == Light::BAKE_ALL);
+
+			} break;
+		}
+	}
+	/*if (bake_step_function) {
+		bake_step_function(pmc++, RTR("Finishing Plot"));
+	}*/
+
+	baker.end_bake();
+
+	Set<String> used_mesh_names;
+
+	pmc = 0;
+	for (List<PlotMesh>::Element *E = mesh_list.front(); E; E = E->next()) {
+
+		String mesh_name = E->get().mesh->get_name();
+		if (mesh_name == "" || mesh_name.find(":") != -1 || mesh_name.find("/") != -1) {
+			mesh_name = "LightMap";
+		}
+
+		if (used_mesh_names.has(mesh_name)) {
+			int idx = 2;
+			String base = mesh_name;
+			while (true) {
+				mesh_name = base + itos(idx);
+				if (!used_mesh_names.has(mesh_name))
+					break;
+				idx++;
+			}
+		}
+		used_mesh_names.insert(mesh_name);
+
+		pmc++;
+		VoxelLightBaker::LightMapData lm;
+
+		Error err;
+		if (bake_step_function) {
+			BakeTimeData btd;
+			btd.text = RTR("Lighting Meshes: ") + mesh_name + " (" + itos(pmc) + "/" + itos(mesh_list.size()) + ")";
+			btd.pass = step;
+			btd.last_step = 0;
+			err = baker.make_lightmap(E->get().local_xform, E->get().mesh, lm, _bake_time, &btd);
+			if (err != OK) {
+				bake_end_function();
+				if (err == ERR_SKIP)
+					return BAKE_ERROR_USER_ABORTED;
+				return BAKE_ERROR_CANT_CREATE_IMAGE;
+			}
+			step += 100;
+		} else {
+
+			err = baker.make_lightmap(E->get().local_xform, E->get().mesh, lm);
+		}
+
+		if (err == OK) {
+
+			Ref<Image> image;
+			image.instance();
+
+			uint32_t tex_flags = Texture::FLAGS_DEFAULT;
+			if (hdr) {
+
+				//just save a regular image
+				PoolVector<uint8_t> data;
+				int s = lm.light.size();
+				data.resize(lm.light.size() * 2);
+				{
+
+					PoolVector<uint8_t>::Write w = data.write();
+					PoolVector<float>::Read r = lm.light.read();
+					uint16_t *hfw = (uint16_t *)w.ptr();
+					for (int i = 0; i < s; i++) {
+						hfw[i] = Math::make_half_float(r[i]);
+					}
+				}
+
+				image->create(lm.width, lm.height, false, Image::FORMAT_RGBH, data);
+
+			} else {
+
+				//just save a regular image
+				PoolVector<uint8_t> data;
+				int s = lm.light.size();
+				data.resize(lm.light.size());
+				{
+
+					PoolVector<uint8_t>::Write w = data.write();
+					PoolVector<float>::Read r = lm.light.read();
+					for (int i = 0; i < s; i += 3) {
+						Color c(r[i + 0], r[i + 1], r[i + 2]);
+						c = c.to_srgb();
+						w[i + 0] = CLAMP(c.r * 255, 0, 255);
+						w[i + 1] = CLAMP(c.g * 255, 0, 255);
+						w[i + 2] = CLAMP(c.b * 255, 0, 255);
+					}
+				}
+
+				image->create(lm.width, lm.height, false, Image::FORMAT_RGB8, data);
+
+				//This texture is saved to SRGB for two reasons:
+				// 1) first is so it looks better when doing the LINEAR->SRGB conversion (more accurate)
+				// 2) So it can be used in the GLES2 backend, which does not support linkear workflow
+				tex_flags |= Texture::FLAG_CONVERT_TO_LINEAR;
+			}
+
+			Ref<ImageTexture> tex;
+			String image_path = save_path.plus_file(mesh_name + ".tex");
+			bool set_path = true;
+			if (ResourceCache::has(image_path)) {
+				tex = Ref<Resource>((Resource *)ResourceCache::get(image_path));
+				set_path = false;
+			}
+
+			if (!tex.is_valid()) {
+				tex.instance();
+			}
+
+			tex->create_from_image(image, tex_flags);
+
+			err = ResourceSaver::save(image_path, tex, ResourceSaver::FLAG_CHANGE_PATH);
+			if (err != OK) {
+				if (bake_end_function) {
+					bake_end_function();
+				}
+				ERR_FAIL_COND_V(err != OK, BAKE_ERROR_CANT_CREATE_IMAGE);
+			}
+
+			if (set_path) {
+				tex->set_path(image_path);
+			}
+			new_light_data->add_user(E->get().path, tex);
+		}
+	}
+
+	int csubdiv = subdiv_value[capture_subdiv];
+	AABB bounds = AABB(-extents, extents * 2);
+	new_light_data->set_cell_subdiv(csubdiv);
+	new_light_data->set_bounds(bounds);
+	new_light_data->set_octree(baker.create_capture_octree(csubdiv));
+	{
+
+		Transform to_bounds;
+		to_bounds.basis.scale(Vector3(bounds.get_longest_axis_size(), bounds.get_longest_axis_size(), bounds.get_longest_axis_size()));
+		to_bounds.origin = bounds.position;
+
+		Transform to_grid;
+		to_grid.basis.scale(Vector3(1 << (csubdiv - 1), 1 << (csubdiv - 1), 1 << (csubdiv - 1)));
+
+		Transform to_cell_space = to_grid * to_bounds.affine_inverse();
+		new_light_data->set_cell_space_transform(to_cell_space);
+	}
+
+	if (bake_end_function) {
+		bake_end_function();
+	}
+
+	//create the data for visual server
+
+	if (p_create_visual_debug) {
+		MultiMeshInstance *mmi = memnew(MultiMeshInstance);
+		mmi->set_multimesh(baker.create_debug_multimesh(VoxelLightBaker::DEBUG_LIGHT));
+		add_child(mmi);
+#ifdef TOOLS_ENABLED
+		if (get_tree()->get_edited_scene_root() == this) {
+			mmi->set_owner(this);
+		} else {
+			mmi->set_owner(get_owner());
+		}
+#else
+		mmi->set_owner(get_owner());
+#endif
+	}
+
+	set_light_data(new_light_data);
+
+	return BAKE_ERROR_OK;
+}
+
+void BakedLightmap::_notification(int p_what) {
+	if (p_what == NOTIFICATION_READY) {
+
+		if (light_data.is_valid()) {
+			_assign_lightmaps();
+		}
+		request_ready(); //will need ready again if re-enters tree
+	}
+
+	if (p_what == NOTIFICATION_EXIT_TREE) {
+
+		if (light_data.is_valid()) {
+			_clear_lightmaps();
+		}
+	}
+}
+
+void BakedLightmap::_assign_lightmaps() {
+
+	ERR_FAIL_COND(!light_data.is_valid());
+
+	for (int i = 0; i < light_data->get_user_count(); i++) {
+		Node *node = get_node(light_data->get_user_path(i));
+		VisualInstance *vi = Object::cast_to<VisualInstance>(node);
+		ERR_CONTINUE(!vi);
+		Ref<Texture> lightmap = light_data->get_user_lightmap(i);
+		ERR_CONTINUE(!lightmap.is_valid());
+		VS::get_singleton()->instance_set_use_lightmap(vi->get_instance(), get_instance(), lightmap->get_rid());
+	}
+}
+
+void BakedLightmap::_clear_lightmaps() {
+	ERR_FAIL_COND(!light_data.is_valid());
+	for (int i = 0; i < light_data->get_user_count(); i++) {
+		Node *node = get_node(light_data->get_user_path(i));
+		VisualInstance *vi = Object::cast_to<VisualInstance>(node);
+		ERR_CONTINUE(!vi);
+		VS::get_singleton()->instance_set_use_lightmap(vi->get_instance(), RID(), RID());
+	}
+}
+
+void BakedLightmap::set_light_data(const Ref<BakedLightmapData> &p_data) {
+
+	if (light_data.is_valid()) {
+		if (is_inside_tree()) {
+			_clear_lightmaps();
+		}
+		set_base(RID());
+	}
+	light_data = p_data;
+
+	if (light_data.is_valid()) {
+		set_base(light_data->get_rid());
+		if (is_inside_tree()) {
+			_assign_lightmaps();
+		}
+	}
+}
+
+Ref<BakedLightmapData> BakedLightmap::get_light_data() const {
+
+	return light_data;
+}
+
+void BakedLightmap::_debug_bake() {
+	bake(get_parent(), true);
+}
+
+void BakedLightmap::set_propagation(float p_propagation) {
+	propagation = p_propagation;
+}
+
+float BakedLightmap::get_propagation() const {
+
+	return propagation;
+}
+
+void BakedLightmap::set_energy(float p_energy) {
+	energy = p_energy;
+}
+
+float BakedLightmap::get_energy() const {
+
+	return energy;
+}
+
+void BakedLightmap::set_bake_quality(BakeQuality p_quality) {
+	bake_quality = p_quality;
+}
+
+BakedLightmap::BakeQuality BakedLightmap::get_bake_quality() const {
+	return bake_quality;
+}
+
+void BakedLightmap::set_bake_mode(BakeMode p_mode) {
+	bake_mode = p_mode;
+}
+
+BakedLightmap::BakeMode BakedLightmap::get_bake_mode() const {
+	return bake_mode;
+}
+
+void BakedLightmap::set_image_path(const String &p_path) {
+	image_path = p_path;
+}
+
+String BakedLightmap::get_image_path() const {
+	return image_path;
+}
+
+AABB BakedLightmap::get_aabb() const {
+	return AABB(-extents, extents * 2);
+}
+PoolVector<Face3> BakedLightmap::get_faces(uint32_t p_usage_flags) const {
+	return PoolVector<Face3>();
+}
+
+void BakedLightmap::_bind_methods() {
+
+	ClassDB::bind_method(D_METHOD("set_light_data", "data"), &BakedLightmap::set_light_data);
+	ClassDB::bind_method(D_METHOD("get_light_data"), &BakedLightmap::get_light_data);
+
+	ClassDB::bind_method(D_METHOD("set_bake_subdiv", "bake_subdiv"), &BakedLightmap::set_bake_subdiv);
+	ClassDB::bind_method(D_METHOD("get_bake_subdiv"), &BakedLightmap::get_bake_subdiv);
+
+	ClassDB::bind_method(D_METHOD("set_capture_subdiv", "capture_subdiv"), &BakedLightmap::set_capture_subdiv);
+	ClassDB::bind_method(D_METHOD("get_capture_subdiv"), &BakedLightmap::get_capture_subdiv);
+
+	ClassDB::bind_method(D_METHOD("set_bake_quality", "bake_quality"), &BakedLightmap::set_bake_quality);
+	ClassDB::bind_method(D_METHOD("get_bake_quality"), &BakedLightmap::get_bake_quality);
+
+	ClassDB::bind_method(D_METHOD("set_bake_mode", "bake_mode"), &BakedLightmap::set_bake_mode);
+	ClassDB::bind_method(D_METHOD("get_bake_mode"), &BakedLightmap::get_bake_mode);
+
+	ClassDB::bind_method(D_METHOD("set_extents", "extents"), &BakedLightmap::set_extents);
+	ClassDB::bind_method(D_METHOD("get_extents"), &BakedLightmap::get_extents);
+
+	ClassDB::bind_method(D_METHOD("set_propagation", "propagation"), &BakedLightmap::set_propagation);
+	ClassDB::bind_method(D_METHOD("get_propagation"), &BakedLightmap::get_propagation);
+
+	ClassDB::bind_method(D_METHOD("set_energy", "energy"), &BakedLightmap::set_energy);
+	ClassDB::bind_method(D_METHOD("get_energy"), &BakedLightmap::get_energy);
+
+	ClassDB::bind_method(D_METHOD("set_hdr", "hdr"), &BakedLightmap::set_hdr);
+	ClassDB::bind_method(D_METHOD("is_hdr"), &BakedLightmap::is_hdr);
+
+	ClassDB::bind_method(D_METHOD("set_image_path", "image_path"), &BakedLightmap::set_image_path);
+	ClassDB::bind_method(D_METHOD("get_image_path"), &BakedLightmap::get_image_path);
+
+	ClassDB::bind_method(D_METHOD("bake", "from_node", "create_visual_debug"), &BakedLightmap::bake, DEFVAL(Variant()), DEFVAL(false));
+	ClassDB::bind_method(D_METHOD("debug_bake"), &BakedLightmap::_debug_bake);
+	ClassDB::set_method_flags(get_class_static(), _scs_create("debug_bake"), METHOD_FLAGS_DEFAULT | METHOD_FLAG_EDITOR);
+
+	ADD_PROPERTY(PropertyInfo(Variant::INT, "bake_subdiv", PROPERTY_HINT_ENUM, "128,256,512,1024,2048,4096"), "set_bake_subdiv", "get_bake_subdiv");
+	ADD_PROPERTY(PropertyInfo(Variant::INT, "capture_subdiv", PROPERTY_HINT_ENUM, "128,256,512"), "set_capture_subdiv", "get_capture_subdiv");
+	ADD_PROPERTY(PropertyInfo(Variant::INT, "bake_quality", PROPERTY_HINT_ENUM, "Low,Medium,High"), "set_bake_quality", "get_bake_quality");
+	ADD_PROPERTY(PropertyInfo(Variant::INT, "bake_mode", PROPERTY_HINT_ENUM, "ConeTrace,RayTrace"), "set_bake_mode", "get_bake_mode");
+	ADD_PROPERTY(PropertyInfo(Variant::REAL, "propagation", PROPERTY_HINT_RANGE, "0,1,0.01"), "set_propagation", "get_propagation");
+	ADD_PROPERTY(PropertyInfo(Variant::REAL, "energy", PROPERTY_HINT_RANGE, "0,32,0.01"), "set_energy", "get_energy");
+	ADD_PROPERTY(PropertyInfo(Variant::BOOL, "hdr"), "set_hdr", "is_hdr");
+	ADD_PROPERTY(PropertyInfo(Variant::STRING, "image_path", PROPERTY_HINT_DIR), "set_image_path", "get_image_path");
+	ADD_PROPERTY(PropertyInfo(Variant::VECTOR3, "extents"), "set_extents", "get_extents");
+	ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "light_data", PROPERTY_HINT_RESOURCE_TYPE, "BakedIndirectLightData"), "set_light_data", "get_light_data");
+
+	BIND_ENUM_CONSTANT(SUBDIV_128);
+	BIND_ENUM_CONSTANT(SUBDIV_256);
+	BIND_ENUM_CONSTANT(SUBDIV_512);
+	BIND_ENUM_CONSTANT(SUBDIV_1024);
+	BIND_ENUM_CONSTANT(SUBDIV_2048);
+	BIND_ENUM_CONSTANT(SUBDIV_4096);
+	BIND_ENUM_CONSTANT(SUBDIV_MAX);
+
+	BIND_ENUM_CONSTANT(BAKE_QUALITY_LOW);
+	BIND_ENUM_CONSTANT(BAKE_QUALITY_MEDIUM);
+	BIND_ENUM_CONSTANT(BAKE_QUALITY_HIGH);
+	BIND_ENUM_CONSTANT(BAKE_MODE_CONE_TRACE);
+	BIND_ENUM_CONSTANT(BAKE_MODE_RAY_TRACE);
+}
+
+BakedLightmap::BakedLightmap() {
+
+	extents = Vector3(10, 10, 10);
+	bake_subdiv = SUBDIV_256;
+	capture_subdiv = SUBDIV_128;
+	bake_quality = BAKE_QUALITY_MEDIUM;
+	bake_mode = BAKE_MODE_CONE_TRACE;
+	energy = 1;
+	propagation = 1;
+	hdr = false;
+	image_path = ".";
+}

+ 189 - 0
scene/3d/baked_lightmap.h

@@ -0,0 +1,189 @@
+#ifndef BAKED_INDIRECT_LIGHT_H
+#define BAKED_INDIRECT_LIGHT_H
+
+#include "multimesh_instance.h"
+#include "scene/3d/light.h"
+#include "scene/3d/visual_instance.h"
+
+class BakedLightmapData : public Resource {
+	GDCLASS(BakedLightmapData, Resource);
+
+	RID baked_light;
+	AABB bounds;
+	float energy;
+	int cell_subdiv;
+	Transform cell_space_xform;
+
+	struct User {
+
+		NodePath path;
+		Ref<Texture> lightmap;
+	};
+
+	Vector<User> users;
+
+	void _set_user_data(const Array &p_data);
+	Array _get_user_data() const;
+
+protected:
+	static void _bind_methods();
+
+public:
+	void set_bounds(const AABB &p_bounds);
+	AABB get_bounds() const;
+
+	void set_octree(const PoolVector<uint8_t> &p_octree);
+	PoolVector<uint8_t> get_octree() const;
+
+	void set_cell_space_transform(const Transform &p_xform);
+	Transform get_cell_space_transform() const;
+
+	void set_cell_subdiv(int p_cell_subdiv);
+	int get_cell_subdiv() const;
+
+	void set_energy(float p_energy);
+	float get_energy() const;
+
+	void add_user(const NodePath &p_path, const Ref<Texture> &p_lightmap);
+	int get_user_count() const;
+	NodePath get_user_path(int p_user) const;
+	Ref<Texture> get_user_lightmap(int p_user) const;
+	void clear_users();
+
+	virtual RID get_rid() const;
+	BakedLightmapData();
+	~BakedLightmapData();
+};
+
+class BakedLightmap : public VisualInstance {
+	GDCLASS(BakedLightmap, VisualInstance);
+
+public:
+	enum Subdiv {
+		SUBDIV_128,
+		SUBDIV_256,
+		SUBDIV_512,
+		SUBDIV_1024,
+		SUBDIV_2048,
+		SUBDIV_4096,
+		SUBDIV_MAX
+
+	};
+
+	enum BakeQuality {
+		BAKE_QUALITY_LOW,
+		BAKE_QUALITY_MEDIUM,
+		BAKE_QUALITY_HIGH
+	};
+
+	enum BakeMode {
+		BAKE_MODE_CONE_TRACE,
+		BAKE_MODE_RAY_TRACE,
+	};
+
+	enum BakeError {
+		BAKE_ERROR_OK,
+		BAKE_ERROR_NO_SAVE_PATH,
+		BAKE_ERROR_NO_MESHES,
+		BAKE_ERROR_CANT_CREATE_IMAGE,
+		BAKE_ERROR_USER_ABORTED
+
+	};
+
+	typedef void (*BakeBeginFunc)(int);
+	typedef bool (*BakeStepFunc)(int, const String &);
+	typedef void (*BakeEndFunc)();
+
+private:
+	Subdiv bake_subdiv;
+	Subdiv capture_subdiv;
+	Vector3 extents;
+	float propagation;
+	float energy;
+	BakeQuality bake_quality;
+	BakeMode bake_mode;
+	bool hdr;
+	String image_path;
+
+	Ref<BakedLightmapData> light_data;
+
+	struct PlotMesh {
+		Ref<Material> override_material;
+		Vector<Ref<Material> > instance_materials;
+		Ref<Mesh> mesh;
+		Transform local_xform;
+		NodePath path;
+	};
+
+	struct PlotLight {
+		Light *light;
+		Transform local_xform;
+	};
+
+	void _find_meshes_and_lights(Node *p_at_node, List<PlotMesh> &plot_meshes, List<PlotLight> &plot_lights);
+
+	void _debug_bake();
+
+	void _assign_lightmaps();
+	void _clear_lightmaps();
+
+	static bool _bake_time(void *ud, float p_secs, float p_progress);
+
+	struct BakeTimeData {
+		String text;
+		int pass;
+		uint64_t last_step;
+	};
+
+protected:
+	static void _bind_methods();
+	void _notification(int p_what);
+
+public:
+	static BakeBeginFunc bake_begin_function;
+	static BakeStepFunc bake_step_function;
+	static BakeEndFunc bake_end_function;
+
+	void set_light_data(const Ref<BakedLightmapData> &p_data);
+	Ref<BakedLightmapData> get_light_data() const;
+
+	void set_bake_subdiv(Subdiv p_subdiv);
+	Subdiv get_bake_subdiv() const;
+
+	void set_capture_subdiv(Subdiv p_subdiv);
+	Subdiv get_capture_subdiv() const;
+
+	void set_extents(const Vector3 &p_extents);
+	Vector3 get_extents() const;
+
+	void set_propagation(float p_propagation);
+	float get_propagation() const;
+
+	void set_energy(float p_energy);
+	float get_energy() const;
+
+	void set_bake_quality(BakeQuality p_quality);
+	BakeQuality get_bake_quality() const;
+
+	void set_bake_mode(BakeMode p_mode);
+	BakeMode get_bake_mode() const;
+
+	void set_hdr(bool p_enable);
+	bool is_hdr() const;
+
+	void set_image_path(const String &p_path);
+	String get_image_path() const;
+
+	AABB get_aabb() const;
+	PoolVector<Face3> get_faces(uint32_t p_usage_flags) const;
+
+	BakeError bake(Node *p_from_node, bool p_create_visual_debug = false);
+	BakedLightmap();
+};
+
+VARIANT_ENUM_CAST(BakedLightmap::Subdiv);
+VARIANT_ENUM_CAST(BakedLightmap::BakeQuality);
+VARIANT_ENUM_CAST(BakedLightmap::BakeMode);
+VARIANT_ENUM_CAST(BakedLightmap::BakeError);
+
+#endif // BAKED_INDIRECT_LIGHT_H

File diff suppressed because it is too large
+ 31 - 995
scene/3d/gi_probe.cpp


+ 7 - 73
scene/3d/gi_probe.h

@@ -100,67 +100,6 @@ public:
 	typedef void (*BakeEndFunc)();
 	typedef void (*BakeEndFunc)();
 
 
 private:
 private:
-	//stuff used for bake
-	struct Baker {
-
-		enum {
-			CHILD_EMPTY = 0xFFFFFFFF
-		};
-		struct Cell {
-
-			uint32_t childs[8];
-			float albedo[3]; //albedo in RGB24
-			float emission[3]; //accumulated light in 16:16 fixed point (needs to be integer for moving lights fast)
-			float normal[3];
-			uint32_t used_sides;
-			float alpha; //used for upsampling
-			int level;
-
-			Cell() {
-				for (int i = 0; i < 8; i++) {
-					childs[i] = CHILD_EMPTY;
-				}
-
-				for (int i = 0; i < 3; i++) {
-					emission[i] = 0;
-					albedo[i] = 0;
-					normal[i] = 0;
-				}
-				alpha = 0;
-				used_sides = 0;
-				level = 0;
-			}
-		};
-
-		Vector<Cell> bake_cells;
-		int cell_subdiv;
-
-		struct MaterialCache {
-			//128x128 textures
-			Vector<Color> albedo;
-			Vector<Color> emission;
-		};
-
-		Vector<Color> _get_bake_texture(Ref<Image> p_image, const Color &p_color);
-		Map<Ref<Material>, MaterialCache> material_cache;
-		MaterialCache _get_material_cache(Ref<Material> p_material);
-		int leaf_voxel_count;
-
-		AABB po2_bounds;
-		int axis_cell_size[3];
-
-		struct PlotMesh {
-			Ref<Material> override_material;
-			Vector<Ref<Material> > instance_materials;
-			Ref<Mesh> mesh;
-			Transform local_xform;
-		};
-
-		Transform to_cell_space;
-
-		List<PlotMesh> mesh_list;
-	};
-
 	Ref<GIProbeData> probe_data;
 	Ref<GIProbeData> probe_data;
 
 
 	RID gi_probe;
 	RID gi_probe;
@@ -175,19 +114,14 @@ private:
 	bool interior;
 	bool interior;
 	bool compress;
 	bool compress;
 
 
-	int color_scan_cell_width;
-	int bake_texture_size;
-
-	Vector<Color> _get_bake_texture(Ref<Image> p_image, const Color &p_color_mul, const Color &p_color_add);
-	Baker::MaterialCache _get_material_cache(Ref<Material> p_material, Baker *p_baker);
-	void _plot_face(int p_idx, int p_level, int p_x, int p_y, int p_z, const Vector3 *p_vtx, const Vector2 *p_uv, const Baker::MaterialCache &p_material, const AABB &p_aabb, Baker *p_baker);
-	void _plot_mesh(const Transform &p_xform, Ref<Mesh> &p_mesh, Baker *p_baker, const Vector<Ref<Material> > &p_materials, const Ref<Material> &p_override_material);
-	void _find_meshes(Node *p_at_node, Baker *p_baker);
-	void _fixup_plot(int p_idx, int p_level, int p_x, int p_y, int p_z, Baker *p_baker);
-
-	void _debug_mesh(int p_idx, int p_level, const AABB &p_aabb, Ref<MultiMesh> &p_multimesh, int &idx, Baker *p_baker);
-	void _create_debug_mesh(Baker *p_baker);
+	struct PlotMesh {
+		Ref<Material> override_material;
+		Vector<Ref<Material> > instance_materials;
+		Ref<Mesh> mesh;
+		Transform local_xform;
+	};
 
 
+	void _find_meshes(Node *p_at_node, List<PlotMesh> &plot_meshes);
 	void _debug_bake();
 	void _debug_bake();
 
 
 protected:
 protected:

+ 17 - 0
scene/3d/light.cpp

@@ -142,6 +142,14 @@ PoolVector<Face3> Light::get_faces(uint32_t p_usage_flags) const {
 	return PoolVector<Face3>();
 	return PoolVector<Face3>();
 }
 }
 
 
+void Light::set_bake_mode(BakeMode p_mode) {
+	bake_mode = p_mode;
+}
+
+Light::BakeMode Light::get_bake_mode() const {
+	return bake_mode;
+}
+
 void Light::_update_visibility() {
 void Light::_update_visibility() {
 
 
 	if (!is_inside_tree())
 	if (!is_inside_tree())
@@ -219,12 +227,16 @@ void Light::_bind_methods() {
 	ClassDB::bind_method(D_METHOD("set_shadow_color", "shadow_color"), &Light::set_shadow_color);
 	ClassDB::bind_method(D_METHOD("set_shadow_color", "shadow_color"), &Light::set_shadow_color);
 	ClassDB::bind_method(D_METHOD("get_shadow_color"), &Light::get_shadow_color);
 	ClassDB::bind_method(D_METHOD("get_shadow_color"), &Light::get_shadow_color);
 
 
+	ClassDB::bind_method(D_METHOD("set_bake_mode", "bake_mode"), &Light::set_bake_mode);
+	ClassDB::bind_method(D_METHOD("get_bake_mode"), &Light::get_bake_mode);
+
 	ADD_GROUP("Light", "light_");
 	ADD_GROUP("Light", "light_");
 	ADD_PROPERTY(PropertyInfo(Variant::COLOR, "light_color", PROPERTY_HINT_COLOR_NO_ALPHA), "set_color", "get_color");
 	ADD_PROPERTY(PropertyInfo(Variant::COLOR, "light_color", PROPERTY_HINT_COLOR_NO_ALPHA), "set_color", "get_color");
 	ADD_PROPERTYI(PropertyInfo(Variant::REAL, "light_energy", PROPERTY_HINT_RANGE, "0,16,0.01"), "set_param", "get_param", PARAM_ENERGY);
 	ADD_PROPERTYI(PropertyInfo(Variant::REAL, "light_energy", PROPERTY_HINT_RANGE, "0,16,0.01"), "set_param", "get_param", PARAM_ENERGY);
 	ADD_PROPERTYI(PropertyInfo(Variant::REAL, "light_indirect_energy", PROPERTY_HINT_RANGE, "0,16,0.01"), "set_param", "get_param", PARAM_INDIRECT_ENERGY);
 	ADD_PROPERTYI(PropertyInfo(Variant::REAL, "light_indirect_energy", PROPERTY_HINT_RANGE, "0,16,0.01"), "set_param", "get_param", PARAM_INDIRECT_ENERGY);
 	ADD_PROPERTY(PropertyInfo(Variant::BOOL, "light_negative"), "set_negative", "is_negative");
 	ADD_PROPERTY(PropertyInfo(Variant::BOOL, "light_negative"), "set_negative", "is_negative");
 	ADD_PROPERTYI(PropertyInfo(Variant::REAL, "light_specular", PROPERTY_HINT_RANGE, "0,1,0.01"), "set_param", "get_param", PARAM_SPECULAR);
 	ADD_PROPERTYI(PropertyInfo(Variant::REAL, "light_specular", PROPERTY_HINT_RANGE, "0,1,0.01"), "set_param", "get_param", PARAM_SPECULAR);
+	ADD_PROPERTY(PropertyInfo(Variant::INT, "light_bake_mode", PROPERTY_HINT_ENUM, "Disable,Indirect,All"), "set_bake_mode", "get_bake_mode");
 	ADD_PROPERTY(PropertyInfo(Variant::INT, "light_cull_mask", PROPERTY_HINT_LAYERS_3D_RENDER), "set_cull_mask", "get_cull_mask");
 	ADD_PROPERTY(PropertyInfo(Variant::INT, "light_cull_mask", PROPERTY_HINT_LAYERS_3D_RENDER), "set_cull_mask", "get_cull_mask");
 	ADD_GROUP("Shadow", "shadow_");
 	ADD_GROUP("Shadow", "shadow_");
 	ADD_PROPERTY(PropertyInfo(Variant::BOOL, "shadow_enabled"), "set_shadow", "has_shadow");
 	ADD_PROPERTY(PropertyInfo(Variant::BOOL, "shadow_enabled"), "set_shadow", "has_shadow");
@@ -252,6 +264,10 @@ void Light::_bind_methods() {
 	BIND_ENUM_CONSTANT(PARAM_SHADOW_BIAS);
 	BIND_ENUM_CONSTANT(PARAM_SHADOW_BIAS);
 	BIND_ENUM_CONSTANT(PARAM_SHADOW_BIAS_SPLIT_SCALE);
 	BIND_ENUM_CONSTANT(PARAM_SHADOW_BIAS_SPLIT_SCALE);
 	BIND_ENUM_CONSTANT(PARAM_MAX);
 	BIND_ENUM_CONSTANT(PARAM_MAX);
+
+	BIND_ENUM_CONSTANT(BAKE_DISABLED);
+	BIND_ENUM_CONSTANT(BAKE_INDIRECT);
+	BIND_ENUM_CONSTANT(BAKE_ALL);
 }
 }
 
 
 Light::Light(VisualServer::LightType p_type) {
 Light::Light(VisualServer::LightType p_type) {
@@ -267,6 +283,7 @@ Light::Light(VisualServer::LightType p_type) {
 	VS::get_singleton()->instance_set_base(get_instance(), light);
 	VS::get_singleton()->instance_set_base(get_instance(), light);
 
 
 	reverse_cull = false;
 	reverse_cull = false;
+	bake_mode = BAKE_INDIRECT;
 
 
 	editor_only = false;
 	editor_only = false;
 	set_color(Color(1, 1, 1, 1));
 	set_color(Color(1, 1, 1, 1));

+ 11 - 0
scene/3d/light.h

@@ -63,6 +63,12 @@ public:
 		PARAM_MAX = VS::LIGHT_PARAM_MAX
 		PARAM_MAX = VS::LIGHT_PARAM_MAX
 	};
 	};
 
 
+	enum BakeMode {
+		BAKE_DISABLED,
+		BAKE_INDIRECT,
+		BAKE_ALL
+	};
+
 private:
 private:
 	Color color;
 	Color color;
 	float param[PARAM_MAX];
 	float param[PARAM_MAX];
@@ -74,6 +80,7 @@ private:
 	VS::LightType type;
 	VS::LightType type;
 	bool editor_only;
 	bool editor_only;
 	void _update_visibility();
 	void _update_visibility();
+	BakeMode bake_mode;
 
 
 	// bind helpers
 	// bind helpers
 
 
@@ -114,6 +121,9 @@ public:
 	void set_shadow_reverse_cull_face(bool p_enable);
 	void set_shadow_reverse_cull_face(bool p_enable);
 	bool get_shadow_reverse_cull_face() const;
 	bool get_shadow_reverse_cull_face() const;
 
 
+	void set_bake_mode(BakeMode p_mode);
+	BakeMode get_bake_mode() const;
+
 	virtual AABB get_aabb() const;
 	virtual AABB get_aabb() const;
 	virtual PoolVector<Face3> get_faces(uint32_t p_usage_flags) const;
 	virtual PoolVector<Face3> get_faces(uint32_t p_usage_flags) const;
 
 
@@ -122,6 +132,7 @@ public:
 };
 };
 
 
 VARIANT_ENUM_CAST(Light::Param);
 VARIANT_ENUM_CAST(Light::Param);
+VARIANT_ENUM_CAST(Light::BakeMode);
 
 
 class DirectionalLight : public Light {
 class DirectionalLight : public Light {
 
 

+ 2373 - 0
scene/3d/voxel_light_baker.cpp

@@ -0,0 +1,2373 @@
+#include "voxel_light_baker.h"
+#include "os/os.h"
+#define FINDMINMAX(x0, x1, x2, min, max) \
+	min = max = x0;                      \
+	if (x1 < min) min = x1;              \
+	if (x1 > max) max = x1;              \
+	if (x2 < min) min = x2;              \
+	if (x2 > max) max = x2;
+
+static bool planeBoxOverlap(Vector3 normal, float d, Vector3 maxbox) {
+	int q;
+	Vector3 vmin, vmax;
+	for (q = 0; q <= 2; q++) {
+		if (normal[q] > 0.0f) {
+			vmin[q] = -maxbox[q];
+			vmax[q] = maxbox[q];
+		} else {
+			vmin[q] = maxbox[q];
+			vmax[q] = -maxbox[q];
+		}
+	}
+	if (normal.dot(vmin) + d > 0.0f) return false;
+	if (normal.dot(vmax) + d >= 0.0f) return true;
+
+	return false;
+}
+
+/*======================== X-tests ========================*/
+#define AXISTEST_X01(a, b, fa, fb)                 \
+	p0 = a * v0.y - b * v0.z;                      \
+	p2 = a * v2.y - b * v2.z;                      \
+	if (p0 < p2) {                                 \
+		min = p0;                                  \
+		max = p2;                                  \
+	} else {                                       \
+		min = p2;                                  \
+		max = p0;                                  \
+	}                                              \
+	rad = fa * boxhalfsize.y + fb * boxhalfsize.z; \
+	if (min > rad || max < -rad) return false;
+
+#define AXISTEST_X2(a, b, fa, fb)                  \
+	p0 = a * v0.y - b * v0.z;                      \
+	p1 = a * v1.y - b * v1.z;                      \
+	if (p0 < p1) {                                 \
+		min = p0;                                  \
+		max = p1;                                  \
+	} else {                                       \
+		min = p1;                                  \
+		max = p0;                                  \
+	}                                              \
+	rad = fa * boxhalfsize.y + fb * boxhalfsize.z; \
+	if (min > rad || max < -rad) return false;
+
+/*======================== Y-tests ========================*/
+#define AXISTEST_Y02(a, b, fa, fb)                 \
+	p0 = -a * v0.x + b * v0.z;                     \
+	p2 = -a * v2.x + b * v2.z;                     \
+	if (p0 < p2) {                                 \
+		min = p0;                                  \
+		max = p2;                                  \
+	} else {                                       \
+		min = p2;                                  \
+		max = p0;                                  \
+	}                                              \
+	rad = fa * boxhalfsize.x + fb * boxhalfsize.z; \
+	if (min > rad || max < -rad) return false;
+
+#define AXISTEST_Y1(a, b, fa, fb)                  \
+	p0 = -a * v0.x + b * v0.z;                     \
+	p1 = -a * v1.x + b * v1.z;                     \
+	if (p0 < p1) {                                 \
+		min = p0;                                  \
+		max = p1;                                  \
+	} else {                                       \
+		min = p1;                                  \
+		max = p0;                                  \
+	}                                              \
+	rad = fa * boxhalfsize.x + fb * boxhalfsize.z; \
+	if (min > rad || max < -rad) return false;
+
+	/*======================== Z-tests ========================*/
+
+#define AXISTEST_Z12(a, b, fa, fb)                 \
+	p1 = a * v1.x - b * v1.y;                      \
+	p2 = a * v2.x - b * v2.y;                      \
+	if (p2 < p1) {                                 \
+		min = p2;                                  \
+		max = p1;                                  \
+	} else {                                       \
+		min = p1;                                  \
+		max = p2;                                  \
+	}                                              \
+	rad = fa * boxhalfsize.x + fb * boxhalfsize.y; \
+	if (min > rad || max < -rad) return false;
+
+#define AXISTEST_Z0(a, b, fa, fb)                  \
+	p0 = a * v0.x - b * v0.y;                      \
+	p1 = a * v1.x - b * v1.y;                      \
+	if (p0 < p1) {                                 \
+		min = p0;                                  \
+		max = p1;                                  \
+	} else {                                       \
+		min = p1;                                  \
+		max = p0;                                  \
+	}                                              \
+	rad = fa * boxhalfsize.x + fb * boxhalfsize.y; \
+	if (min > rad || max < -rad) return false;
+
+static bool fast_tri_box_overlap(const Vector3 &boxcenter, const Vector3 boxhalfsize, const Vector3 *triverts) {
+
+	/*    use separating axis theorem to test overlap between triangle and box */
+	/*    need to test for overlap in these directions: */
+	/*    1) the {x,y,z}-directions (actually, since we use the AABB of the triangle */
+	/*       we do not even need to test these) */
+	/*    2) normal of the triangle */
+	/*    3) crossproduct(edge from tri, {x,y,z}-directin) */
+	/*       this gives 3x3=9 more tests */
+	Vector3 v0, v1, v2;
+	float min, max, d, p0, p1, p2, rad, fex, fey, fez;
+	Vector3 normal, e0, e1, e2;
+
+	/* This is the fastest branch on Sun */
+	/* move everything so that the boxcenter is in (0,0,0) */
+
+	v0 = triverts[0] - boxcenter;
+	v1 = triverts[1] - boxcenter;
+	v2 = triverts[2] - boxcenter;
+
+	/* compute triangle edges */
+	e0 = v1 - v0; /* tri edge 0 */
+	e1 = v2 - v1; /* tri edge 1 */
+	e2 = v0 - v2; /* tri edge 2 */
+
+	/* Bullet 3:  */
+	/*  test the 9 tests first (this was faster) */
+	fex = Math::abs(e0.x);
+	fey = Math::abs(e0.y);
+	fez = Math::abs(e0.z);
+	AXISTEST_X01(e0.z, e0.y, fez, fey);
+	AXISTEST_Y02(e0.z, e0.x, fez, fex);
+	AXISTEST_Z12(e0.y, e0.x, fey, fex);
+
+	fex = Math::abs(e1.x);
+	fey = Math::abs(e1.y);
+	fez = Math::abs(e1.z);
+	AXISTEST_X01(e1.z, e1.y, fez, fey);
+	AXISTEST_Y02(e1.z, e1.x, fez, fex);
+	AXISTEST_Z0(e1.y, e1.x, fey, fex);
+
+	fex = Math::abs(e2.x);
+	fey = Math::abs(e2.y);
+	fez = Math::abs(e2.z);
+	AXISTEST_X2(e2.z, e2.y, fez, fey);
+	AXISTEST_Y1(e2.z, e2.x, fez, fex);
+	AXISTEST_Z12(e2.y, e2.x, fey, fex);
+
+	/* Bullet 1: */
+	/*  first test overlap in the {x,y,z}-directions */
+	/*  find min, max of the triangle each direction, and test for overlap in */
+	/*  that direction -- this is equivalent to testing a minimal AABB around */
+	/*  the triangle against the AABB */
+
+	/* test in X-direction */
+	FINDMINMAX(v0.x, v1.x, v2.x, min, max);
+	if (min > boxhalfsize.x || max < -boxhalfsize.x) return false;
+
+	/* test in Y-direction */
+	FINDMINMAX(v0.y, v1.y, v2.y, min, max);
+	if (min > boxhalfsize.y || max < -boxhalfsize.y) return false;
+
+	/* test in Z-direction */
+	FINDMINMAX(v0.z, v1.z, v2.z, min, max);
+	if (min > boxhalfsize.z || max < -boxhalfsize.z) return false;
+
+	/* Bullet 2: */
+	/*  test if the box intersects the plane of the triangle */
+	/*  compute plane equation of triangle: normal*x+d=0 */
+	normal = e0.cross(e1);
+	d = -normal.dot(v0); /* plane eq: normal.x+d=0 */
+	if (!planeBoxOverlap(normal, d, boxhalfsize)) return false;
+
+	return true; /* box and triangle overlaps */
+}
+
+static _FORCE_INLINE_ Vector2 get_uv(const Vector3 &p_pos, const Vector3 *p_vtx, const Vector2 *p_uv) {
+
+	if (p_pos.distance_squared_to(p_vtx[0]) < CMP_EPSILON2)
+		return p_uv[0];
+	if (p_pos.distance_squared_to(p_vtx[1]) < CMP_EPSILON2)
+		return p_uv[1];
+	if (p_pos.distance_squared_to(p_vtx[2]) < CMP_EPSILON2)
+		return p_uv[2];
+
+	Vector3 v0 = p_vtx[1] - p_vtx[0];
+	Vector3 v1 = p_vtx[2] - p_vtx[0];
+	Vector3 v2 = p_pos - p_vtx[0];
+
+	float d00 = v0.dot(v0);
+	float d01 = v0.dot(v1);
+	float d11 = v1.dot(v1);
+	float d20 = v2.dot(v0);
+	float d21 = v2.dot(v1);
+	float denom = (d00 * d11 - d01 * d01);
+	if (denom == 0)
+		return p_uv[0];
+	float v = (d11 * d20 - d01 * d21) / denom;
+	float w = (d00 * d21 - d01 * d20) / denom;
+	float u = 1.0f - v - w;
+
+	return p_uv[0] * u + p_uv[1] * v + p_uv[2] * w;
+}
+
+void VoxelLightBaker::_plot_face(int p_idx, int p_level, int p_x, int p_y, int p_z, const Vector3 *p_vtx, const Vector2 *p_uv, const MaterialCache &p_material, const AABB &p_aabb) {
+
+	if (p_level == cell_subdiv - 1) {
+		//plot the face by guessing it's albedo and emission value
+
+		//find best axis to map to, for scanning values
+		int closest_axis = 0;
+		float closest_dot = 0;
+
+		Plane plane = Plane(p_vtx[0], p_vtx[1], p_vtx[2]);
+		Vector3 normal = plane.normal;
+
+		for (int i = 0; i < 3; i++) {
+
+			Vector3 axis;
+			axis[i] = 1.0;
+			float dot = ABS(normal.dot(axis));
+			if (i == 0 || dot > closest_dot) {
+				closest_axis = i;
+				closest_dot = dot;
+			}
+		}
+
+		Vector3 axis;
+		axis[closest_axis] = 1.0;
+		Vector3 t1;
+		t1[(closest_axis + 1) % 3] = 1.0;
+		Vector3 t2;
+		t2[(closest_axis + 2) % 3] = 1.0;
+
+		t1 *= p_aabb.size[(closest_axis + 1) % 3] / float(color_scan_cell_width);
+		t2 *= p_aabb.size[(closest_axis + 2) % 3] / float(color_scan_cell_width);
+
+		Color albedo_accum;
+		Color emission_accum;
+		Vector3 normal_accum;
+
+		float alpha = 0.0;
+
+		//map to a grid average in the best axis for this face
+		for (int i = 0; i < color_scan_cell_width; i++) {
+
+			Vector3 ofs_i = float(i) * t1;
+
+			for (int j = 0; j < color_scan_cell_width; j++) {
+
+				Vector3 ofs_j = float(j) * t2;
+
+				Vector3 from = p_aabb.position + ofs_i + ofs_j;
+				Vector3 to = from + t1 + t2 + axis * p_aabb.size[closest_axis];
+				Vector3 half = (to - from) * 0.5;
+
+				//is in this cell?
+				if (!fast_tri_box_overlap(from + half, half, p_vtx)) {
+					continue; //face does not span this cell
+				}
+
+				//go from -size to +size*2 to avoid skipping collisions
+				Vector3 ray_from = from + (t1 + t2) * 0.5 - axis * p_aabb.size[closest_axis];
+				Vector3 ray_to = ray_from + axis * p_aabb.size[closest_axis] * 2;
+
+				if (normal.dot(ray_from - ray_to) < 0) {
+					SWAP(ray_from, ray_to);
+				}
+
+				Vector3 intersection;
+
+				if (!plane.intersects_segment(ray_from, ray_to, &intersection)) {
+					if (ABS(plane.distance_to(ray_from)) < ABS(plane.distance_to(ray_to))) {
+						intersection = plane.project(ray_from);
+					} else {
+
+						intersection = plane.project(ray_to);
+					}
+				}
+
+				intersection = Face3(p_vtx[0], p_vtx[1], p_vtx[2]).get_closest_point_to(intersection);
+
+				Vector2 uv = get_uv(intersection, p_vtx, p_uv);
+
+				int uv_x = CLAMP(Math::fposmod(uv.x, 1.0f) * bake_texture_size, 0, bake_texture_size - 1);
+				int uv_y = CLAMP(Math::fposmod(uv.y, 1.0f) * bake_texture_size, 0, bake_texture_size - 1);
+
+				int ofs = uv_y * bake_texture_size + uv_x;
+				albedo_accum.r += p_material.albedo[ofs].r;
+				albedo_accum.g += p_material.albedo[ofs].g;
+				albedo_accum.b += p_material.albedo[ofs].b;
+				albedo_accum.a += p_material.albedo[ofs].a;
+
+				emission_accum.r += p_material.emission[ofs].r;
+				emission_accum.g += p_material.emission[ofs].g;
+				emission_accum.b += p_material.emission[ofs].b;
+
+				normal_accum += normal;
+
+				alpha += 1.0;
+			}
+		}
+
+		if (alpha == 0) {
+			//could not in any way get texture information.. so use closest point to center
+
+			Face3 f(p_vtx[0], p_vtx[1], p_vtx[2]);
+			Vector3 inters = f.get_closest_point_to(p_aabb.position + p_aabb.size * 0.5);
+
+			Vector2 uv = get_uv(inters, p_vtx, p_uv);
+
+			int uv_x = CLAMP(Math::fposmod(uv.x, 1.0f) * bake_texture_size, 0, bake_texture_size - 1);
+			int uv_y = CLAMP(Math::fposmod(uv.y, 1.0f) * bake_texture_size, 0, bake_texture_size - 1);
+
+			int ofs = uv_y * bake_texture_size + uv_x;
+
+			alpha = 1.0 / (color_scan_cell_width * color_scan_cell_width);
+
+			albedo_accum.r = p_material.albedo[ofs].r * alpha;
+			albedo_accum.g = p_material.albedo[ofs].g * alpha;
+			albedo_accum.b = p_material.albedo[ofs].b * alpha;
+			albedo_accum.a = p_material.albedo[ofs].a * alpha;
+
+			emission_accum.r = p_material.emission[ofs].r * alpha;
+			emission_accum.g = p_material.emission[ofs].g * alpha;
+			emission_accum.b = p_material.emission[ofs].b * alpha;
+
+			normal_accum *= alpha;
+
+		} else {
+
+			float accdiv = 1.0 / (color_scan_cell_width * color_scan_cell_width);
+			alpha *= accdiv;
+
+			albedo_accum.r *= accdiv;
+			albedo_accum.g *= accdiv;
+			albedo_accum.b *= accdiv;
+			albedo_accum.a *= accdiv;
+
+			emission_accum.r *= accdiv;
+			emission_accum.g *= accdiv;
+			emission_accum.b *= accdiv;
+
+			normal_accum *= accdiv;
+		}
+
+		//put this temporarily here, corrected in a later step
+		bake_cells[p_idx].albedo[0] += albedo_accum.r;
+		bake_cells[p_idx].albedo[1] += albedo_accum.g;
+		bake_cells[p_idx].albedo[2] += albedo_accum.b;
+		bake_cells[p_idx].emission[0] += emission_accum.r;
+		bake_cells[p_idx].emission[1] += emission_accum.g;
+		bake_cells[p_idx].emission[2] += emission_accum.b;
+		bake_cells[p_idx].normal[0] += normal_accum.x;
+		bake_cells[p_idx].normal[1] += normal_accum.y;
+		bake_cells[p_idx].normal[2] += normal_accum.z;
+		bake_cells[p_idx].alpha += alpha;
+
+	} else {
+		//go down
+
+		int half = (1 << (cell_subdiv - 1)) >> (p_level + 1);
+		for (int i = 0; i < 8; i++) {
+
+			AABB aabb = p_aabb;
+			aabb.size *= 0.5;
+
+			int nx = p_x;
+			int ny = p_y;
+			int nz = p_z;
+
+			if (i & 1) {
+				aabb.position.x += aabb.size.x;
+				nx += half;
+			}
+			if (i & 2) {
+				aabb.position.y += aabb.size.y;
+				ny += half;
+			}
+			if (i & 4) {
+				aabb.position.z += aabb.size.z;
+				nz += half;
+			}
+			//make sure to not plot beyond limits
+			if (nx < 0 || nx >= axis_cell_size[0] || ny < 0 || ny >= axis_cell_size[1] || nz < 0 || nz >= axis_cell_size[2])
+				continue;
+
+			{
+				AABB test_aabb = aabb;
+				//test_aabb.grow_by(test_aabb.get_longest_axis_size()*0.05); //grow a bit to avoid numerical error in real-time
+				Vector3 qsize = test_aabb.size * 0.5; //quarter size, for fast aabb test
+
+				if (!fast_tri_box_overlap(test_aabb.position + qsize, qsize, p_vtx)) {
+					//if (!Face3(p_vtx[0],p_vtx[1],p_vtx[2]).intersects_aabb2(aabb)) {
+					//does not fit in child, go on
+					continue;
+				}
+			}
+
+			if (bake_cells[p_idx].childs[i] == CHILD_EMPTY) {
+				//sub cell must be created
+
+				uint32_t child_idx = bake_cells.size();
+				bake_cells[p_idx].childs[i] = child_idx;
+				bake_cells.resize(bake_cells.size() + 1);
+				bake_cells[child_idx].level = p_level + 1;
+			}
+
+			_plot_face(bake_cells[p_idx].childs[i], p_level + 1, nx, ny, nz, p_vtx, p_uv, p_material, aabb);
+		}
+	}
+}
+
+Vector<Color> VoxelLightBaker::_get_bake_texture(Ref<Image> p_image, const Color &p_color_mul, const Color &p_color_add) {
+
+	Vector<Color> ret;
+
+	if (p_image.is_null() || p_image->empty()) {
+
+		ret.resize(bake_texture_size * bake_texture_size);
+		for (int i = 0; i < bake_texture_size * bake_texture_size; i++) {
+			ret[i] = p_color_add;
+		}
+
+		return ret;
+	}
+	p_image = p_image->duplicate();
+
+	if (p_image->is_compressed()) {
+		print_line("DECOMPRESSING!!!!");
+
+		p_image->decompress();
+	}
+	p_image->convert(Image::FORMAT_RGBA8);
+	p_image->resize(bake_texture_size, bake_texture_size, Image::INTERPOLATE_CUBIC);
+
+	PoolVector<uint8_t>::Read r = p_image->get_data().read();
+	ret.resize(bake_texture_size * bake_texture_size);
+
+	for (int i = 0; i < bake_texture_size * bake_texture_size; i++) {
+		Color c;
+		c.r = (r[i * 4 + 0] / 255.0) * p_color_mul.r + p_color_add.r;
+		c.g = (r[i * 4 + 1] / 255.0) * p_color_mul.g + p_color_add.g;
+		c.b = (r[i * 4 + 2] / 255.0) * p_color_mul.b + p_color_add.b;
+
+		c.a = r[i * 4 + 3] / 255.0;
+
+		ret[i] = c;
+	}
+
+	return ret;
+}
+
+VoxelLightBaker::MaterialCache VoxelLightBaker::_get_material_cache(Ref<Material> p_material) {
+
+	//this way of obtaining materials is inaccurate and also does not support some compressed formats very well
+	Ref<SpatialMaterial> mat = p_material;
+
+	Ref<Material> material = mat; //hack for now
+
+	if (material_cache.has(material)) {
+		return material_cache[material];
+	}
+
+	MaterialCache mc;
+
+	if (mat.is_valid()) {
+
+		Ref<Texture> albedo_tex = mat->get_texture(SpatialMaterial::TEXTURE_ALBEDO);
+
+		Ref<Image> img_albedo;
+		if (albedo_tex.is_valid()) {
+
+			img_albedo = albedo_tex->get_data();
+			mc.albedo = _get_bake_texture(img_albedo, mat->get_albedo(), Color(0, 0, 0)); // albedo texture, color is multiplicative
+		} else {
+			mc.albedo = _get_bake_texture(img_albedo, Color(1, 1, 1), mat->get_albedo()); // no albedo texture, color is additive
+		}
+
+		Ref<Texture> emission_tex = mat->get_texture(SpatialMaterial::TEXTURE_EMISSION);
+
+		Color emission_col = mat->get_emission();
+		float emission_energy = mat->get_emission_energy();
+
+		Ref<Image> img_emission;
+
+		if (emission_tex.is_valid()) {
+
+			img_emission = emission_tex->get_data();
+		}
+
+		if (mat->get_emission_operator() == SpatialMaterial::EMISSION_OP_ADD) {
+			mc.emission = _get_bake_texture(img_emission, Color(1, 1, 1) * emission_energy, emission_col * emission_energy);
+		} else {
+			mc.emission = _get_bake_texture(img_emission, emission_col * emission_energy, Color(0, 0, 0));
+		}
+
+	} else {
+		Ref<Image> empty;
+
+		mc.albedo = _get_bake_texture(empty, Color(0, 0, 0), Color(1, 1, 1));
+		mc.emission = _get_bake_texture(empty, Color(0, 0, 0), Color(0, 0, 0));
+	}
+
+	material_cache[p_material] = mc;
+	return mc;
+}
+
+void VoxelLightBaker::plot_mesh(const Transform &p_xform, Ref<Mesh> &p_mesh, const Vector<Ref<Material> > &p_materials, const Ref<Material> &p_override_material) {
+
+	for (int i = 0; i < p_mesh->get_surface_count(); i++) {
+
+		if (p_mesh->surface_get_primitive_type(i) != Mesh::PRIMITIVE_TRIANGLES)
+			continue; //only triangles
+
+		Ref<Material> src_material;
+
+		if (p_override_material.is_valid()) {
+			src_material = p_override_material;
+		} else if (i < p_materials.size() && p_materials[i].is_valid()) {
+			src_material = p_materials[i];
+		} else {
+			src_material = p_mesh->surface_get_material(i);
+		}
+		MaterialCache material = _get_material_cache(src_material);
+
+		Array a = p_mesh->surface_get_arrays(i);
+
+		PoolVector<Vector3> vertices = a[Mesh::ARRAY_VERTEX];
+		PoolVector<Vector3>::Read vr = vertices.read();
+		PoolVector<Vector2> uv = a[Mesh::ARRAY_TEX_UV];
+		PoolVector<Vector2>::Read uvr;
+		PoolVector<int> index = a[Mesh::ARRAY_INDEX];
+
+		bool read_uv = false;
+
+		if (uv.size()) {
+
+			uvr = uv.read();
+			read_uv = true;
+		}
+
+		if (index.size()) {
+
+			int facecount = index.size() / 3;
+			PoolVector<int>::Read ir = index.read();
+
+			for (int j = 0; j < facecount; j++) {
+
+				Vector3 vtxs[3];
+				Vector2 uvs[3];
+
+				for (int k = 0; k < 3; k++) {
+					vtxs[k] = p_xform.xform(vr[ir[j * 3 + k]]);
+				}
+
+				if (read_uv) {
+					for (int k = 0; k < 3; k++) {
+						uvs[k] = uvr[ir[j * 3 + k]];
+					}
+				}
+
+				//test against original bounds
+				if (!fast_tri_box_overlap(original_bounds.position + original_bounds.size * 0.5, original_bounds.size * 0.5, vtxs))
+					continue;
+				//plot
+				_plot_face(0, 0, 0, 0, 0, vtxs, uvs, material, po2_bounds);
+			}
+
+		} else {
+
+			int facecount = vertices.size() / 3;
+
+			for (int j = 0; j < facecount; j++) {
+
+				Vector3 vtxs[3];
+				Vector2 uvs[3];
+
+				for (int k = 0; k < 3; k++) {
+					vtxs[k] = p_xform.xform(vr[j * 3 + k]);
+				}
+
+				if (read_uv) {
+					for (int k = 0; k < 3; k++) {
+						uvs[k] = uvr[j * 3 + k];
+					}
+				}
+
+				//test against original bounds
+				if (!fast_tri_box_overlap(original_bounds.position + original_bounds.size * 0.5, original_bounds.size * 0.5, vtxs))
+					continue;
+				//plot face
+				_plot_face(0, 0, 0, 0, 0, vtxs, uvs, material, po2_bounds);
+			}
+		}
+	}
+
+	max_original_cells = bake_cells.size();
+}
+
+void VoxelLightBaker::_init_light_plot(int p_idx, int p_level, int p_x, int p_y, int p_z, uint32_t p_parent) {
+
+	bake_light[p_idx].x = p_x;
+	bake_light[p_idx].y = p_y;
+	bake_light[p_idx].z = p_z;
+
+	if (p_level == cell_subdiv - 1) {
+
+		bake_light[p_idx].next_leaf = first_leaf;
+		first_leaf = p_idx;
+	} else {
+
+		//go down
+		int half = (1 << (cell_subdiv - 1)) >> (p_level + 1);
+		for (int i = 0; i < 8; i++) {
+
+			uint32_t child = bake_cells[p_idx].childs[i];
+
+			if (child == CHILD_EMPTY)
+				continue;
+
+			int nx = p_x;
+			int ny = p_y;
+			int nz = p_z;
+
+			if (i & 1)
+				nx += half;
+			if (i & 2)
+				ny += half;
+			if (i & 4)
+				nz += half;
+
+			_init_light_plot(child, p_level + 1, nx, ny, nz, p_idx);
+		}
+	}
+}
+
+void VoxelLightBaker::begin_bake_light(BakeQuality p_quality, BakeMode p_bake_mode, float p_propagation, float p_energy) {
+	_check_init_light();
+	propagation = p_propagation;
+	bake_quality = p_quality;
+	bake_mode = p_bake_mode;
+	energy = p_energy;
+}
+
+void VoxelLightBaker::_check_init_light() {
+	if (bake_light.size() == 0) {
+
+		direct_lights_baked = false;
+		leaf_voxel_count = 0;
+		_fixup_plot(0, 0); //pre fixup, so normal, albedo, emission, etc. work for lighting.
+		bake_light.resize(bake_cells.size());
+		zeromem(bake_light.ptrw(), bake_light.size() * sizeof(Light));
+		first_leaf = -1;
+		_init_light_plot(0, 0, 0, 0, 0, CHILD_EMPTY);
+	}
+}
+
+static float _get_normal_advance(const Vector3 &p_normal) {
+
+	Vector3 normal = p_normal;
+	Vector3 unorm = normal.abs();
+
+	if ((unorm.x >= unorm.y) && (unorm.x >= unorm.z)) {
+		// x code
+		unorm = normal.x > 0.0 ? Vector3(1.0, 0.0, 0.0) : Vector3(-1.0, 0.0, 0.0);
+	} else if ((unorm.y > unorm.x) && (unorm.y >= unorm.z)) {
+		// y code
+		unorm = normal.y > 0.0 ? Vector3(0.0, 1.0, 0.0) : Vector3(0.0, -1.0, 0.0);
+	} else if ((unorm.z > unorm.x) && (unorm.z > unorm.y)) {
+		// z code
+		unorm = normal.z > 0.0 ? Vector3(0.0, 0.0, 1.0) : Vector3(0.0, 0.0, -1.0);
+	} else {
+		// oh-no we messed up code
+		// has to be
+		unorm = Vector3(1.0, 0.0, 0.0);
+	}
+
+	return 1.0 / normal.dot(unorm);
+}
+
+static const Vector3 aniso_normal[6] = {
+	Vector3(-1, 0, 0),
+	Vector3(1, 0, 0),
+	Vector3(0, -1, 0),
+	Vector3(0, 1, 0),
+	Vector3(0, 0, -1),
+	Vector3(0, 0, 1)
+};
+
+uint32_t VoxelLightBaker::_find_cell_at_pos(const Cell *cells, int x, int y, int z) {
+
+	uint32_t cell = 0;
+
+	int ofs_x = 0;
+	int ofs_y = 0;
+	int ofs_z = 0;
+	int size = 1 << (cell_subdiv - 1);
+	int half = size / 2;
+
+	if (x < 0 || x >= size)
+		return -1;
+	if (y < 0 || y >= size)
+		return -1;
+	if (z < 0 || z >= size)
+		return -1;
+
+	for (int i = 0; i < cell_subdiv - 1; i++) {
+
+		const Cell *bc = &cells[cell];
+
+		int child = 0;
+		if (x >= ofs_x + half) {
+			child |= 1;
+			ofs_x += half;
+		}
+		if (y >= ofs_y + half) {
+			child |= 2;
+			ofs_y += half;
+		}
+		if (z >= ofs_z + half) {
+			child |= 4;
+			ofs_z += half;
+		}
+
+		cell = bc->childs[child];
+		if (cell == CHILD_EMPTY)
+			return CHILD_EMPTY;
+
+		half >>= 1;
+	}
+
+	return cell;
+}
+void VoxelLightBaker::plot_light_directional(const Vector3 &p_direction, const Color &p_color, float p_energy, float p_indirect_energy, bool p_direct) {
+
+	_check_init_light();
+
+	float max_len = Vector3(axis_cell_size[0], axis_cell_size[1], axis_cell_size[2]).length() * 1.1;
+
+	if (p_direct)
+		direct_lights_baked = true;
+
+	Vector3 light_axis = p_direction;
+	Plane clip[3];
+	int clip_planes = 0;
+
+	Light *light_data = bake_light.ptrw();
+	const Cell *cells = bake_cells.ptr();
+
+	for (int i = 0; i < 3; i++) {
+
+		if (ABS(light_axis[i]) < CMP_EPSILON)
+			continue;
+		clip[clip_planes].normal[i] = 1.0;
+
+		if (light_axis[i] < 0) {
+
+			clip[clip_planes].d = axis_cell_size[i] + 1;
+		} else {
+			clip[clip_planes].d -= 1.0;
+		}
+
+		clip_planes++;
+	}
+
+	float distance_adv = _get_normal_advance(light_axis);
+
+	int success_count = 0;
+
+	Vector3 light_energy = Vector3(p_color.r, p_color.g, p_color.b) * p_energy * p_indirect_energy;
+
+	int idx = first_leaf;
+	while (idx >= 0) {
+
+		//print_line("plot idx " + itos(idx));
+		Light *light = &light_data[idx];
+
+		Vector3 to(light->x + 0.5, light->y + 0.5, light->z + 0.5);
+		to += -light_axis.sign() * 0.47; //make it more likely to receive a ray
+
+		Vector3 from = to - max_len * light_axis;
+
+		for (int j = 0; j < clip_planes; j++) {
+
+			clip[j].intersects_segment(from, to, &from);
+		}
+
+		float distance = (to - from).length();
+		distance += distance_adv - Math::fmod(distance, distance_adv); //make it reach the center of the box always
+		from = to - light_axis * distance;
+
+		uint32_t result = 0xFFFFFFFF;
+
+		while (distance > -distance_adv) { //use this to avoid precision errors
+
+			result = _find_cell_at_pos(cells, int(floor(from.x)), int(floor(from.y)), int(floor(from.z)));
+			if (result != 0xFFFFFFFF) {
+				break;
+			}
+
+			from += light_axis * distance_adv;
+			distance -= distance_adv;
+		}
+
+		if (result == idx) {
+			//cell hit itself! hooray!
+
+			Vector3 normal(cells[idx].normal[0], cells[idx].normal[1], cells[idx].normal[2]);
+			if (normal == Vector3()) {
+				for (int i = 0; i < 6; i++) {
+					light->accum[i][0] += light_energy.x * cells[idx].albedo[0];
+					light->accum[i][1] += light_energy.y * cells[idx].albedo[1];
+					light->accum[i][2] += light_energy.z * cells[idx].albedo[2];
+				}
+
+			} else {
+
+				for (int i = 0; i < 6; i++) {
+					float s = MAX(0.0, aniso_normal[i].dot(-normal));
+					light->accum[i][0] += light_energy.x * cells[idx].albedo[0] * s;
+					light->accum[i][1] += light_energy.y * cells[idx].albedo[1] * s;
+					light->accum[i][2] += light_energy.z * cells[idx].albedo[2] * s;
+				}
+			}
+
+			for (int i = 0; i < 6; i++) {
+				float s = MAX(0.0, aniso_normal[i].dot(-light_axis)); //light depending on normal for direct
+				light->direct_accum[i][0] += light_energy.x * s;
+				light->direct_accum[i][1] += light_energy.y * s;
+				light->direct_accum[i][2] += light_energy.z * s;
+			}
+			success_count++;
+		}
+
+		idx = light_data[idx].next_leaf;
+	}
+}
+
+void VoxelLightBaker::plot_light_omni(const Vector3 &p_pos, const Color &p_color, float p_energy, float p_indirect_energy, float p_radius, float p_attenutation, bool p_direct) {
+
+	_check_init_light();
+
+	if (p_direct)
+		direct_lights_baked = true;
+
+	Plane clip[3];
+	int clip_planes = 0;
+
+	// uint64_t us = OS::get_singleton()->get_ticks_usec();
+
+	Vector3 light_pos = to_cell_space.xform(p_pos) + Vector3(0.5, 0.5, 0.5);
+	//Vector3 spot_axis = -light_cache.transform.basis.get_axis(2).normalized();
+
+	float local_radius = to_cell_space.basis.xform(Vector3(0, 0, 1)).length() * p_radius;
+
+	Light *light_data = bake_light.ptrw();
+	const Cell *cells = bake_cells.ptr();
+	Vector3 light_energy = Vector3(p_color.r, p_color.g, p_color.b) * p_energy * p_indirect_energy;
+
+	int idx = first_leaf;
+	while (idx >= 0) {
+
+		//print_line("plot idx " + itos(idx));
+		Light *light = &light_data[idx];
+
+		Vector3 to(light->x + 0.5, light->y + 0.5, light->z + 0.5);
+		to += (light_pos - to).sign() * 0.47; //make it more likely to receive a ray
+
+		Vector3 light_axis = (to - light_pos).normalized();
+		float distance_adv = _get_normal_advance(light_axis);
+
+		Vector3 normal(cells[idx].normal[0], cells[idx].normal[1], cells[idx].normal[2]);
+
+		if (normal != Vector3() && normal.dot(-light_axis) < 0.001) {
+			idx = light_data[idx].next_leaf;
+			continue;
+		}
+
+		float att = 1.0;
+		{
+			float d = light_pos.distance_to(to);
+			if (d + distance_adv > local_radius) {
+				idx = light_data[idx].next_leaf;
+				continue; // too far away
+			}
+
+			float dt = CLAMP((d + distance_adv) / local_radius, 0, 1);
+			att *= powf(1.0 - dt, p_attenutation);
+		}
+#if 0
+		if (light_cache.type == VS::LIGHT_SPOT) {
+
+			float angle = Math::rad2deg(acos(light_axis.dot(spot_axis)));
+			if (angle > light_cache.spot_angle)
+				continue;
+
+			float d = CLAMP(angle / light_cache.spot_angle, 1, 0);
+			att *= powf(1.0 - d, light_cache.spot_attenuation);
+		}
+#endif
+		clip_planes = 0;
+
+		for (int c = 0; c < 3; c++) {
+
+			if (ABS(light_axis[c]) < CMP_EPSILON)
+				continue;
+			clip[clip_planes].normal[c] = 1.0;
+
+			if (light_axis[c] < 0) {
+
+				clip[clip_planes].d = (1 << (cell_subdiv - 1)) + 1;
+			} else {
+				clip[clip_planes].d -= 1.0;
+			}
+
+			clip_planes++;
+		}
+
+		Vector3 from = light_pos;
+
+		for (int j = 0; j < clip_planes; j++) {
+
+			clip[j].intersects_segment(from, to, &from);
+		}
+
+		float distance = (to - from).length();
+
+		distance -= Math::fmod(distance, distance_adv); //make it reach the center of the box always, but this tame make it closer
+		from = to - light_axis * distance;
+		to += (light_pos - to).sign() * 0.47; //make it more likely to receive a ray
+
+		uint32_t result = 0xFFFFFFFF;
+
+		while (distance > -distance_adv) { //use this to avoid precision errors
+
+			result = _find_cell_at_pos(cells, int(floor(from.x)), int(floor(from.y)), int(floor(from.z)));
+			if (result != 0xFFFFFFFF) {
+				break;
+			}
+
+			from += light_axis * distance_adv;
+			distance -= distance_adv;
+		}
+
+		if (result == idx) {
+			//cell hit itself! hooray!
+
+			if (normal == Vector3()) {
+				for (int i = 0; i < 6; i++) {
+					light->accum[i][0] += light_energy.x * cells[idx].albedo[0] * att;
+					light->accum[i][1] += light_energy.y * cells[idx].albedo[1] * att;
+					light->accum[i][2] += light_energy.z * cells[idx].albedo[2] * att;
+				}
+
+			} else {
+
+				for (int i = 0; i < 6; i++) {
+					float s = MAX(0.0, aniso_normal[i].dot(-normal));
+					light->accum[i][0] += light_energy.x * cells[idx].albedo[0] * s * att;
+					light->accum[i][1] += light_energy.y * cells[idx].albedo[1] * s * att;
+					light->accum[i][2] += light_energy.z * cells[idx].albedo[2] * s * att;
+				}
+			}
+
+			for (int i = 0; i < 6; i++) {
+				float s = MAX(0.0, aniso_normal[i].dot(-light_axis)); //light depending on normal for direct
+				light->direct_accum[i][0] += light_energy.x * s * att;
+				light->direct_accum[i][1] += light_energy.y * s * att;
+				light->direct_accum[i][2] += light_energy.z * s * att;
+			}
+		}
+
+		idx = light_data[idx].next_leaf;
+	}
+}
+
+void VoxelLightBaker::plot_light_spot(const Vector3 &p_pos, const Vector3 &p_axis, const Color &p_color, float p_energy, float p_indirect_energy, float p_radius, float p_attenutation, float p_spot_angle, float p_spot_attenuation, bool p_direct) {
+
+	_check_init_light();
+
+	if (p_direct)
+		direct_lights_baked = true;
+
+	Plane clip[3];
+	int clip_planes = 0;
+
+	// uint64_t us = OS::get_singleton()->get_ticks_usec();
+
+	Vector3 light_pos = to_cell_space.xform(p_pos) + Vector3(0.5, 0.5, 0.5);
+	Vector3 spot_axis = to_cell_space.basis.xform(p_axis).normalized();
+
+	float local_radius = to_cell_space.basis.xform(Vector3(0, 0, 1)).length() * p_radius;
+
+	Light *light_data = bake_light.ptrw();
+	const Cell *cells = bake_cells.ptr();
+	Vector3 light_energy = Vector3(p_color.r, p_color.g, p_color.b) * p_energy * p_indirect_energy;
+
+	int idx = first_leaf;
+	while (idx >= 0) {
+
+		//print_line("plot idx " + itos(idx));
+		Light *light = &light_data[idx];
+
+		Vector3 to(light->x + 0.5, light->y + 0.5, light->z + 0.5);
+
+		Vector3 light_axis = (to - light_pos).normalized();
+		float distance_adv = _get_normal_advance(light_axis);
+
+		Vector3 normal(cells[idx].normal[0], cells[idx].normal[1], cells[idx].normal[2]);
+
+		if (normal != Vector3() && normal.dot(-light_axis) < 0.001) {
+			idx = light_data[idx].next_leaf;
+			continue;
+		}
+
+		float angle = Math::rad2deg(Math::acos(light_axis.dot(-spot_axis)));
+		if (angle > p_spot_angle) {
+			idx = light_data[idx].next_leaf;
+			continue; // too far away
+		}
+
+		float att = Math::pow(1.0 - angle / p_spot_angle, p_spot_attenuation);
+
+		{
+			float d = light_pos.distance_to(to);
+			if (d + distance_adv > local_radius) {
+				idx = light_data[idx].next_leaf;
+				continue; // too far away
+			}
+
+			float dt = CLAMP((d + distance_adv) / local_radius, 0, 1);
+			att *= powf(1.0 - dt, p_attenutation);
+		}
+#if 0
+		if (light_cache.type == VS::LIGHT_SPOT) {
+
+			float angle = Math::rad2deg(acos(light_axis.dot(spot_axis)));
+			if (angle > light_cache.spot_angle)
+				continue;
+
+			float d = CLAMP(angle / light_cache.spot_angle, 1, 0);
+			att *= powf(1.0 - d, light_cache.spot_attenuation);
+		}
+#endif
+		clip_planes = 0;
+
+		for (int c = 0; c < 3; c++) {
+
+			if (ABS(light_axis[c]) < CMP_EPSILON)
+				continue;
+			clip[clip_planes].normal[c] = 1.0;
+
+			if (light_axis[c] < 0) {
+
+				clip[clip_planes].d = (1 << (cell_subdiv - 1)) + 1;
+			} else {
+				clip[clip_planes].d -= 1.0;
+			}
+
+			clip_planes++;
+		}
+
+		Vector3 from = light_pos;
+
+		for (int j = 0; j < clip_planes; j++) {
+
+			clip[j].intersects_segment(from, to, &from);
+		}
+
+		float distance = (to - from).length();
+
+		distance -= Math::fmod(distance, distance_adv); //make it reach the center of the box always, but this tame make it closer
+		from = to - light_axis * distance;
+
+		uint32_t result = 0xFFFFFFFF;
+
+		while (distance > -distance_adv) { //use this to avoid precision errors
+
+			result = _find_cell_at_pos(cells, int(floor(from.x)), int(floor(from.y)), int(floor(from.z)));
+			if (result != 0xFFFFFFFF) {
+				break;
+			}
+
+			from += light_axis * distance_adv;
+			distance -= distance_adv;
+		}
+
+		if (result == idx) {
+			//cell hit itself! hooray!
+
+			if (normal == Vector3()) {
+				for (int i = 0; i < 6; i++) {
+					light->accum[i][0] += light_energy.x * cells[idx].albedo[0] * att;
+					light->accum[i][1] += light_energy.y * cells[idx].albedo[1] * att;
+					light->accum[i][2] += light_energy.z * cells[idx].albedo[2] * att;
+				}
+
+			} else {
+
+				for (int i = 0; i < 6; i++) {
+					float s = MAX(0.0, aniso_normal[i].dot(-normal));
+					light->accum[i][0] += light_energy.x * cells[idx].albedo[0] * s * att;
+					light->accum[i][1] += light_energy.y * cells[idx].albedo[1] * s * att;
+					light->accum[i][2] += light_energy.z * cells[idx].albedo[2] * s * att;
+				}
+			}
+
+			for (int i = 0; i < 6; i++) {
+				float s = MAX(0.0, aniso_normal[i].dot(-light_axis)); //light depending on normal for direct
+				light->direct_accum[i][0] += light_energy.x * s * att;
+				light->direct_accum[i][1] += light_energy.y * s * att;
+				light->direct_accum[i][2] += light_energy.z * s * att;
+			}
+		}
+
+		idx = light_data[idx].next_leaf;
+	}
+}
+
+void VoxelLightBaker::_fixup_plot(int p_idx, int p_level) {
+
+	if (p_level == cell_subdiv - 1) {
+
+		leaf_voxel_count++;
+		float alpha = bake_cells[p_idx].alpha;
+
+		bake_cells[p_idx].albedo[0] /= alpha;
+		bake_cells[p_idx].albedo[1] /= alpha;
+		bake_cells[p_idx].albedo[2] /= alpha;
+
+		//transfer emission to light
+		bake_cells[p_idx].emission[0] /= alpha;
+		bake_cells[p_idx].emission[1] /= alpha;
+		bake_cells[p_idx].emission[2] /= alpha;
+
+		bake_cells[p_idx].normal[0] /= alpha;
+		bake_cells[p_idx].normal[1] /= alpha;
+		bake_cells[p_idx].normal[2] /= alpha;
+
+		Vector3 n(bake_cells[p_idx].normal[0], bake_cells[p_idx].normal[1], bake_cells[p_idx].normal[2]);
+		if (n.length() < 0.01) {
+			//too much fight over normal, zero it
+			bake_cells[p_idx].normal[0] = 0;
+			bake_cells[p_idx].normal[1] = 0;
+			bake_cells[p_idx].normal[2] = 0;
+		} else {
+			n.normalize();
+			bake_cells[p_idx].normal[0] = n.x;
+			bake_cells[p_idx].normal[1] = n.y;
+			bake_cells[p_idx].normal[2] = n.z;
+		}
+
+		bake_cells[p_idx].alpha = 1.0;
+
+		/*if (bake_light.size()) {
+			for(int i=0;i<6;i++) {
+
+			}
+		}*/
+
+	} else {
+
+		//go down
+
+		bake_cells[p_idx].emission[0] = 0;
+		bake_cells[p_idx].emission[1] = 0;
+		bake_cells[p_idx].emission[2] = 0;
+		bake_cells[p_idx].normal[0] = 0;
+		bake_cells[p_idx].normal[1] = 0;
+		bake_cells[p_idx].normal[2] = 0;
+		bake_cells[p_idx].albedo[0] = 0;
+		bake_cells[p_idx].albedo[1] = 0;
+		bake_cells[p_idx].albedo[2] = 0;
+		if (bake_light.size()) {
+			for (int j = 0; j < 6; j++) {
+				bake_light[p_idx].accum[j][0] = 0;
+				bake_light[p_idx].accum[j][1] = 0;
+				bake_light[p_idx].accum[j][2] = 0;
+			}
+		}
+
+		float alpha_average = 0;
+		int children_found = 0;
+
+		for (int i = 0; i < 8; i++) {
+
+			uint32_t child = bake_cells[p_idx].childs[i];
+
+			if (child == CHILD_EMPTY)
+				continue;
+
+			_fixup_plot(child, p_level + 1);
+			alpha_average += bake_cells[child].alpha;
+
+			if (bake_light.size() > 0) {
+				for (int j = 0; j < 6; j++) {
+					bake_light[p_idx].accum[j][0] += bake_light[child].accum[j][0];
+					bake_light[p_idx].accum[j][1] += bake_light[child].accum[j][1];
+					bake_light[p_idx].accum[j][2] += bake_light[child].accum[j][2];
+				}
+				bake_cells[p_idx].emission[0] += bake_cells[child].emission[0];
+				bake_cells[p_idx].emission[1] += bake_cells[child].emission[1];
+				bake_cells[p_idx].emission[2] += bake_cells[child].emission[2];
+			}
+
+			children_found++;
+		}
+
+		bake_cells[p_idx].alpha = alpha_average / 8.0;
+		if (bake_light.size() && children_found) {
+			float divisor = Math::lerp(8, children_found, propagation);
+			for (int j = 0; j < 6; j++) {
+				bake_light[p_idx].accum[j][0] /= divisor;
+				bake_light[p_idx].accum[j][1] /= divisor;
+				bake_light[p_idx].accum[j][2] /= divisor;
+			}
+			bake_cells[p_idx].emission[0] /= divisor;
+			bake_cells[p_idx].emission[1] /= divisor;
+			bake_cells[p_idx].emission[2] /= divisor;
+		}
+	}
+}
+
+//make sure any cell (save for the root) has an empty cell previous to it, so it can be interpolated into
+
+void VoxelLightBaker::_plot_triangle(Vector2 *vertices, Vector3 *positions, Vector3 *normals, LightMap *pixels, int width, int height) {
+
+	int x[3];
+	int y[3];
+
+	for (int j = 0; j < 3; j++) {
+
+		x[j] = vertices[j].x * width;
+		y[j] = vertices[j].y * height;
+		//x[j] = CLAMP(x[j], 0, bt.width - 1);
+		//y[j] = CLAMP(y[j], 0, bt.height - 1);
+	}
+
+	// sort the points vertically
+	if (y[1] > y[2]) {
+		SWAP(x[1], x[2]);
+		SWAP(y[1], y[2]);
+		SWAP(positions[1], positions[2]);
+		SWAP(normals[1], normals[2]);
+	}
+	if (y[0] > y[1]) {
+		SWAP(x[0], x[1]);
+		SWAP(y[0], y[1]);
+		SWAP(positions[0], positions[1]);
+		SWAP(normals[0], normals[1]);
+	}
+	if (y[1] > y[2]) {
+		SWAP(x[1], x[2]);
+		SWAP(y[1], y[2]);
+		SWAP(positions[1], positions[2]);
+		SWAP(normals[1], normals[2]);
+	}
+
+	double dx_far = double(x[2] - x[0]) / (y[2] - y[0] + 1);
+	double dx_upper = double(x[1] - x[0]) / (y[1] - y[0] + 1);
+	double dx_low = double(x[2] - x[1]) / (y[2] - y[1] + 1);
+	double xf = x[0];
+	double xt = x[0] + dx_upper; // if y[0] == y[1], special case
+	for (int yi = y[0]; yi <= (y[2] > height - 1 ? height - 1 : y[2]); yi++) {
+		if (yi >= 0) {
+			for (int xi = (xf > 0 ? int(xf) : 0); xi <= (xt < width ? xt : width - 1); xi++) {
+				//pixels[int(x + y * width)] = color;
+
+				Vector2 v0 = Vector2(x[1] - x[0], y[1] - y[0]);
+				Vector2 v1 = Vector2(x[2] - x[0], y[2] - y[0]);
+				//vertices[2] - vertices[0];
+				Vector2 v2 = Vector2(xi - x[0], yi - y[0]);
+				float d00 = v0.dot(v0);
+				float d01 = v0.dot(v1);
+				float d11 = v1.dot(v1);
+				float d20 = v2.dot(v0);
+				float d21 = v2.dot(v1);
+				float denom = (d00 * d11 - d01 * d01);
+				Vector3 pos;
+				Vector3 normal;
+				if (denom == 0) {
+					pos = positions[0];
+					normal = normals[0];
+				} else {
+					float v = (d11 * d20 - d01 * d21) / denom;
+					float w = (d00 * d21 - d01 * d20) / denom;
+					float u = 1.0f - v - w;
+					pos = positions[0] * u + positions[1] * v + positions[2] * w;
+					normal = normals[0] * u + normals[1] * v + normals[2] * w;
+				}
+
+				int ofs = yi * width + xi;
+				pixels[ofs].normal = normal;
+				pixels[ofs].pos = pos;
+			}
+
+			for (int xi = (xf < width ? int(xf) : width - 1); xi >= (xt > 0 ? xt : 0); xi--) {
+				//pixels[int(x + y * width)] = color;
+				Vector2 v0 = Vector2(x[1] - x[0], y[1] - y[0]);
+				Vector2 v1 = Vector2(x[2] - x[0], y[2] - y[0]);
+				//vertices[2] - vertices[0];
+				Vector2 v2 = Vector2(xi - x[0], yi - y[0]);
+				float d00 = v0.dot(v0);
+				float d01 = v0.dot(v1);
+				float d11 = v1.dot(v1);
+				float d20 = v2.dot(v0);
+				float d21 = v2.dot(v1);
+				float denom = (d00 * d11 - d01 * d01);
+				Vector3 pos;
+				Vector3 normal;
+				if (denom == 0) {
+					pos = positions[0];
+					normal = normals[0];
+				} else {
+					float v = (d11 * d20 - d01 * d21) / denom;
+					float w = (d00 * d21 - d01 * d20) / denom;
+					float u = 1.0f - v - w;
+					pos = positions[0] * u + positions[1] * v + positions[2] * w;
+					normal = normals[0] * u + normals[1] * v + normals[2] * w;
+				}
+
+				int ofs = yi * width + xi;
+				pixels[ofs].normal = normal;
+				pixels[ofs].pos = pos;
+			}
+		}
+		xf += dx_far;
+		if (yi < y[1])
+			xt += dx_upper;
+		else
+			xt += dx_low;
+	}
+}
+
+void VoxelLightBaker::_sample_baked_octree_filtered_and_anisotropic(const Vector3 &p_posf, const Vector3 &p_direction, float p_level, Vector3 &r_color, float &r_alpha) {
+
+	int size = 1 << (cell_subdiv - 1);
+
+	int clamp_v = size - 1;
+	//first of all, clamp
+	Vector3 pos;
+	pos.x = CLAMP(p_posf.x, 0, clamp_v);
+	pos.y = CLAMP(p_posf.y, 0, clamp_v);
+	pos.z = CLAMP(p_posf.z, 0, clamp_v);
+
+	float level = (cell_subdiv - 1) - p_level;
+
+	int target_level;
+	float level_filter;
+	if (level <= 0.0) {
+		level_filter = 0;
+		target_level = 0;
+	} else {
+		target_level = Math::ceil(level);
+		level_filter = target_level - level;
+	}
+
+	const Cell *cells = bake_cells.ptr();
+	const Light *light = bake_light.ptr();
+
+	Vector3 color[2][8];
+	float alpha[2][8];
+	zeromem(alpha, sizeof(float) * 2 * 8);
+
+	//find cell at given level first
+
+	for (int c = 0; c < 2; c++) {
+
+		int current_level = MAX(0, target_level - c);
+		int level_cell_size = (1 << (cell_subdiv - 1)) >> current_level;
+
+		for (int n = 0; n < 8; n++) {
+
+			int x = int(pos.x);
+			int y = int(pos.y);
+			int z = int(pos.z);
+
+			if (n & 1)
+				x += level_cell_size;
+			if (n & 2)
+				y += level_cell_size;
+			if (n & 4)
+				z += level_cell_size;
+
+			int ofs_x = 0;
+			int ofs_y = 0;
+			int ofs_z = 0;
+
+			x = CLAMP(x, 0, clamp_v);
+			y = CLAMP(y, 0, clamp_v);
+			z = CLAMP(z, 0, clamp_v);
+
+			int half = size / 2;
+			uint32_t cell = 0;
+			for (int i = 0; i < current_level; i++) {
+
+				const Cell *bc = &cells[cell];
+
+				int child = 0;
+				if (x >= ofs_x + half) {
+					child |= 1;
+					ofs_x += half;
+				}
+				if (y >= ofs_y + half) {
+					child |= 2;
+					ofs_y += half;
+				}
+				if (z >= ofs_z + half) {
+					child |= 4;
+					ofs_z += half;
+				}
+
+				cell = bc->childs[child];
+				if (cell == CHILD_EMPTY)
+					break;
+
+				half >>= 1;
+			}
+
+			if (cell == CHILD_EMPTY) {
+				alpha[c][n] = 0;
+			} else {
+				alpha[c][n] = cells[cell].alpha;
+
+				for (int i = 0; i < 6; i++) {
+					//anisotropic read light
+					float amount = p_direction.dot(aniso_normal[i]);
+					//if (c == 0) {
+					//	print_line("\t" + itos(n) + " aniso " + itos(i) + " " + rtos(light[cell].accum[i][0]) + " VEC: " + aniso_normal[i]);
+					//}
+					if (amount < 0)
+						amount = 0;
+					//amount = 1;
+					color[c][n].x += light[cell].accum[i][0] * amount;
+					color[c][n].y += light[cell].accum[i][1] * amount;
+					color[c][n].z += light[cell].accum[i][2] * amount;
+				}
+
+				color[c][n].x += cells[cell].emission[0];
+				color[c][n].y += cells[cell].emission[1];
+				color[c][n].z += cells[cell].emission[2];
+			}
+
+			//print_line("\tlev " + itos(c) + " - " + itos(n) + " alpha: " + rtos(cells[test_cell].alpha) + " col: " + color[c][n]);
+		}
+	}
+
+	float target_level_size = size >> target_level;
+	Vector3 pos_fract[2];
+
+	pos_fract[0].x = Math::fmod(pos.x, target_level_size) / target_level_size;
+	pos_fract[0].y = Math::fmod(pos.y, target_level_size) / target_level_size;
+	pos_fract[0].z = Math::fmod(pos.z, target_level_size) / target_level_size;
+
+	target_level_size = size >> MAX(0, target_level - 1);
+
+	pos_fract[1].x = Math::fmod(pos.x, target_level_size) / target_level_size;
+	pos_fract[1].y = Math::fmod(pos.y, target_level_size) / target_level_size;
+	pos_fract[1].z = Math::fmod(pos.z, target_level_size) / target_level_size;
+
+	float alpha_interp[2];
+	Vector3 color_interp[2];
+
+	for (int i = 0; i < 2; i++) {
+
+		Vector3 color_x00 = color[i][0].linear_interpolate(color[i][1], pos_fract[i].x);
+		Vector3 color_xy0 = color[i][2].linear_interpolate(color[i][3], pos_fract[i].x);
+		Vector3 blend_z0 = color_x00.linear_interpolate(color_xy0, pos_fract[i].y);
+
+		Vector3 color_x0z = color[i][4].linear_interpolate(color[i][5], pos_fract[i].x);
+		Vector3 color_xyz = color[i][6].linear_interpolate(color[i][7], pos_fract[i].x);
+		Vector3 blend_z1 = color_x0z.linear_interpolate(color_xyz, pos_fract[i].y);
+
+		color_interp[i] = blend_z0.linear_interpolate(blend_z1, pos_fract[i].z);
+
+		float alpha_x00 = Math::lerp(alpha[i][0], alpha[i][1], pos_fract[i].x);
+		float alpha_xy0 = Math::lerp(alpha[i][2], alpha[i][3], pos_fract[i].x);
+		float alpha_z0 = Math::lerp(alpha_x00, alpha_xy0, pos_fract[i].y);
+
+		float alpha_x0z = Math::lerp(alpha[i][4], alpha[i][5], pos_fract[i].x);
+		float alpha_xyz = Math::lerp(alpha[i][6], alpha[i][7], pos_fract[i].x);
+		float alpha_z1 = Math::lerp(alpha_x0z, alpha_xyz, pos_fract[i].y);
+
+		alpha_interp[i] = Math::lerp(alpha_z0, alpha_z1, pos_fract[i].z);
+	}
+
+	r_color = color_interp[0].linear_interpolate(color_interp[1], level_filter);
+	r_alpha = Math::lerp(alpha_interp[0], alpha_interp[1], level_filter);
+
+	//	print_line("pos: " + p_posf + " level " + rtos(p_level) + " down to " + itos(target_level) + "." + rtos(level_filter) + " color " + r_color + " alpha " + rtos(r_alpha));
+}
+
+Vector3 VoxelLightBaker::_voxel_cone_trace(const Vector3 &p_pos, const Vector3 &p_normal, float p_aperture) {
+
+	float bias = 2.5;
+	float max_distance = (Vector3(1, 1, 1) * (1 << (cell_subdiv - 1))).length();
+
+	float dist = bias;
+	float alpha = 0.0;
+	Vector3 color;
+
+	Vector3 scolor;
+	float salpha;
+
+	while (dist < max_distance && alpha < 0.95) {
+		float diameter = MAX(1.0, 2.0 * p_aperture * dist);
+		//print_line("VCT: pos " + (p_pos + dist * p_normal) + " dist " + rtos(dist) + " mipmap " + rtos(log2(diameter)) + " alpha " + rtos(alpha));
+		//Plane scolor = textureLod(probe, (pos + dist * direction) * cell_size, log2(diameter) );
+		_sample_baked_octree_filtered_and_anisotropic(p_pos + dist * p_normal, p_normal, log2(diameter), scolor, salpha);
+		float a = (1.0 - alpha);
+		color += scolor * a;
+		alpha += a * salpha;
+		dist += diameter * 0.5;
+	}
+
+	/*if (blend_ambient) {
+		color.rgb = mix(ambient,color.rgb,min(1.0,alpha/0.95));
+	}*/
+
+	return color;
+}
+
+Vector3 VoxelLightBaker::_compute_pixel_light_at_pos(const Vector3 &p_pos, const Vector3 &p_normal) {
+
+	//find arbitrary tangent and bitangent, then build a matrix
+	Vector3 v0 = Math::abs(p_normal.z) < 0.999 ? Vector3(0, 0, 1) : Vector3(0, 1, 0);
+	Vector3 tangent = v0.cross(p_normal).normalized();
+	Vector3 bitangent = tangent.cross(p_normal).normalized();
+	Basis normal_xform = Basis(tangent, bitangent, p_normal).transposed();
+
+	//	print_line("normal xform: " + normal_xform);
+	const Vector3 *cone_dirs;
+	const float *cone_weights;
+	int cone_dir_count;
+	float cone_aperture;
+
+	switch (bake_quality) {
+		case BAKE_QUALITY_LOW: {
+			//default quality
+			static const Vector3 dirs[4] = {
+				Vector3(0.707107, 0, 0.707107),
+				Vector3(0, 0.707107, 0.707107),
+				Vector3(-0.707107, 0, 0.707107),
+				Vector3(0, -0.707107, 0.707107)
+			};
+
+			static const float weights[4] = { 0.25, 0.25, 0.25, 0.25 };
+
+			cone_dirs = dirs;
+			cone_dir_count = 4;
+			cone_aperture = 1.0; // tan(angle) 90 degrees
+			cone_weights = weights;
+		} break;
+		case BAKE_QUALITY_MEDIUM: {
+			//default quality
+			static const Vector3 dirs[6] = {
+				Vector3(0, 0, 1),
+				Vector3(0.866025, 0, 0.5),
+				Vector3(0.267617, 0.823639, 0.5),
+				Vector3(-0.700629, 0.509037, 0.5),
+				Vector3(-0.700629, -0.509037, 0.5),
+				Vector3(0.267617, -0.823639, 0.5)
+			};
+			static const float weights[6] = { 0.25, 0.15, 0.15, 0.15, 0.15, 0.15 };
+			//
+			cone_dirs = dirs;
+			cone_dir_count = 6;
+			cone_aperture = 0.577; // tan(angle) 60 degrees
+			cone_weights = weights;
+		} break;
+		case BAKE_QUALITY_HIGH: {
+
+			//high qualily
+			static const Vector3 dirs[10] = {
+				Vector3(0.8781648411741658, 0.0, 0.478358141694643),
+				Vector3(0.5369754325592234, 0.6794204427701518, 0.5000452447267606),
+				Vector3(-0.19849436573466497, 0.8429904390140635, 0.49996710542041645),
+				Vector3(-0.7856196499811189, 0.3639120321329737, 0.5003696617825604),
+				Vector3(-0.7856196499811189, -0.3639120321329737, 0.5003696617825604),
+				Vector3(-0.19849436573466497, -0.8429904390140635, 0.49996710542041645),
+				Vector3(0.5369754325592234, -0.6794204427701518, 0.5000452447267606),
+				Vector3(-0.4451656858129485, 0.0, 0.8954482185892644),
+				Vector3(0.19124006749743122, 0.39355745585016605, 0.8991883926788214),
+				Vector3(0.19124006749743122, -0.39355745585016605, 0.8991883926788214),
+			};
+			static const float weights[10] = { 0.08571, 0.08571, 0.08571, 0.08571, 0.08571, 0.08571, 0.08571, 0.133333, 0.133333, 0.13333 };
+			cone_dirs = dirs;
+			cone_dir_count = 10;
+			cone_aperture = 0.404; // tan(angle) 45 degrees
+			cone_weights = weights;
+		} break;
+	}
+
+	Vector3 accum;
+
+	for (int i = 0; i < cone_dir_count; i++) {
+		//	if (i > 0)
+		//		continue;
+		Vector3 dir = normal_xform.xform(cone_dirs[i]).normalized(); //normal may not completely correct when transformed to cell
+		//print_line("direction: " + dir);
+		accum += _voxel_cone_trace(p_pos, dir, cone_aperture) * cone_weights[i];
+	}
+
+	return accum;
+}
+
+Vector3 VoxelLightBaker::_compute_ray_trace_at_pos(const Vector3 &p_pos, const Vector3 &p_normal) {
+
+	int samples_per_quality[3] = { 48, 128, 512 };
+
+	int samples = samples_per_quality[bake_quality];
+
+	//create a basis in Z
+	Vector3 v0 = Math::abs(p_normal.z) < 0.999 ? Vector3(0, 0, 1) : Vector3(0, 1, 0);
+	Vector3 tangent = v0.cross(p_normal).normalized();
+	Vector3 bitangent = tangent.cross(p_normal).normalized();
+	Basis normal_xform = Basis(tangent, bitangent, p_normal).transposed();
+
+	float bias = 1.5;
+	int max_level = cell_subdiv - 1;
+	int size = 1 << max_level;
+
+	Vector3 accum;
+	float spread = Math::deg2rad(80.0);
+
+	const Light *light = bake_light.ptr();
+	const Cell *cells = bake_cells.ptr();
+
+	for (int i = 0; i < samples; i++) {
+
+		float random_angle1 = (((rand() % 65535) / 65535.0) * 2.0 - 1.0) * spread;
+		Vector3 axis(0, sin(random_angle1), cos(random_angle1));
+		float random_angle2 = ((rand() % 65535) / 65535.0) * Math_PI * 2.0;
+		Basis rot(Vector3(0, 0, 1), random_angle2);
+		axis = rot.xform(axis);
+
+		Vector3 direction = normal_xform.xform(axis).normalized();
+
+		Vector3 pos = p_pos + Vector3(0.5, 0.5, 0.5) + direction * bias;
+
+		Vector3 advance = direction * _get_normal_advance(direction);
+
+		uint32_t cell = CHILD_EMPTY;
+
+		while (cell == CHILD_EMPTY) {
+
+			int x = int(pos.x);
+			int y = int(pos.y);
+			int z = int(pos.z);
+
+			int ofs_x = 0;
+			int ofs_y = 0;
+			int ofs_z = 0;
+			int half = size / 2;
+
+			if (x < 0 || x >= size)
+				break;
+			if (y < 0 || y >= size)
+				break;
+			if (z < 0 || z >= size)
+				break;
+
+			//int level_limit = max_level;
+
+			cell = 0; //start from root
+			for (int i = 0; i < max_level; i++) {
+
+				const Cell *bc = &cells[cell];
+
+				int child = 0;
+				if (x >= ofs_x + half) {
+					child |= 1;
+					ofs_x += half;
+				}
+				if (y >= ofs_y + half) {
+					child |= 2;
+					ofs_y += half;
+				}
+				if (z >= ofs_z + half) {
+					child |= 4;
+					ofs_z += half;
+				}
+
+				cell = bc->childs[child];
+				if (cell == CHILD_EMPTY)
+					break;
+
+				half >>= 1;
+			}
+
+			pos += advance;
+		}
+
+		if (cell != CHILD_EMPTY) {
+			for (int i = 0; i < 6; i++) {
+				//anisotropic read light
+				float amount = direction.dot(aniso_normal[i]);
+				if (amount < 0)
+					amount = 0;
+				accum.x += light[cell].accum[i][0] * amount;
+				accum.y += light[cell].accum[i][1] * amount;
+				accum.z += light[cell].accum[i][2] * amount;
+			}
+		}
+	}
+
+	return accum / samples;
+}
+
+Error VoxelLightBaker::make_lightmap(const Transform &p_xform, Ref<Mesh> &p_mesh, LightMapData &r_lightmap, bool (*p_bake_time_func)(void *, float, float), void *p_bake_time_ud) {
+
+	//transfer light information to a lightmap
+	Ref<Mesh> mesh = p_mesh;
+
+	int width = mesh->get_lightmap_size_hint().x;
+	int height = mesh->get_lightmap_size_hint().y;
+
+	//step 1 - create lightmap
+	Vector<LightMap> lightmap;
+	lightmap.resize(width * height);
+
+	Transform xform = to_cell_space * p_xform;
+
+	//step 2 plot faces to lightmap
+	for (int i = 0; i < mesh->get_surface_count(); i++) {
+		Array arrays = mesh->surface_get_arrays(i);
+		PoolVector<Vector3> vertices = arrays[Mesh::ARRAY_VERTEX];
+		PoolVector<Vector3> normals = arrays[Mesh::ARRAY_NORMAL];
+		PoolVector<Vector2> uv2 = arrays[Mesh::ARRAY_TEX_UV2];
+		PoolVector<int> indices = arrays[Mesh::ARRAY_INDEX];
+
+		ERR_FAIL_COND_V(vertices.size() == 0, ERR_INVALID_PARAMETER);
+		ERR_FAIL_COND_V(normals.size() == 0, ERR_INVALID_PARAMETER);
+		ERR_FAIL_COND_V(uv2.size() == 0, ERR_INVALID_PARAMETER);
+
+		int vc = vertices.size();
+		PoolVector<Vector3>::Read vr = vertices.read();
+		PoolVector<Vector3>::Read nr = normals.read();
+		PoolVector<Vector2>::Read u2r = uv2.read();
+		PoolVector<int>::Read ir;
+		int ic = 0;
+
+		if (indices.size()) {
+			ic = indices.size();
+			ir = indices.read();
+		}
+
+		int faces = ic ? ic / 3 : vc / 3;
+		for (int i = 0; i < faces; i++) {
+			Vector3 vertex[3];
+			Vector3 normal[3];
+			Vector2 uv[3];
+			for (int j = 0; j < 3; j++) {
+				int idx = ic ? ir[i * 3 + j] : i * 3 + j;
+				vertex[j] = xform.xform(vr[idx]);
+				normal[j] = xform.basis.xform(nr[idx]).normalized();
+				uv[j] = u2r[idx];
+			}
+
+			_plot_triangle(uv, vertex, normal, lightmap.ptrw(), width, height);
+		}
+	}
+	//step 3 perform voxel cone trace on lightmap pixels
+
+	{
+		LightMap *lightmap_ptr = lightmap.ptrw();
+		uint64_t begin_time = OS::get_singleton()->get_ticks_usec();
+		volatile int lines = 0;
+
+		for (int i = 0; i < height; i++) {
+
+		//print_line("bake line " + itos(i) + " / " + itos(height));
+#ifdef _OPENMP
+#pragma omp parallel for
+#endif
+			for (int j = 0; j < width; j++) {
+
+				//if (i == 125 && j == 280) {
+
+				LightMap *pixel = &lightmap_ptr[i * width + j];
+				if (pixel->pos == Vector3())
+					continue; //unused, skipe
+
+				//print_line("pos: " + pixel->pos + " normal " + pixel->normal);
+				switch (bake_mode) {
+					case BAKE_MODE_CONE_TRACE: {
+						pixel->light = _compute_pixel_light_at_pos(pixel->pos, pixel->normal) * energy;
+					} break;
+					case BAKE_MODE_RAY_TRACE: {
+						pixel->light = _compute_ray_trace_at_pos(pixel->pos, pixel->normal) * energy;
+					} break;
+						//	pixel->light = Vector3(1, 1, 1);
+						//}
+				}
+			}
+
+			lines = MAX(lines, i); //for multithread
+			if (p_bake_time_func) {
+				uint64_t elapsed = OS::get_singleton()->get_ticks_usec() - begin_time;
+				float elapsed_sec = double(elapsed) / 1000000.0;
+				float remaining = lines < 1 ? 0 : (elapsed_sec / lines) * (height - lines - 1);
+				if (p_bake_time_func(p_bake_time_ud, remaining, lines / float(height))) {
+					return ERR_SKIP;
+				}
+			}
+		}
+
+		if (bake_mode == BAKE_MODE_RAY_TRACE) {
+			//blur
+			print_line("bluring, use pos for separatable copy");
+			//gauss kernel, 7 step sigma 2
+			static const float gauss_kernel[4] = { 0.214607, 0.189879, 0.131514, 0.071303 };
+			//horizontal pass
+			for (int i = 0; i < height; i++) {
+				for (int j = 0; j < width; j++) {
+					if (lightmap_ptr[i * width + j].normal == Vector3()) {
+						continue; //empty
+					}
+					float gauss_sum = gauss_kernel[0];
+					Vector3 accum = lightmap_ptr[i * width + j].light * gauss_kernel[0];
+					for (int k = 1; k < 4; k++) {
+						int new_x = j + k;
+						if (new_x >= width || lightmap_ptr[i * width + new_x].normal == Vector3())
+							break;
+						gauss_sum += gauss_kernel[k];
+						accum += lightmap_ptr[i * width + new_x].light * gauss_kernel[k];
+					}
+					for (int k = 1; k < 4; k++) {
+						int new_x = j - k;
+						if (new_x < 0 || lightmap_ptr[i * width + new_x].normal == Vector3())
+							break;
+						gauss_sum += gauss_kernel[k];
+						accum += lightmap_ptr[i * width + new_x].light * gauss_kernel[k];
+					}
+
+					lightmap_ptr[i * width + j].pos = accum /= gauss_sum;
+				}
+			}
+			//vertical pass
+			for (int i = 0; i < height; i++) {
+				for (int j = 0; j < width; j++) {
+					if (lightmap_ptr[i * width + j].normal == Vector3())
+						continue; //empty, dont write over it anyway
+					float gauss_sum = gauss_kernel[0];
+					Vector3 accum = lightmap_ptr[i * width + j].pos * gauss_kernel[0];
+					for (int k = 1; k < 4; k++) {
+						int new_y = i + k;
+						if (new_y >= height || lightmap_ptr[new_y * width + j].normal == Vector3())
+							break;
+						gauss_sum += gauss_kernel[k];
+						accum += lightmap_ptr[new_y * width + j].pos * gauss_kernel[k];
+					}
+					for (int k = 1; k < 4; k++) {
+						int new_y = i - k;
+						if (new_y < 0 || lightmap_ptr[new_y * width + j].normal == Vector3())
+							break;
+						gauss_sum += gauss_kernel[k];
+						accum += lightmap_ptr[new_y * width + j].pos * gauss_kernel[k];
+					}
+
+					lightmap_ptr[i * width + j].light = accum /= gauss_sum;
+				}
+			}
+		}
+
+		//add directional light (do this after blur)
+		{
+			LightMap *lightmap_ptr = lightmap.ptrw();
+			const Cell *cells = bake_cells.ptr();
+			const Light *light = bake_light.ptr();
+
+			for (int i = 0; i < height; i++) {
+
+			//print_line("bake line " + itos(i) + " / " + itos(height));
+#ifdef _OPENMP
+#pragma omp parallel for
+#endif
+				for (int j = 0; j < width; j++) {
+
+					//if (i == 125 && j == 280) {
+
+					LightMap *pixel = &lightmap_ptr[i * width + j];
+					if (pixel->pos == Vector3())
+						continue; //unused, skipe
+
+					int x = int(pixel->pos.x) - 1;
+					int y = int(pixel->pos.y) - 1;
+					int z = int(pixel->pos.z) - 1;
+					Color accum;
+					int size = 1 << (cell_subdiv - 1);
+
+					int found = 0;
+
+					for (int k = 0; k < 8; k++) {
+
+						int ofs_x = x;
+						int ofs_y = y;
+						int ofs_z = z;
+
+						if (k & 1)
+							ofs_x++;
+						if (k & 2)
+							ofs_y++;
+						if (k & 4)
+							ofs_z++;
+
+						if (x < 0 || x >= size)
+							continue;
+						if (y < 0 || y >= size)
+							continue;
+						if (z < 0 || z >= size)
+							continue;
+
+						uint32_t cell = _find_cell_at_pos(cells, ofs_x, ofs_y, ofs_z);
+
+						if (cell == CHILD_EMPTY)
+							continue;
+						for (int l = 0; l < 6; l++) {
+							float s = pixel->normal.dot(aniso_normal[l]);
+							if (s < 0)
+								s = 0;
+							accum.r += light[cell].direct_accum[l][0] * s;
+							accum.g += light[cell].direct_accum[l][1] * s;
+							accum.b += light[cell].direct_accum[l][2] * s;
+						}
+						found++;
+					}
+					if (found) {
+						accum /= found;
+						pixel->light.x += accum.r;
+						pixel->light.y += accum.g;
+						pixel->light.z += accum.b;
+					}
+				}
+			}
+		}
+
+		{
+			//fill gaps with neighbour vertices to avoid filter fades to black on edges
+
+			for (int i = 0; i < height; i++) {
+				for (int j = 0; j < width; j++) {
+					if (lightmap_ptr[i * width + j].normal != Vector3()) {
+						continue; //filled, skip
+					}
+
+					//this can't be made separatable..
+
+					int closest_i = -1, closest_j = 1;
+					float closest_dist = 1e20;
+
+					const int margin = 3;
+					for (int y = i - margin; y <= i + margin; y++) {
+						for (int x = j - margin; x <= j + margin; x++) {
+
+							if (x == j && y == i)
+								continue;
+							if (x < 0 || x >= width)
+								continue;
+							if (y < 0 || y >= height)
+								continue;
+							if (lightmap_ptr[y * width + x].normal == Vector3())
+								continue; //also ensures that blitted stuff is not reused
+
+							float dist = Vector2(i - y, j - x).length();
+							if (dist > closest_dist)
+								continue;
+
+							closest_dist = dist;
+							closest_i = y;
+							closest_j = x;
+						}
+					}
+
+					if (closest_i != -1) {
+						lightmap_ptr[i * width + j].light = lightmap_ptr[closest_i * width + closest_j].light;
+					}
+				}
+			}
+		}
+
+		{
+			//fill the lightmap data
+			r_lightmap.width = width;
+			r_lightmap.height = height;
+			r_lightmap.light.resize(lightmap.size() * 3);
+			PoolVector<float>::Write w = r_lightmap.light.write();
+			for (int i = 0; i < lightmap.size(); i++) {
+				w[i * 3 + 0] = lightmap[i].light.x;
+				w[i * 3 + 1] = lightmap[i].light.y;
+				w[i * 3 + 2] = lightmap[i].light.z;
+			}
+		}
+
+#if 0
+		{
+			PoolVector<uint8_t> img;
+			int ls = lightmap.size();
+			img.resize(ls * 3);
+			{
+				PoolVector<uint8_t>::Write w = img.write();
+				for (int i = 0; i < ls; i++) {
+					w[i * 3 + 0] = CLAMP(lightmap_ptr[i].light.x * 255, 0, 255);
+					w[i * 3 + 1] = CLAMP(lightmap_ptr[i].light.y * 255, 0, 255);
+					w[i * 3 + 2] = CLAMP(lightmap_ptr[i].light.z * 255, 0, 255);
+					//w[i * 3 + 0] = CLAMP(lightmap_ptr[i].normal.x * 255, 0, 255);
+					//w[i * 3 + 1] = CLAMP(lightmap_ptr[i].normal.y * 255, 0, 255);
+					//w[i * 3 + 2] = CLAMP(lightmap_ptr[i].normal.z * 255, 0, 255);
+					//w[i * 3 + 0] = CLAMP(lightmap_ptr[i].pos.x / (1 << (cell_subdiv - 1)) * 255, 0, 255);
+					//w[i * 3 + 1] = CLAMP(lightmap_ptr[i].pos.y / (1 << (cell_subdiv - 1)) * 255, 0, 255);
+					//w[i * 3 + 2] = CLAMP(lightmap_ptr[i].pos.z / (1 << (cell_subdiv - 1)) * 255, 0, 255);
+				}
+			}
+
+			Ref<Image> image;
+			image.instance();
+			image->create(width, height, false, Image::FORMAT_RGB8, img);
+
+			String name = p_mesh->get_name();
+			if (name == "") {
+				name = "Mesh" + itos(p_mesh->get_instance_id());
+			}
+			image->save_png(name + ".png");
+		}
+#endif
+	}
+
+	return OK;
+}
+
+void VoxelLightBaker::begin_bake(int p_subdiv, const AABB &p_bounds) {
+
+	original_bounds = p_bounds;
+	cell_subdiv = p_subdiv;
+	bake_cells.resize(1);
+	material_cache.clear();
+
+	//find out the actual real bounds, power of 2, which gets the highest subdivision
+	po2_bounds = p_bounds;
+	int longest_axis = po2_bounds.get_longest_axis_index();
+	axis_cell_size[longest_axis] = (1 << (cell_subdiv - 1));
+	leaf_voxel_count = 0;
+
+	for (int i = 0; i < 3; i++) {
+
+		if (i == longest_axis)
+			continue;
+
+		axis_cell_size[i] = axis_cell_size[longest_axis];
+		float axis_size = po2_bounds.size[longest_axis];
+
+		//shrink until fit subdiv
+		while (axis_size / 2.0 >= po2_bounds.size[i]) {
+			axis_size /= 2.0;
+			axis_cell_size[i] >>= 1;
+		}
+
+		po2_bounds.size[i] = po2_bounds.size[longest_axis];
+	}
+
+	Transform to_bounds;
+	to_bounds.basis.scale(Vector3(po2_bounds.size[longest_axis], po2_bounds.size[longest_axis], po2_bounds.size[longest_axis]));
+	to_bounds.origin = po2_bounds.position;
+
+	Transform to_grid;
+	to_grid.basis.scale(Vector3(axis_cell_size[longest_axis], axis_cell_size[longest_axis], axis_cell_size[longest_axis]));
+
+	to_cell_space = to_grid * to_bounds.affine_inverse();
+
+	cell_size = po2_bounds.size[longest_axis] / axis_cell_size[longest_axis];
+}
+
+void VoxelLightBaker::end_bake() {
+	_fixup_plot(0, 0);
+}
+
+//create the data for visual server
+
+PoolVector<int> VoxelLightBaker::create_gi_probe_data() {
+
+	PoolVector<int> data;
+
+	data.resize(16 + (8 + 1 + 1 + 1 + 1) * bake_cells.size()); //4 for header, rest for rest.
+
+	{
+		PoolVector<int>::Write w = data.write();
+
+		uint32_t *w32 = (uint32_t *)w.ptr();
+
+		w32[0] = 0; //version
+		w32[1] = cell_subdiv; //subdiv
+		w32[2] = axis_cell_size[0];
+		w32[3] = axis_cell_size[1];
+		w32[4] = axis_cell_size[2];
+		w32[5] = bake_cells.size();
+		w32[6] = leaf_voxel_count;
+
+		int ofs = 16;
+
+		for (int i = 0; i < bake_cells.size(); i++) {
+
+			for (int j = 0; j < 8; j++) {
+				w32[ofs++] = bake_cells[i].childs[j];
+			}
+
+			{ //albedo
+				uint32_t rgba = uint32_t(CLAMP(bake_cells[i].albedo[0] * 255.0, 0, 255)) << 16;
+				rgba |= uint32_t(CLAMP(bake_cells[i].albedo[1] * 255.0, 0, 255)) << 8;
+				rgba |= uint32_t(CLAMP(bake_cells[i].albedo[2] * 255.0, 0, 255)) << 0;
+
+				w32[ofs++] = rgba;
+			}
+			{ //emission
+
+				Vector3 e(bake_cells[i].emission[0], bake_cells[i].emission[1], bake_cells[i].emission[2]);
+				float l = e.length();
+				if (l > 0) {
+					e.normalize();
+					l = CLAMP(l / 8.0, 0, 1.0);
+				}
+
+				uint32_t em = uint32_t(CLAMP(e[0] * 255, 0, 255)) << 24;
+				em |= uint32_t(CLAMP(e[1] * 255, 0, 255)) << 16;
+				em |= uint32_t(CLAMP(e[2] * 255, 0, 255)) << 8;
+				em |= uint32_t(CLAMP(l * 255, 0, 255));
+
+				w32[ofs++] = em;
+			}
+
+			//w32[ofs++]=bake_cells[i].used_sides;
+			{ //normal
+
+				Vector3 n(bake_cells[i].normal[0], bake_cells[i].normal[1], bake_cells[i].normal[2]);
+				n = n * Vector3(0.5, 0.5, 0.5) + Vector3(0.5, 0.5, 0.5);
+				uint32_t norm = 0;
+
+				norm |= uint32_t(CLAMP(n.x * 255.0, 0, 255)) << 16;
+				norm |= uint32_t(CLAMP(n.y * 255.0, 0, 255)) << 8;
+				norm |= uint32_t(CLAMP(n.z * 255.0, 0, 255)) << 0;
+
+				w32[ofs++] = norm;
+			}
+
+			{
+				uint16_t alpha = CLAMP(uint32_t(bake_cells[i].alpha * 65535.0), 0, 65535);
+				uint16_t level = bake_cells[i].level;
+
+				w32[ofs++] = (uint32_t(level) << 16) | uint32_t(alpha);
+			}
+		}
+	}
+
+	return data;
+}
+
+void VoxelLightBaker::_debug_mesh(int p_idx, int p_level, const AABB &p_aabb, Ref<MultiMesh> &p_multimesh, int &idx, DebugMode p_mode) {
+
+	if (p_level == cell_subdiv - 1) {
+
+		Vector3 center = p_aabb.position + p_aabb.size * 0.5;
+		Transform xform;
+		xform.origin = center;
+		xform.basis.scale(p_aabb.size * 0.5);
+		p_multimesh->set_instance_transform(idx, xform);
+		Color col;
+		if (p_mode == DEBUG_ALBEDO) {
+			col = Color(bake_cells[p_idx].albedo[0], bake_cells[p_idx].albedo[1], bake_cells[p_idx].albedo[2]);
+		} else if (p_mode == DEBUG_LIGHT) {
+			for (int i = 0; i < 6; i++) {
+				col.r += bake_light[p_idx].accum[i][0];
+				col.g += bake_light[p_idx].accum[i][1];
+				col.b += bake_light[p_idx].accum[i][2];
+				col.r += bake_light[p_idx].direct_accum[i][0];
+				col.g += bake_light[p_idx].direct_accum[i][1];
+				col.b += bake_light[p_idx].direct_accum[i][2];
+			}
+		}
+		//Color col = Color(bake_cells[p_idx].emission[0], bake_cells[p_idx].emission[1], bake_cells[p_idx].emission[2]);
+		p_multimesh->set_instance_color(idx, col);
+
+		idx++;
+
+	} else {
+
+		for (int i = 0; i < 8; i++) {
+
+			uint32_t child = bake_cells[p_idx].childs[i];
+
+			if (child == CHILD_EMPTY || child >= max_original_cells)
+				continue;
+
+			AABB aabb = p_aabb;
+			aabb.size *= 0.5;
+
+			if (i & 1)
+				aabb.position.x += aabb.size.x;
+			if (i & 2)
+				aabb.position.y += aabb.size.y;
+			if (i & 4)
+				aabb.position.z += aabb.size.z;
+
+			_debug_mesh(bake_cells[p_idx].childs[i], p_level + 1, aabb, p_multimesh, idx, p_mode);
+		}
+	}
+}
+
+Ref<MultiMesh> VoxelLightBaker::create_debug_multimesh(DebugMode p_mode) {
+
+	Ref<MultiMesh> mm;
+
+	ERR_FAIL_COND_V(p_mode == DEBUG_LIGHT && bake_light.size() == 0, mm);
+	mm.instance();
+
+	mm->set_transform_format(MultiMesh::TRANSFORM_3D);
+	mm->set_color_format(MultiMesh::COLOR_8BIT);
+	print_line("leaf voxels: " + itos(leaf_voxel_count));
+	mm->set_instance_count(leaf_voxel_count);
+
+	Ref<ArrayMesh> mesh;
+	mesh.instance();
+
+	{
+		Array arr;
+		arr.resize(Mesh::ARRAY_MAX);
+
+		PoolVector<Vector3> vertices;
+		PoolVector<Color> colors;
+
+		int vtx_idx = 0;
+#define ADD_VTX(m_idx)                      \
+	;                                       \
+	vertices.push_back(face_points[m_idx]); \
+	colors.push_back(Color(1, 1, 1, 1));    \
+	vtx_idx++;
+
+		for (int i = 0; i < 6; i++) {
+
+			Vector3 face_points[4];
+
+			for (int j = 0; j < 4; j++) {
+
+				float v[3];
+				v[0] = 1.0;
+				v[1] = 1 - 2 * ((j >> 1) & 1);
+				v[2] = v[1] * (1 - 2 * (j & 1));
+
+				for (int k = 0; k < 3; k++) {
+
+					if (i < 3)
+						face_points[j][(i + k) % 3] = v[k] * (i >= 3 ? -1 : 1);
+					else
+						face_points[3 - j][(i + k) % 3] = v[k] * (i >= 3 ? -1 : 1);
+				}
+			}
+
+			//tri 1
+			ADD_VTX(0);
+			ADD_VTX(1);
+			ADD_VTX(2);
+			//tri 2
+			ADD_VTX(2);
+			ADD_VTX(3);
+			ADD_VTX(0);
+		}
+
+		arr[Mesh::ARRAY_VERTEX] = vertices;
+		arr[Mesh::ARRAY_COLOR] = colors;
+		mesh->add_surface_from_arrays(Mesh::PRIMITIVE_TRIANGLES, arr);
+	}
+
+	{
+		Ref<SpatialMaterial> fsm;
+		fsm.instance();
+		fsm->set_flag(SpatialMaterial::FLAG_SRGB_VERTEX_COLOR, true);
+		fsm->set_flag(SpatialMaterial::FLAG_ALBEDO_FROM_VERTEX_COLOR, true);
+		fsm->set_flag(SpatialMaterial::FLAG_UNSHADED, true);
+		fsm->set_albedo(Color(1, 1, 1, 1));
+
+		mesh->surface_set_material(0, fsm);
+	}
+
+	mm->set_mesh(mesh);
+
+	int idx = 0;
+	_debug_mesh(0, 0, po2_bounds, mm, idx, p_mode);
+
+	return mm;
+}
+
+struct VoxelLightBakerOctree {
+
+	enum {
+		CHILD_EMPTY = 0xFFFFFFFF
+	};
+
+	uint16_t light[6][3]; //anisotropic light
+	float alpha;
+	uint32_t children[8];
+};
+
+PoolVector<uint8_t> VoxelLightBaker::create_capture_octree(int p_subdiv) {
+
+	p_subdiv = MIN(p_subdiv, cell_subdiv); // use the smaller one
+
+	Vector<uint32_t> remap;
+	int bc = bake_cells.size();
+	remap.resize(bc);
+	Vector<uint32_t> demap;
+
+	int new_size = 0;
+	for (int i = 0; i < bc; i++) {
+		uint32_t c = CHILD_EMPTY;
+		if (bake_cells[i].level < p_subdiv) {
+			c = new_size;
+			new_size++;
+			demap.push_back(i);
+		}
+		remap[i] = c;
+	}
+
+	Vector<VoxelLightBakerOctree> octree;
+	octree.resize(new_size);
+
+	for (int i = 0; i < new_size; i++) {
+		octree[i].alpha = bake_cells[demap[i]].alpha;
+		for (int j = 0; j < 6; j++) {
+			for (int k = 0; k < 3; k++) {
+				float l = bake_light[demap[i]].accum[j][k]; //add anisotropic light
+				l += bake_cells[demap[i]].emission[k]; //add emission
+				octree[i].light[j][k] = CLAMP(l * 1024, 0, 65535); //give two more bits to octree
+			}
+		}
+
+		for (int j = 0; j < 8; j++) {
+			uint32_t child = bake_cells[demap[i]].childs[j];
+			octree[i].children[j] = child == CHILD_EMPTY ? CHILD_EMPTY : remap[child];
+		}
+	}
+
+	PoolVector<uint8_t> ret;
+	int ret_bytes = octree.size() * sizeof(VoxelLightBakerOctree);
+	ret.resize(ret_bytes);
+	{
+		PoolVector<uint8_t>::Write w = ret.write();
+		copymem(w.ptr(), octree.ptr(), ret_bytes);
+	}
+
+	return ret;
+}
+
+float VoxelLightBaker::get_cell_size() const {
+	return cell_size;
+}
+
+Transform VoxelLightBaker::get_to_cell_space_xform() const {
+	return to_cell_space;
+}
+VoxelLightBaker::VoxelLightBaker() {
+	color_scan_cell_width = 4;
+	bake_texture_size = 128;
+	propagation = 0.85;
+	energy = 1.0;
+}

+ 148 - 0
scene/3d/voxel_light_baker.h

@@ -0,0 +1,148 @@
+#ifndef VOXEL_LIGHT_BAKER_H
+#define VOXEL_LIGHT_BAKER_H
+
+#include "scene/3d/mesh_instance.h"
+#include "scene/resources/multimesh.h"
+
+class VoxelLightBaker {
+public:
+	enum DebugMode {
+		DEBUG_ALBEDO,
+		DEBUG_LIGHT
+	};
+
+	enum BakeQuality {
+		BAKE_QUALITY_LOW,
+		BAKE_QUALITY_MEDIUM,
+		BAKE_QUALITY_HIGH
+	};
+
+	enum BakeMode {
+		BAKE_MODE_CONE_TRACE,
+		BAKE_MODE_RAY_TRACE,
+	};
+
+private:
+	enum {
+		CHILD_EMPTY = 0xFFFFFFFF
+
+	};
+
+	struct Cell {
+
+		uint32_t childs[8];
+		float albedo[3]; //albedo in RGB24
+		float emission[3]; //accumulated light in 16:16 fixed point (needs to be integer for moving lights fast)
+		float normal[3];
+		uint32_t used_sides;
+		float alpha; //used for upsampling
+		int level;
+
+		Cell() {
+			for (int i = 0; i < 8; i++) {
+				childs[i] = CHILD_EMPTY;
+			}
+
+			for (int i = 0; i < 3; i++) {
+				emission[i] = 0;
+				albedo[i] = 0;
+				normal[i] = 0;
+			}
+			alpha = 0;
+			used_sides = 0;
+			level = 0;
+		}
+	};
+
+	Vector<Cell> bake_cells;
+	int cell_subdiv;
+
+	struct Light {
+		int x, y, z;
+		float accum[6][3]; //rgb anisotropic
+		float direct_accum[6][3]; //for direct bake
+		int next_leaf;
+	};
+
+	int first_leaf;
+
+	Vector<Light> bake_light;
+
+	struct MaterialCache {
+		//128x128 textures
+		Vector<Color> albedo;
+		Vector<Color> emission;
+	};
+
+	Map<Ref<Material>, MaterialCache> material_cache;
+	int leaf_voxel_count;
+	bool direct_lights_baked;
+
+	AABB original_bounds;
+	AABB po2_bounds;
+	int axis_cell_size[3];
+
+	Transform to_cell_space;
+
+	int color_scan_cell_width;
+	int bake_texture_size;
+	float cell_size;
+	float propagation;
+	float energy;
+
+	BakeQuality bake_quality;
+	BakeMode bake_mode;
+
+	int max_original_cells;
+
+	void _init_light_plot(int p_idx, int p_level, int p_x, int p_y, int p_z, uint32_t p_parent);
+
+	Vector<Color> _get_bake_texture(Ref<Image> p_image, const Color &p_color_mul, const Color &p_color_add);
+	MaterialCache _get_material_cache(Ref<Material> p_material);
+	void _plot_face(int p_idx, int p_level, int p_x, int p_y, int p_z, const Vector3 *p_vtx, const Vector2 *p_uv, const MaterialCache &p_material, const AABB &p_aabb);
+	void _fixup_plot(int p_idx, int p_level);
+	void _debug_mesh(int p_idx, int p_level, const AABB &p_aabb, Ref<MultiMesh> &p_multimesh, int &idx, DebugMode p_mode);
+	void _check_init_light();
+
+	uint32_t _find_cell_at_pos(const Cell *cells, int x, int y, int z);
+
+	struct LightMap {
+		Vector3 light;
+		Vector3 pos;
+		Vector3 normal;
+	};
+
+	void _plot_triangle(Vector2 *vertices, Vector3 *positions, Vector3 *normals, LightMap *pixels, int width, int height);
+
+	_FORCE_INLINE_ void _sample_baked_octree_filtered_and_anisotropic(const Vector3 &p_posf, const Vector3 &p_direction, float p_level, Vector3 &r_color, float &r_alpha);
+	_FORCE_INLINE_ Vector3 _voxel_cone_trace(const Vector3 &p_pos, const Vector3 &p_normal, float p_aperture);
+	_FORCE_INLINE_ Vector3 _compute_pixel_light_at_pos(const Vector3 &p_pos, const Vector3 &p_normal);
+	_FORCE_INLINE_ Vector3 _compute_ray_trace_at_pos(const Vector3 &p_pos, const Vector3 &p_normal);
+
+public:
+	void begin_bake(int p_subdiv, const AABB &p_bounds);
+	void plot_mesh(const Transform &p_xform, Ref<Mesh> &p_mesh, const Vector<Ref<Material> > &p_materials, const Ref<Material> &p_override_material);
+	void begin_bake_light(BakeQuality p_quality = BAKE_QUALITY_MEDIUM, BakeMode p_bake_mode = BAKE_MODE_CONE_TRACE, float p_propagation = 0.85, float p_energy = 1);
+	void plot_light_directional(const Vector3 &p_direction, const Color &p_color, float p_energy, float p_indirect_energy, bool p_direct);
+	void plot_light_omni(const Vector3 &p_pos, const Color &p_color, float p_energy, float p_indirect_energy, float p_radius, float p_attenutation, bool p_direct);
+	void plot_light_spot(const Vector3 &p_pos, const Vector3 &p_axis, const Color &p_color, float p_energy, float p_indirect_energy, float p_radius, float p_attenutation, float p_spot_angle, float p_spot_attenuation, bool p_direct);
+	void end_bake();
+
+	struct LightMapData {
+		int width;
+		int height;
+		PoolVector<float> light;
+	};
+
+	Error make_lightmap(const Transform &p_xform, Ref<Mesh> &p_mesh, LightMapData &r_lightmap, bool (*p_bake_time_func)(void *, float, float) = NULL, void *p_bake_time_ud = NULL);
+
+	PoolVector<int> create_gi_probe_data();
+	Ref<MultiMesh> create_debug_multimesh(DebugMode p_mode = DEBUG_ALBEDO);
+	PoolVector<uint8_t> create_capture_octree(int p_subdiv);
+
+	float get_cell_size() const;
+	Transform get_to_cell_space_xform() const;
+	VoxelLightBaker();
+};
+
+#endif // VOXEL_LIGHT_BAKER_H

+ 1 - 1
scene/main/node.cpp

@@ -177,8 +177,8 @@ void Node::_propagate_ready() {
 	}
 	}
 	data.blocked--;
 	data.blocked--;
 	if (data.ready_first) {
 	if (data.ready_first) {
-		notification(NOTIFICATION_READY);
 		data.ready_first = false;
 		data.ready_first = false;
+		notification(NOTIFICATION_READY);
 	}
 	}
 }
 }
 
 

+ 3 - 0
scene/register_scene_types.cpp

@@ -160,6 +160,7 @@
 #include "scene/3d/area.h"
 #include "scene/3d/area.h"
 #include "scene/3d/arvr_nodes.h"
 #include "scene/3d/arvr_nodes.h"
 #include "scene/3d/audio_stream_player_3d.h"
 #include "scene/3d/audio_stream_player_3d.h"
+#include "scene/3d/baked_lightmap.h"
 #include "scene/3d/bone_attachment.h"
 #include "scene/3d/bone_attachment.h"
 #include "scene/3d/camera.h"
 #include "scene/3d/camera.h"
 #include "scene/3d/collision_polygon.h"
 #include "scene/3d/collision_polygon.h"
@@ -375,6 +376,8 @@ void register_scene_types() {
 	ClassDB::register_class<ReflectionProbe>();
 	ClassDB::register_class<ReflectionProbe>();
 	ClassDB::register_class<GIProbe>();
 	ClassDB::register_class<GIProbe>();
 	ClassDB::register_class<GIProbeData>();
 	ClassDB::register_class<GIProbeData>();
+	ClassDB::register_class<BakedLightmap>();
+	ClassDB::register_class<BakedLightmapData>();
 	ClassDB::register_class<AnimationTreePlayer>();
 	ClassDB::register_class<AnimationTreePlayer>();
 	ClassDB::register_class<Particles>();
 	ClassDB::register_class<Particles>();
 	ClassDB::register_class<Position3D>();
 	ClassDB::register_class<Position3D>();

+ 15 - 4
scene/resources/material.cpp

@@ -645,7 +645,7 @@ void SpatialMaterial::_update_shader() {
 		code += "\tvec2 base_uv = UV;\n";
 		code += "\tvec2 base_uv = UV;\n";
 	}
 	}
 
 
-	if ((features[FEATURE_DETAIL] && detail_uv == DETAIL_UV_2) || (features[FEATURE_AMBIENT_OCCLUSION] && flags[FLAG_AO_ON_UV2])) {
+	if ((features[FEATURE_DETAIL] && detail_uv == DETAIL_UV_2) || (features[FEATURE_AMBIENT_OCCLUSION] && flags[FLAG_AO_ON_UV2]) || (features[FEATURE_EMISSION] && flags[FLAG_EMISSION_ON_UV2])) {
 		code += "\tvec2 base_uv2 = UV2;\n";
 		code += "\tvec2 base_uv2 = UV2;\n";
 	}
 	}
 
 
@@ -729,11 +729,20 @@ void SpatialMaterial::_update_shader() {
 	}
 	}
 
 
 	if (features[FEATURE_EMISSION]) {
 	if (features[FEATURE_EMISSION]) {
-		if (flags[FLAG_UV1_USE_TRIPLANAR]) {
-			code += "\tvec3 emission_tex = triplanar_texture(texture_emission,uv1_power_normal,uv1_triplanar_pos).rgb;\n";
+		if (flags[FLAG_EMISSION_ON_UV2]) {
+			if (flags[FLAG_UV2_USE_TRIPLANAR]) {
+				code += "\tvec3 emission_tex = triplanar_texture(texture_emission,uv2_power_normal,uv2_triplanar_pos).rgb;\n";
+			} else {
+				code += "\tvec3 emission_tex = texture(texture_emission,base_uv2).rgb;\n";
+			}
 		} else {
 		} else {
-			code += "\tvec3 emission_tex = texture(texture_emission,base_uv).rgb;\n";
+			if (flags[FLAG_UV1_USE_TRIPLANAR]) {
+				code += "\tvec3 emission_tex = triplanar_texture(texture_emission,uv1_power_normal,uv1_triplanar_pos).rgb;\n";
+			} else {
+				code += "\tvec3 emission_tex = texture(texture_emission,base_uv).rgb;\n";
+			}
 		}
 		}
+
 		if (emission_op == EMISSION_OP_ADD) {
 		if (emission_op == EMISSION_OP_ADD) {
 			code += "\tEMISSION = (emission.rgb+emission_tex)*emission_energy;\n";
 			code += "\tEMISSION = (emission.rgb+emission_tex)*emission_energy;\n";
 		} else {
 		} else {
@@ -1892,6 +1901,7 @@ void SpatialMaterial::_bind_methods() {
 	ADD_PROPERTY(PropertyInfo(Variant::COLOR, "emission", PROPERTY_HINT_COLOR_NO_ALPHA), "set_emission", "get_emission");
 	ADD_PROPERTY(PropertyInfo(Variant::COLOR, "emission", PROPERTY_HINT_COLOR_NO_ALPHA), "set_emission", "get_emission");
 	ADD_PROPERTY(PropertyInfo(Variant::REAL, "emission_energy", PROPERTY_HINT_RANGE, "0,16,0.01"), "set_emission_energy", "get_emission_energy");
 	ADD_PROPERTY(PropertyInfo(Variant::REAL, "emission_energy", PROPERTY_HINT_RANGE, "0,16,0.01"), "set_emission_energy", "get_emission_energy");
 	ADD_PROPERTY(PropertyInfo(Variant::INT, "emission_operator", PROPERTY_HINT_ENUM, "Add,Multiply"), "set_emission_operator", "get_emission_operator");
 	ADD_PROPERTY(PropertyInfo(Variant::INT, "emission_operator", PROPERTY_HINT_ENUM, "Add,Multiply"), "set_emission_operator", "get_emission_operator");
+	ADD_PROPERTYI(PropertyInfo(Variant::BOOL, "emission_on_uv2"), "set_flag", "get_flag", FLAG_EMISSION_ON_UV2);
 	ADD_PROPERTYI(PropertyInfo(Variant::OBJECT, "emission_texture", PROPERTY_HINT_RESOURCE_TYPE, "Texture"), "set_texture", "get_texture", TEXTURE_EMISSION);
 	ADD_PROPERTYI(PropertyInfo(Variant::OBJECT, "emission_texture", PROPERTY_HINT_RESOURCE_TYPE, "Texture"), "set_texture", "get_texture", TEXTURE_EMISSION);
 
 
 	ADD_GROUP("NormalMap", "normal_");
 	ADD_GROUP("NormalMap", "normal_");
@@ -2034,6 +2044,7 @@ void SpatialMaterial::_bind_methods() {
 	BIND_ENUM_CONSTANT(FLAG_UV1_USE_TRIPLANAR);
 	BIND_ENUM_CONSTANT(FLAG_UV1_USE_TRIPLANAR);
 	BIND_ENUM_CONSTANT(FLAG_UV2_USE_TRIPLANAR);
 	BIND_ENUM_CONSTANT(FLAG_UV2_USE_TRIPLANAR);
 	BIND_ENUM_CONSTANT(FLAG_AO_ON_UV2);
 	BIND_ENUM_CONSTANT(FLAG_AO_ON_UV2);
+	BIND_ENUM_CONSTANT(FLAG_EMISSION_ON_UV2);
 	BIND_ENUM_CONSTANT(FLAG_USE_ALPHA_SCISSOR);
 	BIND_ENUM_CONSTANT(FLAG_USE_ALPHA_SCISSOR);
 	BIND_ENUM_CONSTANT(FLAG_TRIPLANAR_USE_WORLD);
 	BIND_ENUM_CONSTANT(FLAG_TRIPLANAR_USE_WORLD);
 	BIND_ENUM_CONSTANT(FLAG_ALBEDO_TEXTURE_FORCE_SRGB);
 	BIND_ENUM_CONSTANT(FLAG_ALBEDO_TEXTURE_FORCE_SRGB);

+ 2 - 1
scene/resources/material.h

@@ -184,6 +184,7 @@ public:
 		FLAG_UV2_USE_TRIPLANAR,
 		FLAG_UV2_USE_TRIPLANAR,
 		FLAG_TRIPLANAR_USE_WORLD,
 		FLAG_TRIPLANAR_USE_WORLD,
 		FLAG_AO_ON_UV2,
 		FLAG_AO_ON_UV2,
+		FLAG_EMISSION_ON_UV2,
 		FLAG_USE_ALPHA_SCISSOR,
 		FLAG_USE_ALPHA_SCISSOR,
 		FLAG_ALBEDO_TEXTURE_FORCE_SRGB,
 		FLAG_ALBEDO_TEXTURE_FORCE_SRGB,
 		FLAG_MAX
 		FLAG_MAX
@@ -234,7 +235,7 @@ private:
 			uint64_t blend_mode : 2;
 			uint64_t blend_mode : 2;
 			uint64_t depth_draw_mode : 2;
 			uint64_t depth_draw_mode : 2;
 			uint64_t cull_mode : 2;
 			uint64_t cull_mode : 2;
-			uint64_t flags : 13;
+			uint64_t flags : 14;
 			uint64_t detail_blend_mode : 2;
 			uint64_t detail_blend_mode : 2;
 			uint64_t diffuse_mode : 3;
 			uint64_t diffuse_mode : 3;
 			uint64_t specular_mode : 2;
 			uint64_t specular_mode : 2;

+ 15 - 13
scene/resources/mesh.cpp

@@ -1123,27 +1123,29 @@ Error ArrayMesh::lightmap_unwrap(const Transform &p_base_transform, float p_texe
 
 
 		PoolVector<int> rindices = arrays[Mesh::ARRAY_INDEX];
 		PoolVector<int> rindices = arrays[Mesh::ARRAY_INDEX];
 		int ic = rindices.size();
 		int ic = rindices.size();
-		int index_ofs = indices.size();
 
 
 		if (ic == 0) {
 		if (ic == 0) {
-			indices.resize(index_ofs + vc);
-			face_materials.resize((index_ofs + vc) / 3);
-			for (int j = 0; j < vc; j++) {
-				indices[index_ofs + j] = vertex_ofs + j;
-			}
+
 			for (int j = 0; j < vc / 3; j++) {
 			for (int j = 0; j < vc / 3; j++) {
-				face_materials[(index_ofs / 3) + j] = i;
+				if (Face3(r[j * 3 + 0], r[j * 3 + 1], r[j * 3 + 2]).is_degenerate())
+					continue;
+
+				indices.push_back(vertex_ofs + j * 3 + 0);
+				indices.push_back(vertex_ofs + j * 3 + 1);
+				indices.push_back(vertex_ofs + j * 3 + 2);
+				face_materials.push_back(i);
 			}
 			}
 
 
 		} else {
 		} else {
 			PoolVector<int>::Read ri = rindices.read();
 			PoolVector<int>::Read ri = rindices.read();
-			indices.resize(index_ofs + ic);
-			face_materials.resize((index_ofs + ic) / 3);
-			for (int j = 0; j < ic; j++) {
-				indices[index_ofs + j] = vertex_ofs + ri[j];
-			}
+
 			for (int j = 0; j < ic / 3; j++) {
 			for (int j = 0; j < ic / 3; j++) {
-				face_materials[(index_ofs / 3) + j] = i;
+				if (Face3(r[ri[j * 3 + 0]], r[ri[j * 3 + 1]], r[ri[j * 3 + 2]]).is_degenerate())
+					continue;
+				indices.push_back(vertex_ofs + ri[j * 3 + 0]);
+				indices.push_back(vertex_ofs + ri[j * 3 + 1]);
+				indices.push_back(vertex_ofs + ri[j * 3 + 2]);
+				face_materials.push_back(i);
 			}
 			}
 		}
 		}
 
 

+ 31 - 0
servers/visual/rasterizer.h

@@ -112,6 +112,10 @@ public:
 
 
 		SelfList<InstanceBase> dependency_item;
 		SelfList<InstanceBase> dependency_item;
 
 
+		InstanceBase *lightmap_capture;
+		RID lightmap;
+		Vector<Color> lightmap_capture_data; //in a array (12 values) to avoid wasting space if unused. Alpha is unused, but needed to send to shader
+
 		virtual void base_removed() = 0;
 		virtual void base_removed() = 0;
 		virtual void base_changed() = 0;
 		virtual void base_changed() = 0;
 		virtual void base_material_changed() = 0;
 		virtual void base_material_changed() = 0;
@@ -126,6 +130,7 @@ public:
 			depth_layer = 0;
 			depth_layer = 0;
 			layer_mask = 1;
 			layer_mask = 1;
 			baked_light = false;
 			baked_light = false;
+			lightmap_capture = NULL;
 		}
 		}
 	};
 	};
 
 
@@ -437,6 +442,32 @@ public:
 	virtual RID gi_probe_dynamic_data_create(int p_width, int p_height, int p_depth, GIProbeCompression p_compression) = 0;
 	virtual RID gi_probe_dynamic_data_create(int p_width, int p_height, int p_depth, GIProbeCompression p_compression) = 0;
 	virtual void gi_probe_dynamic_data_update(RID p_gi_probe_data, int p_depth_slice, int p_slice_count, int p_mipmap, const void *p_data) = 0;
 	virtual void gi_probe_dynamic_data_update(RID p_gi_probe_data, int p_depth_slice, int p_slice_count, int p_mipmap, const void *p_data) = 0;
 
 
+	/* LIGHTMAP CAPTURE */
+
+	struct LightmapCaptureOctree {
+
+		enum {
+			CHILD_EMPTY = 0xFFFFFFFF
+		};
+
+		uint16_t light[6][3]; //anisotropic light
+		float alpha;
+		uint32_t children[8];
+	};
+
+	virtual RID lightmap_capture_create() = 0;
+	virtual void lightmap_capture_set_bounds(RID p_capture, const AABB &p_bounds) = 0;
+	virtual AABB lightmap_capture_get_bounds(RID p_capture) const = 0;
+	virtual void lightmap_capture_set_octree(RID p_capture, const PoolVector<uint8_t> &p_octree) = 0;
+	virtual PoolVector<uint8_t> lightmap_capture_get_octree(RID p_capture) const = 0;
+	virtual void lightmap_capture_set_octree_cell_transform(RID p_capture, const Transform &p_xform) = 0;
+	virtual Transform lightmap_capture_get_octree_cell_transform(RID p_capture) const = 0;
+	virtual void lightmap_capture_set_octree_cell_subdiv(RID p_capture, int p_subdiv) = 0;
+	virtual int lightmap_capture_get_octree_cell_subdiv(RID p_capture) const = 0;
+	virtual void lightmap_capture_set_energy(RID p_capture, float p_energy) = 0;
+	virtual float lightmap_capture_get_energy(RID p_capture) const = 0;
+	virtual const PoolVector<LightmapCaptureOctree> *lightmap_capture_get_octree_ptr(RID p_capture) const = 0;
+
 	/* PARTICLES */
 	/* PARTICLES */
 
 
 	virtual RID particles_create() = 0;
 	virtual RID particles_create() = 0;

+ 19 - 0
servers/visual/visual_server_raster.h

@@ -361,6 +361,24 @@ public:
 	BIND2(gi_probe_set_dynamic_data, RID, const PoolVector<int> &)
 	BIND2(gi_probe_set_dynamic_data, RID, const PoolVector<int> &)
 	BIND1RC(PoolVector<int>, gi_probe_get_dynamic_data, RID)
 	BIND1RC(PoolVector<int>, gi_probe_get_dynamic_data, RID)
 
 
+	/* LIGHTMAP CAPTURE */
+
+	BIND0R(RID, lightmap_capture_create)
+
+	BIND2(lightmap_capture_set_bounds, RID, const AABB &)
+	BIND1RC(AABB, lightmap_capture_get_bounds, RID)
+
+	BIND2(lightmap_capture_set_octree, RID, const PoolVector<uint8_t> &)
+	BIND1RC(PoolVector<uint8_t>, lightmap_capture_get_octree, RID)
+
+	BIND2(lightmap_capture_set_octree_cell_transform, RID, const Transform &)
+	BIND1RC(Transform, lightmap_capture_get_octree_cell_transform, RID)
+	BIND2(lightmap_capture_set_octree_cell_subdiv, RID, int)
+	BIND1RC(int, lightmap_capture_get_octree_cell_subdiv, RID)
+
+	BIND2(lightmap_capture_set_energy, RID, float)
+	BIND1RC(float, lightmap_capture_get_energy, RID)
+
 	/* PARTICLES */
 	/* PARTICLES */
 
 
 	BIND0R(RID, particles_create)
 	BIND0R(RID, particles_create)
@@ -504,6 +522,7 @@ public:
 	BIND3(instance_set_blend_shape_weight, RID, int, float)
 	BIND3(instance_set_blend_shape_weight, RID, int, float)
 	BIND3(instance_set_surface_material, RID, int, RID)
 	BIND3(instance_set_surface_material, RID, int, RID)
 	BIND2(instance_set_visible, RID, bool)
 	BIND2(instance_set_visible, RID, bool)
+	BIND3(instance_set_use_lightmap, RID, RID, RID)
 
 
 	BIND2(instance_set_custom_aabb, RID, AABB)
 	BIND2(instance_set_custom_aabb, RID, AABB)
 
 

+ 328 - 4
servers/visual/visual_server_scene.cpp

@@ -132,6 +132,19 @@ void *VisualServerScene::_instance_pair(void *p_self, OctreeElementID, Instance
 
 
 		geom->reflection_dirty = true;
 		geom->reflection_dirty = true;
 
 
+		return E; //this element should make freeing faster
+	} else if (B->base_type == VS::INSTANCE_LIGHTMAP_CAPTURE && ((1 << A->base_type) & VS::INSTANCE_GEOMETRY_MASK)) {
+
+		InstanceLightmapCaptureData *lightmap_capture = static_cast<InstanceLightmapCaptureData *>(B->base_data);
+		InstanceGeometryData *geom = static_cast<InstanceGeometryData *>(A->base_data);
+
+		InstanceLightmapCaptureData::PairInfo pinfo;
+		pinfo.geometry = A;
+		pinfo.L = geom->lightmap_captures.push_back(B);
+
+		List<InstanceLightmapCaptureData::PairInfo>::Element *E = lightmap_capture->geometries.push_back(pinfo);
+		((VisualServerScene *)p_self)->_instance_queue_update(A, false, false); //need to update capture
+
 		return E; //this element should make freeing faster
 		return E; //this element should make freeing faster
 	} else if (B->base_type == VS::INSTANCE_GI_PROBE && ((1 << A->base_type) & VS::INSTANCE_GEOMETRY_MASK)) {
 	} else if (B->base_type == VS::INSTANCE_GI_PROBE && ((1 << A->base_type) & VS::INSTANCE_GEOMETRY_MASK)) {
 
 
@@ -193,6 +206,16 @@ void VisualServerScene::_instance_unpair(void *p_self, OctreeElementID, Instance
 		reflection_probe->geometries.erase(E);
 		reflection_probe->geometries.erase(E);
 
 
 		geom->reflection_dirty = true;
 		geom->reflection_dirty = true;
+	} else if (B->base_type == VS::INSTANCE_LIGHTMAP_CAPTURE && ((1 << A->base_type) & VS::INSTANCE_GEOMETRY_MASK)) {
+
+		InstanceLightmapCaptureData *lightmap_capture = static_cast<InstanceLightmapCaptureData *>(B->base_data);
+		InstanceGeometryData *geom = static_cast<InstanceGeometryData *>(A->base_data);
+
+		List<InstanceLightmapCaptureData::PairInfo>::Element *E = reinterpret_cast<List<InstanceLightmapCaptureData::PairInfo>::Element *>(udata);
+
+		geom->lightmap_captures.erase(E->get().L);
+		lightmap_capture->geometries.erase(E);
+		((VisualServerScene *)p_self)->_instance_queue_update(A, false, false); //need to update capture
 
 
 	} else if (B->base_type == VS::INSTANCE_GI_PROBE && ((1 << A->base_type) & VS::INSTANCE_GEOMETRY_MASK)) {
 	} else if (B->base_type == VS::INSTANCE_GI_PROBE && ((1 << A->base_type) & VS::INSTANCE_GEOMETRY_MASK)) {
 
 
@@ -344,6 +367,14 @@ void VisualServerScene::instance_set_base(RID p_instance, RID p_base) {
 					reflection_probe_render_list.remove(&reflection_probe->update_list);
 					reflection_probe_render_list.remove(&reflection_probe->update_list);
 				}
 				}
 			} break;
 			} break;
+			case VS::INSTANCE_LIGHTMAP_CAPTURE: {
+
+				InstanceLightmapCaptureData *lightmap_capture = static_cast<InstanceLightmapCaptureData *>(instance->base_data);
+				//erase dependencies, since no longer a lightmap
+				while (lightmap_capture->users.front()) {
+					instance_set_use_lightmap(lightmap_capture->users.front()->get()->self, RID(), RID());
+				}
+			} break;
 			case VS::INSTANCE_GI_PROBE: {
 			case VS::INSTANCE_GI_PROBE: {
 
 
 				InstanceGIProbeData *gi_probe = static_cast<InstanceGIProbeData *>(instance->base_data);
 				InstanceGIProbeData *gi_probe = static_cast<InstanceGIProbeData *>(instance->base_data);
@@ -355,6 +386,14 @@ void VisualServerScene::instance_set_base(RID p_instance, RID p_base) {
 					VSG::storage->free(gi_probe->dynamic.probe_data);
 					VSG::storage->free(gi_probe->dynamic.probe_data);
 				}
 				}
 
 
+				if (instance->lightmap_capture) {
+					Instance *capture = (Instance *)instance->lightmap_capture;
+					InstanceLightmapCaptureData *lightmap_capture = static_cast<InstanceLightmapCaptureData *>(capture->base_data);
+					lightmap_capture->users.erase(instance);
+					instance->lightmap_capture = NULL;
+					instance->lightmap = RID();
+				}
+
 				VSG::scene_render->free(gi_probe->probe_instance);
 				VSG::scene_render->free(gi_probe->probe_instance);
 
 
 			} break;
 			} break;
@@ -412,6 +451,12 @@ void VisualServerScene::instance_set_base(RID p_instance, RID p_base) {
 
 
 				reflection_probe->instance = VSG::scene_render->reflection_probe_instance_create(p_base);
 				reflection_probe->instance = VSG::scene_render->reflection_probe_instance_create(p_base);
 			} break;
 			} break;
+			case VS::INSTANCE_LIGHTMAP_CAPTURE: {
+
+				InstanceLightmapCaptureData *lightmap_capture = memnew(InstanceLightmapCaptureData);
+				instance->base_data = lightmap_capture;
+				//lightmap_capture->instance = VSG::scene_render->lightmap_capture_instance_create(p_base);
+			} break;
 			case VS::INSTANCE_GI_PROBE: {
 			case VS::INSTANCE_GI_PROBE: {
 
 
 				InstanceGIProbeData *gi_probe = memnew(InstanceGIProbeData);
 				InstanceGIProbeData *gi_probe = memnew(InstanceGIProbeData);
@@ -590,6 +635,12 @@ void VisualServerScene::instance_set_visible(RID p_instance, bool p_visible) {
 				instance->scenario->octree.set_pairable(instance->octree_id, p_visible, 1 << VS::INSTANCE_REFLECTION_PROBE, p_visible ? VS::INSTANCE_GEOMETRY_MASK : 0);
 				instance->scenario->octree.set_pairable(instance->octree_id, p_visible, 1 << VS::INSTANCE_REFLECTION_PROBE, p_visible ? VS::INSTANCE_GEOMETRY_MASK : 0);
 			}
 			}
 
 
+		} break;
+		case VS::INSTANCE_LIGHTMAP_CAPTURE: {
+			if (instance->octree_id && instance->scenario) {
+				instance->scenario->octree.set_pairable(instance->octree_id, p_visible, 1 << VS::INSTANCE_LIGHTMAP_CAPTURE, p_visible ? VS::INSTANCE_GEOMETRY_MASK : 0);
+			}
+
 		} break;
 		} break;
 		case VS::INSTANCE_GI_PROBE: {
 		case VS::INSTANCE_GI_PROBE: {
 			if (instance->octree_id && instance->scenario) {
 			if (instance->octree_id && instance->scenario) {
@@ -599,11 +650,35 @@ void VisualServerScene::instance_set_visible(RID p_instance, bool p_visible) {
 		} break;
 		} break;
 	}
 	}
 }
 }
-
 inline bool is_geometry_instance(VisualServer::InstanceType p_type) {
 inline bool is_geometry_instance(VisualServer::InstanceType p_type) {
 	return p_type == VS::INSTANCE_MESH || p_type == VS::INSTANCE_MULTIMESH || p_type == VS::INSTANCE_PARTICLES || p_type == VS::INSTANCE_IMMEDIATE;
 	return p_type == VS::INSTANCE_MESH || p_type == VS::INSTANCE_MULTIMESH || p_type == VS::INSTANCE_PARTICLES || p_type == VS::INSTANCE_IMMEDIATE;
 }
 }
 
 
+void VisualServerScene::instance_set_use_lightmap(RID p_instance, RID p_lightmap_instance, RID p_lightmap) {
+
+	Instance *instance = instance_owner.get(p_instance);
+	ERR_FAIL_COND(!instance);
+	ERR_FAIL_COND(!is_geometry_instance(instance->base_type));
+
+	if (instance->lightmap_capture) {
+		InstanceLightmapCaptureData *lightmap_capture = static_cast<InstanceLightmapCaptureData *>(((Instance *)instance->lightmap_capture)->base_data);
+		lightmap_capture->users.erase(instance);
+		instance->lightmap = RID();
+		instance->lightmap_capture = NULL;
+	}
+
+	if (p_lightmap_instance.is_valid()) {
+		Instance *lightmap_instance = instance_owner.get(p_lightmap_instance);
+		ERR_FAIL_COND(!lightmap_instance);
+		ERR_FAIL_COND(lightmap_instance->base_type != VS::INSTANCE_LIGHTMAP_CAPTURE);
+		instance->lightmap_capture = lightmap_instance;
+
+		InstanceLightmapCaptureData *lightmap_capture = static_cast<InstanceLightmapCaptureData *>(((Instance *)instance->lightmap_capture)->base_data);
+		lightmap_capture->users.insert(instance);
+		instance->lightmap = p_lightmap;
+	}
+}
+
 void VisualServerScene::instance_set_custom_aabb(RID p_instance, AABB p_aabb) {
 void VisualServerScene::instance_set_custom_aabb(RID p_instance, AABB p_aabb) {
 
 
 	Instance *instance = instance_owner.get(p_instance);
 	Instance *instance = instance_owner.get(p_instance);
@@ -811,6 +886,15 @@ void VisualServerScene::_update_instance(Instance *p_instance) {
 				light->shadow_dirty = true;
 				light->shadow_dirty = true;
 			}
 			}
 		}
 		}
+
+		if (!p_instance->lightmap_capture && geom->lightmap_captures.size()) {
+			//affected by lightmap captures, must update capture info!
+			_update_instance_lightmap_captures(p_instance);
+		} else {
+			if (!p_instance->lightmap_capture_data.empty()) {
+				!p_instance->lightmap_capture_data.resize(0); //not in use, clear capture data
+			}
+		}
 	}
 	}
 
 
 	p_instance->mirror = p_instance->transform.basis.determinant() < 0.0;
 	p_instance->mirror = p_instance->transform.basis.determinant() < 0.0;
@@ -832,7 +916,7 @@ void VisualServerScene::_update_instance(Instance *p_instance) {
 		uint32_t pairable_mask = 0;
 		uint32_t pairable_mask = 0;
 		bool pairable = false;
 		bool pairable = false;
 
 
-		if (p_instance->base_type == VS::INSTANCE_LIGHT || p_instance->base_type == VS::INSTANCE_REFLECTION_PROBE) {
+		if (p_instance->base_type == VS::INSTANCE_LIGHT || p_instance->base_type == VS::INSTANCE_REFLECTION_PROBE || p_instance->base_type == VS::INSTANCE_LIGHTMAP_CAPTURE) {
 
 
 			pairable_mask = p_instance->visible ? VS::INSTANCE_GEOMETRY_MASK : 0;
 			pairable_mask = p_instance->visible ? VS::INSTANCE_GEOMETRY_MASK : 0;
 			pairable = true;
 			pairable = true;
@@ -916,6 +1000,11 @@ void VisualServerScene::_update_instance_aabb(Instance *p_instance) {
 
 
 			new_aabb = VSG::storage->gi_probe_get_bounds(p_instance->base);
 			new_aabb = VSG::storage->gi_probe_get_bounds(p_instance->base);
 
 
+		} break;
+		case VisualServer::INSTANCE_LIGHTMAP_CAPTURE: {
+
+			new_aabb = VSG::storage->lightmap_capture_get_bounds(p_instance->base);
+
 		} break;
 		} break;
 
 
 		default: {}
 		default: {}
@@ -928,6 +1017,237 @@ void VisualServerScene::_update_instance_aabb(Instance *p_instance) {
 	p_instance->aabb = new_aabb;
 	p_instance->aabb = new_aabb;
 }
 }
 
 
+_FORCE_INLINE_ static void _light_capture_sample_octree(const RasterizerStorage::LightmapCaptureOctree *p_octree, int p_cell_subdiv, const Vector3 &p_pos, const Vector3 &p_dir, float p_level, Vector3 &r_color, float &r_alpha) {
+
+	static const Vector3 aniso_normal[6] = {
+		Vector3(-1, 0, 0),
+		Vector3(1, 0, 0),
+		Vector3(0, -1, 0),
+		Vector3(0, 1, 0),
+		Vector3(0, 0, -1),
+		Vector3(0, 0, 1)
+	};
+
+	int size = 1 << (p_cell_subdiv - 1);
+
+	int clamp_v = size - 1;
+	//first of all, clamp
+	Vector3 pos;
+	pos.x = CLAMP(p_pos.x, 0, clamp_v);
+	pos.y = CLAMP(p_pos.y, 0, clamp_v);
+	pos.z = CLAMP(p_pos.z, 0, clamp_v);
+
+	float level = (p_cell_subdiv - 1) - p_level;
+
+	int target_level;
+	float level_filter;
+	if (level <= 0.0) {
+		level_filter = 0;
+		target_level = 0;
+	} else {
+		target_level = Math::ceil(level);
+		level_filter = target_level - level;
+	}
+
+	Vector3 color[2][8];
+	float alpha[2][8];
+	zeromem(alpha, sizeof(float) * 2 * 8);
+
+	//find cell at given level first
+
+	for (int c = 0; c < 2; c++) {
+
+		int current_level = MAX(0, target_level - c);
+		int level_cell_size = (1 << (p_cell_subdiv - 1)) >> current_level;
+
+		for (int n = 0; n < 8; n++) {
+
+			int x = int(pos.x);
+			int y = int(pos.y);
+			int z = int(pos.z);
+
+			if (n & 1)
+				x += level_cell_size;
+			if (n & 2)
+				y += level_cell_size;
+			if (n & 4)
+				z += level_cell_size;
+
+			int ofs_x = 0;
+			int ofs_y = 0;
+			int ofs_z = 0;
+
+			x = CLAMP(x, 0, clamp_v);
+			y = CLAMP(y, 0, clamp_v);
+			z = CLAMP(z, 0, clamp_v);
+
+			int half = size / 2;
+			uint32_t cell = 0;
+			for (int i = 0; i < current_level; i++) {
+
+				const RasterizerStorage::LightmapCaptureOctree *bc = &p_octree[cell];
+
+				int child = 0;
+				if (x >= ofs_x + half) {
+					child |= 1;
+					ofs_x += half;
+				}
+				if (y >= ofs_y + half) {
+					child |= 2;
+					ofs_y += half;
+				}
+				if (z >= ofs_z + half) {
+					child |= 4;
+					ofs_z += half;
+				}
+
+				cell = bc->children[child];
+				if (cell == RasterizerStorage::LightmapCaptureOctree::CHILD_EMPTY)
+					break;
+
+				half >>= 1;
+			}
+
+			if (cell == RasterizerStorage::LightmapCaptureOctree::CHILD_EMPTY) {
+				alpha[c][n] = 0;
+			} else {
+				alpha[c][n] = p_octree[cell].alpha;
+
+				for (int i = 0; i < 6; i++) {
+					//anisotropic read light
+					float amount = p_dir.dot(aniso_normal[i]);
+					if (amount < 0)
+						amount = 0;
+					color[c][n].x += p_octree[cell].light[i][0] / 1024.0 * amount;
+					color[c][n].y += p_octree[cell].light[i][1] / 1024.0 * amount;
+					color[c][n].z += p_octree[cell].light[i][2] / 1024.0 * amount;
+				}
+			}
+
+			//print_line("\tlev " + itos(c) + " - " + itos(n) + " alpha: " + rtos(cells[test_cell].alpha) + " col: " + color[c][n]);
+		}
+	}
+
+	float target_level_size = size >> target_level;
+	Vector3 pos_fract[2];
+
+	pos_fract[0].x = Math::fmod(pos.x, target_level_size) / target_level_size;
+	pos_fract[0].y = Math::fmod(pos.y, target_level_size) / target_level_size;
+	pos_fract[0].z = Math::fmod(pos.z, target_level_size) / target_level_size;
+
+	target_level_size = size >> MAX(0, target_level - 1);
+
+	pos_fract[1].x = Math::fmod(pos.x, target_level_size) / target_level_size;
+	pos_fract[1].y = Math::fmod(pos.y, target_level_size) / target_level_size;
+	pos_fract[1].z = Math::fmod(pos.z, target_level_size) / target_level_size;
+
+	float alpha_interp[2];
+	Vector3 color_interp[2];
+
+	for (int i = 0; i < 2; i++) {
+
+		Vector3 color_x00 = color[i][0].linear_interpolate(color[i][1], pos_fract[i].x);
+		Vector3 color_xy0 = color[i][2].linear_interpolate(color[i][3], pos_fract[i].x);
+		Vector3 blend_z0 = color_x00.linear_interpolate(color_xy0, pos_fract[i].y);
+
+		Vector3 color_x0z = color[i][4].linear_interpolate(color[i][5], pos_fract[i].x);
+		Vector3 color_xyz = color[i][6].linear_interpolate(color[i][7], pos_fract[i].x);
+		Vector3 blend_z1 = color_x0z.linear_interpolate(color_xyz, pos_fract[i].y);
+
+		color_interp[i] = blend_z0.linear_interpolate(blend_z1, pos_fract[i].z);
+
+		float alpha_x00 = Math::lerp(alpha[i][0], alpha[i][1], pos_fract[i].x);
+		float alpha_xy0 = Math::lerp(alpha[i][2], alpha[i][3], pos_fract[i].x);
+		float alpha_z0 = Math::lerp(alpha_x00, alpha_xy0, pos_fract[i].y);
+
+		float alpha_x0z = Math::lerp(alpha[i][4], alpha[i][5], pos_fract[i].x);
+		float alpha_xyz = Math::lerp(alpha[i][6], alpha[i][7], pos_fract[i].x);
+		float alpha_z1 = Math::lerp(alpha_x0z, alpha_xyz, pos_fract[i].y);
+
+		alpha_interp[i] = Math::lerp(alpha_z0, alpha_z1, pos_fract[i].z);
+	}
+
+	r_color = color_interp[0].linear_interpolate(color_interp[1], level_filter);
+	r_alpha = Math::lerp(alpha_interp[0], alpha_interp[1], level_filter);
+
+	//	print_line("pos: " + p_posf + " level " + rtos(p_level) + " down to " + itos(target_level) + "." + rtos(level_filter) + " color " + r_color + " alpha " + rtos(r_alpha));
+}
+
+_FORCE_INLINE_ static Color _light_capture_voxel_cone_trace(const RasterizerStorage::LightmapCaptureOctree *p_octree, const Vector3 &p_pos, const Vector3 &p_dir, float p_aperture, int p_cell_subdiv) {
+
+	float bias = 0.0; //no need for bias here
+	float max_distance = (Vector3(1, 1, 1) * (1 << (p_cell_subdiv - 1))).length();
+
+	float dist = bias;
+	float alpha = 0.0;
+	Vector3 color;
+
+	Vector3 scolor;
+	float salpha;
+
+	while (dist < max_distance && alpha < 0.95) {
+		float diameter = MAX(1.0, 2.0 * p_aperture * dist);
+		_light_capture_sample_octree(p_octree, p_cell_subdiv, p_pos + dist * p_dir, p_dir, log2(diameter), scolor, salpha);
+		float a = (1.0 - alpha);
+		color += scolor * a;
+		alpha += a * salpha;
+		dist += diameter * 0.5;
+	}
+
+	return Color(color.x, color.y, color.z, alpha);
+}
+
+void VisualServerScene::_update_instance_lightmap_captures(Instance *p_instance) {
+
+	InstanceGeometryData *geom = static_cast<InstanceGeometryData *>(p_instance->base_data);
+
+	static const Vector3 cone_traces[12] = {
+		Vector3(0, 0, 1),
+		Vector3(0.866025, 0, 0.5),
+		Vector3(0.267617, 0.823639, 0.5),
+		Vector3(-0.700629, 0.509037, 0.5),
+		Vector3(-0.700629, -0.509037, 0.5),
+		Vector3(0.267617, -0.823639, 0.5),
+		Vector3(0, 0, -1),
+		Vector3(0.866025, 0, -0.5),
+		Vector3(0.267617, 0.823639, -0.5),
+		Vector3(-0.700629, 0.509037, -0.5),
+		Vector3(-0.700629, -0.509037, -0.5),
+		Vector3(0.267617, -0.823639, -0.5)
+	};
+
+	float cone_aperture = 0.577; // tan(angle) 60 degrees
+
+	if (p_instance->lightmap_capture_data.empty()) {
+		p_instance->lightmap_capture_data.resize(12);
+	}
+
+	//print_line("update captures for pos: " + p_instance->transform.origin);
+
+	zeromem(p_instance->lightmap_capture_data.ptrw(), 12 * sizeof(Color));
+	//this could use some sort of blending..
+	for (List<Instance *>::Element *E = geom->lightmap_captures.front(); E; E = E->next()) {
+		const PoolVector<RasterizerStorage::LightmapCaptureOctree> *octree = VSG::storage->lightmap_capture_get_octree_ptr(E->get()->base);
+		//print_line("octree size: " + itos(octree->size()));
+		if (octree->size() == 0)
+			continue;
+		Transform to_cell_xform = VSG::storage->lightmap_capture_get_octree_cell_transform(E->get()->base);
+		int cell_subdiv = VSG::storage->lightmap_capture_get_octree_cell_subdiv(E->get()->base);
+		to_cell_xform = to_cell_xform * E->get()->transform.affine_inverse();
+
+		PoolVector<RasterizerStorage::LightmapCaptureOctree>::Read octree_r = octree->read();
+
+		Vector3 pos = to_cell_xform.xform(p_instance->transform.origin);
+
+		for (int i = 0; i < 12; i++) {
+
+			Vector3 dir = to_cell_xform.basis.xform(cone_traces[i]).normalized();
+			Color capture = _light_capture_voxel_cone_trace(octree_r.ptr(), pos, dir, cone_aperture, cell_subdiv);
+			p_instance->lightmap_capture_data[i] += capture;
+		}
+	}
+}
+
 void VisualServerScene::_light_instance_update_shadow(Instance *p_instance, const Transform p_cam_transform, const CameraMatrix &p_cam_projection, bool p_cam_orthogonal, RID p_shadow_atlas, Scenario *p_scenario) {
 void VisualServerScene::_light_instance_update_shadow(Instance *p_instance, const Transform p_cam_transform, const CameraMatrix &p_cam_projection, bool p_cam_orthogonal, RID p_shadow_atlas, Scenario *p_scenario) {
 
 
 	InstanceLightData *light = static_cast<InstanceLightData *>(p_instance->base_data);
 	InstanceLightData *light = static_cast<InstanceLightData *>(p_instance->base_data);
@@ -2188,6 +2508,8 @@ void VisualServerScene::_bake_gi_probe_light(const GIProbeDataHeader *header, co
 				InstanceGIProbeData::LocalData *light = &local_data[idx];
 				InstanceGIProbeData::LocalData *light = &local_data[idx];
 
 
 				Vector3 to(light->pos[0] + 0.5, light->pos[1] + 0.5, light->pos[2] + 0.5);
 				Vector3 to(light->pos[0] + 0.5, light->pos[1] + 0.5, light->pos[2] + 0.5);
+				to += -light_axis.sign() * 0.47; //make it more likely to receive a ray
+
 				Vector3 norm(
 				Vector3 norm(
 						(((cells[idx].normal >> 16) & 0xFF) / 255.0) * 2.0 - 1.0,
 						(((cells[idx].normal >> 16) & 0xFF) / 255.0) * 2.0 - 1.0,
 						(((cells[idx].normal >> 8) & 0xFF) / 255.0) * 2.0 - 1.0,
 						(((cells[idx].normal >> 8) & 0xFF) / 255.0) * 2.0 - 1.0,
@@ -2254,6 +2576,8 @@ void VisualServerScene::_bake_gi_probe_light(const GIProbeDataHeader *header, co
 				InstanceGIProbeData::LocalData *light = &local_data[idx];
 				InstanceGIProbeData::LocalData *light = &local_data[idx];
 
 
 				Vector3 to(light->pos[0] + 0.5, light->pos[1] + 0.5, light->pos[2] + 0.5);
 				Vector3 to(light->pos[0] + 0.5, light->pos[1] + 0.5, light->pos[2] + 0.5);
+				to += (light_pos - to).sign() * 0.47; //make it more likely to receive a ray
+
 				Vector3 norm(
 				Vector3 norm(
 						(((cells[idx].normal >> 16) & 0xFF) / 255.0) * 2.0 - 1.0,
 						(((cells[idx].normal >> 16) & 0xFF) / 255.0) * 2.0 - 1.0,
 						(((cells[idx].normal >> 8) & 0xFF) / 255.0) * 2.0 - 1.0,
 						(((cells[idx].normal >> 8) & 0xFF) / 255.0) * 2.0 - 1.0,
@@ -2927,12 +3251,12 @@ void VisualServerScene::_update_dirty_instance(Instance *p_instance) {
 		}
 		}
 	}
 	}
 
 
+	_instance_update_list.remove(&p_instance->update_item);
+
 	_update_instance(p_instance);
 	_update_instance(p_instance);
 
 
 	p_instance->update_aabb = false;
 	p_instance->update_aabb = false;
 	p_instance->update_materials = false;
 	p_instance->update_materials = false;
-
-	_instance_update_list.remove(&p_instance->update_item);
 }
 }
 
 
 void VisualServerScene::update_dirty_instances() {
 void VisualServerScene::update_dirty_instances() {

+ 18 - 0
servers/visual/visual_server_scene.h

@@ -281,6 +281,8 @@ public:
 		List<Instance *> gi_probes;
 		List<Instance *> gi_probes;
 		bool gi_probes_dirty;
 		bool gi_probes_dirty;
 
 
+		List<Instance *> lightmap_captures;
+
 		InstanceGeometryData() {
 		InstanceGeometryData() {
 
 
 			lighting_dirty = false;
 			lighting_dirty = false;
@@ -445,6 +447,20 @@ public:
 
 
 	SelfList<InstanceGIProbeData>::List gi_probe_update_list;
 	SelfList<InstanceGIProbeData>::List gi_probe_update_list;
 
 
+	struct InstanceLightmapCaptureData : public InstanceBaseData {
+
+		struct PairInfo {
+			List<Instance *>::Element *L; //iterator in geometry
+			Instance *geometry;
+		};
+		List<PairInfo> geometries;
+
+		Set<Instance *> users;
+
+		InstanceLightmapCaptureData() {
+		}
+	};
+
 	Instance *instance_cull_result[MAX_INSTANCE_CULL];
 	Instance *instance_cull_result[MAX_INSTANCE_CULL];
 	Instance *instance_shadow_cull_result[MAX_INSTANCE_CULL]; //used for generating shadowmaps
 	Instance *instance_shadow_cull_result[MAX_INSTANCE_CULL]; //used for generating shadowmaps
 	Instance *light_cull_result[MAX_LIGHTS_CULLED];
 	Instance *light_cull_result[MAX_LIGHTS_CULLED];
@@ -466,6 +482,7 @@ public:
 	virtual void instance_set_blend_shape_weight(RID p_instance, int p_shape, float p_weight);
 	virtual void instance_set_blend_shape_weight(RID p_instance, int p_shape, float p_weight);
 	virtual void instance_set_surface_material(RID p_instance, int p_surface, RID p_material);
 	virtual void instance_set_surface_material(RID p_instance, int p_surface, RID p_material);
 	virtual void instance_set_visible(RID p_instance, bool p_visible);
 	virtual void instance_set_visible(RID p_instance, bool p_visible);
+	virtual void instance_set_use_lightmap(RID p_instance, RID p_lightmap_instance, RID p_lightmap);
 
 
 	virtual void instance_set_custom_aabb(RID p_insatnce, AABB aabb);
 	virtual void instance_set_custom_aabb(RID p_insatnce, AABB aabb);
 
 
@@ -489,6 +506,7 @@ public:
 	_FORCE_INLINE_ void _update_instance(Instance *p_instance);
 	_FORCE_INLINE_ void _update_instance(Instance *p_instance);
 	_FORCE_INLINE_ void _update_instance_aabb(Instance *p_instance);
 	_FORCE_INLINE_ void _update_instance_aabb(Instance *p_instance);
 	_FORCE_INLINE_ void _update_dirty_instance(Instance *p_instance);
 	_FORCE_INLINE_ void _update_dirty_instance(Instance *p_instance);
+	_FORCE_INLINE_ void _update_instance_lightmap_captures(Instance *p_instance);
 
 
 	_FORCE_INLINE_ void _light_instance_update_shadow(Instance *p_instance, const Transform p_cam_transform, const CameraMatrix &p_cam_projection, bool p_cam_orthogonal, RID p_shadow_atlas, Scenario *p_scenario);
 	_FORCE_INLINE_ void _light_instance_update_shadow(Instance *p_instance, const Transform p_cam_transform, const CameraMatrix &p_cam_projection, bool p_cam_orthogonal, RID p_shadow_atlas, Scenario *p_scenario);
 
 

+ 18 - 0
servers/visual/visual_server_wrap_mt.h

@@ -294,6 +294,22 @@ public:
 	FUNC2(gi_probe_set_dynamic_data, RID, const PoolVector<int> &)
 	FUNC2(gi_probe_set_dynamic_data, RID, const PoolVector<int> &)
 	FUNC1RC(PoolVector<int>, gi_probe_get_dynamic_data, RID)
 	FUNC1RC(PoolVector<int>, gi_probe_get_dynamic_data, RID)
 
 
+	/* LIGHTMAP CAPTURE */
+
+	FUNCRID(lightmap_capture)
+
+	FUNC2(lightmap_capture_set_bounds, RID, const AABB &)
+	FUNC1RC(AABB, lightmap_capture_get_bounds, RID)
+
+	FUNC2(lightmap_capture_set_octree, RID, const PoolVector<uint8_t> &)
+	FUNC1RC(PoolVector<uint8_t>, lightmap_capture_get_octree, RID)
+	FUNC2(lightmap_capture_set_octree_cell_transform, RID, const Transform &)
+	FUNC1RC(Transform, lightmap_capture_get_octree_cell_transform, RID)
+	FUNC2(lightmap_capture_set_octree_cell_subdiv, RID, int)
+	FUNC1RC(int, lightmap_capture_get_octree_cell_subdiv, RID)
+	FUNC2(lightmap_capture_set_energy, RID, float)
+	FUNC1RC(float, lightmap_capture_get_energy, RID)
+
 	/* PARTICLES */
 	/* PARTICLES */
 
 
 	FUNCRID(particles)
 	FUNCRID(particles)
@@ -425,6 +441,8 @@ public:
 	FUNC3(instance_set_blend_shape_weight, RID, int, float)
 	FUNC3(instance_set_blend_shape_weight, RID, int, float)
 	FUNC3(instance_set_surface_material, RID, int, RID)
 	FUNC3(instance_set_surface_material, RID, int, RID)
 	FUNC2(instance_set_visible, RID, bool)
 	FUNC2(instance_set_visible, RID, bool)
+	FUNC3(instance_set_use_lightmap, RID, RID, RID)
+
 	FUNC2(instance_set_custom_aabb, RID, AABB)
 	FUNC2(instance_set_custom_aabb, RID, AABB)
 
 
 	FUNC2(instance_attach_skeleton, RID, RID)
 	FUNC2(instance_attach_skeleton, RID, RID)

+ 17 - 0
servers/visual_server.h

@@ -485,6 +485,20 @@ public:
 	virtual void gi_probe_set_compress(RID p_probe, bool p_enable) = 0;
 	virtual void gi_probe_set_compress(RID p_probe, bool p_enable) = 0;
 	virtual bool gi_probe_is_compressed(RID p_probe) const = 0;
 	virtual bool gi_probe_is_compressed(RID p_probe) const = 0;
 
 
+	/* LIGHTMAP CAPTURE */
+
+	virtual RID lightmap_capture_create() = 0;
+	virtual void lightmap_capture_set_bounds(RID p_capture, const AABB &p_bounds) = 0;
+	virtual AABB lightmap_capture_get_bounds(RID p_capture) const = 0;
+	virtual void lightmap_capture_set_octree(RID p_capture, const PoolVector<uint8_t> &p_octree) = 0;
+	virtual void lightmap_capture_set_octree_cell_transform(RID p_capture, const Transform &p_xform) = 0;
+	virtual Transform lightmap_capture_get_octree_cell_transform(RID p_capture) const = 0;
+	virtual void lightmap_capture_set_octree_cell_subdiv(RID p_capture, int p_subdiv) = 0;
+	virtual int lightmap_capture_get_octree_cell_subdiv(RID p_capture) const = 0;
+	virtual PoolVector<uint8_t> lightmap_capture_get_octree(RID p_capture) const = 0;
+	virtual void lightmap_capture_set_energy(RID p_capture, float p_energy) = 0;
+	virtual float lightmap_capture_get_energy(RID p_capture) const = 0;
+
 	/* PARTICLES API */
 	/* PARTICLES API */
 
 
 	virtual RID particles_create() = 0;
 	virtual RID particles_create() = 0;
@@ -735,6 +749,7 @@ public:
 		INSTANCE_LIGHT,
 		INSTANCE_LIGHT,
 		INSTANCE_REFLECTION_PROBE,
 		INSTANCE_REFLECTION_PROBE,
 		INSTANCE_GI_PROBE,
 		INSTANCE_GI_PROBE,
+		INSTANCE_LIGHTMAP_CAPTURE,
 		INSTANCE_MAX,
 		INSTANCE_MAX,
 		/*INSTANCE_BAKED_LIGHT_SAMPLER,*/
 		/*INSTANCE_BAKED_LIGHT_SAMPLER,*/
 
 
@@ -755,6 +770,8 @@ public:
 	virtual void instance_set_surface_material(RID p_instance, int p_surface, RID p_material) = 0;
 	virtual void instance_set_surface_material(RID p_instance, int p_surface, RID p_material) = 0;
 	virtual void instance_set_visible(RID p_instance, bool p_visible) = 0;
 	virtual void instance_set_visible(RID p_instance, bool p_visible) = 0;
 
 
+	virtual void instance_set_use_lightmap(RID p_instance, RID p_lightmap_instance, RID p_lightmap) = 0;
+
 	virtual void instance_set_custom_aabb(RID p_instance, AABB aabb) = 0;
 	virtual void instance_set_custom_aabb(RID p_instance, AABB aabb) = 0;
 
 
 	virtual void instance_attach_skeleton(RID p_instance, RID p_skeleton) = 0;
 	virtual void instance_attach_skeleton(RID p_instance, RID p_skeleton) = 0;

Some files were not shown because too many files changed in this diff