From e76c5ccdb2c448d3ff17ab248902ef4fa9f7d580 Mon Sep 17 00:00:00 2001 From: Mergul Date: Sat, 6 Jun 2020 22:46:29 +0200 Subject: [PATCH] Huge demos update -moved C stdlib function definitions to ecs_utils.utils -added function to calculate mix(linear interpolation) and rsqrt(fast inverse sqrt) -added some math to vec2 (length, normalize...) -improved renderer with possibility to use multiple materials (one per block, not perfect solution for parallel compute, but works with some requirements) -added blending support for material (opaque, additive, mixed) -added Android support -added gprahical representation for mouse tools (tool_circle.d) -added initial support for editing template components variables -better Component and Templates listing -added possibility to add/removes components using mouse -move CLocation to game_core.basic and reuse in every test -moved tools code from demos to App (now code is fully separated from demos!) -some improvement and fixes in Snake demo, with additional systems to handle adding and removing entities -added new demo: Particles. By now demo has several particles to spawn and support for attractors and vortexes (calculation is made as every attractor with every entity) -fixed bug with window hover and tools -improved tool behaviour -added new material -now window is always opened as maximized windowed mode -some minor fixes and improvements --- demos/assets/shaders/circle.fp | 64 +++ demos/assets/shaders/circle.vp | 114 ++++ demos/assets/textures/atlas.png | Bin 41948 -> 42614 bytes demos/compile_android.py | 2 +- demos/external/sources/glad/gl/loader.d | 4 +- demos/source/app.d | 306 ++++++++++- demos/source/demos/particles.d | 545 ++++++++++++++++++++ demos/source/demos/simple.d | 101 ++-- demos/source/demos/snake.d | 211 ++++---- demos/source/demos/space_invaders.d | 113 ++-- demos/source/game_core/basic.d | 14 + demos/source/gui/attributes.d | 20 + demos/source/gui/component.d | 61 ++- demos/source/gui/manager.d | 317 +++++++++++- demos/source/gui/tool_circle.d | 52 ++ demos/utils/source/ecs_utils/gfx/buffer.d | 2 +- demos/utils/source/ecs_utils/gfx/material.d | 19 + demos/utils/source/ecs_utils/gfx/renderer.d | 76 ++- demos/utils/source/ecs_utils/math/vector.d | 22 + demos/utils/source/ecs_utils/utils.d | 49 +- 20 files changed, 1804 insertions(+), 288 deletions(-) create mode 100644 demos/assets/shaders/circle.fp create mode 100644 demos/assets/shaders/circle.vp create mode 100644 demos/source/demos/particles.d create mode 100644 demos/source/game_core/basic.d create mode 100644 demos/source/gui/attributes.d create mode 100644 demos/source/gui/tool_circle.d diff --git a/demos/assets/shaders/circle.fp b/demos/assets/shaders/circle.fp new file mode 100644 index 0000000..15f46fd --- /dev/null +++ b/demos/assets/shaders/circle.fp @@ -0,0 +1,64 @@ +precision mediump int; +precision mediump float; +precision lowp sampler2D; +precision lowp samplerCube; + + +#ifdef GLES + #define TEX(x,y) texture2D(x,y) + #if __VERSION__ >290 + #define M_IN in mediump + #define L_IN in lowp + #else + #define M_IN varying mediump + #define L_IN varying lowp + #endif +#else + #define TEX(x,y) texture(x,y) + #if __VERSION__ > 320 + #define M_IN in + #define L_IN in + #else + #define M_IN varying + #define L_IN varying + #endif +#endif + +M_IN vec2 pos; +M_IN float edge; +//flat M_IN vec2 fpos; + +//M_IN vec2 uv; +//M_IN vec4 color; +/* +#ifdef GLES + #if __VERSION__ >290 + in mediump vec2 uv; + #else + varying mediump vec2 uv; + #endif +#else + #if __VERSION__ > 320 + in vec2 uv; + #else + varying vec2 uv; + #endif +#endif*/ + +//layout(binding = 0)uniform sampler2D tex; + +//uniform sampler2D tex; + +//layout(location = 0) out vec4 outColor; + +void main() +{ + float len2 = dot(pos,pos); + + if(len2 > 1.0)discard; + + if(len2 > edge)gl_FragColor = vec4(0.4,0.8,1.0,0.8);//TEX(tex,uv) * color; + else gl_FragColor = vec4(0,0.6,1.0,0.35);//TEX(tex,uv) * color; + //gl_FragColor = vec4(pos,0,1); + //if(gl_FragColor.a < 0.01)discard; +} diff --git a/demos/assets/shaders/circle.vp b/demos/assets/shaders/circle.vp new file mode 100644 index 0000000..6997121 --- /dev/null +++ b/demos/assets/shaders/circle.vp @@ -0,0 +1,114 @@ +precision highp float; +precision highp int; +precision lowp sampler2D; +precision lowp samplerCube; +#ifdef GLES + #if __VERSION__ >290 + #define LOC(x) layout(location = x) + #define ATT in + #define M_OUT out mediump + #define L_OUT out lowp + #else + #define LOC(x) + #define ATT attribute + #define M_OUT varying mediump + #define L_OUT varying lowp + #endif +#else + #if __VERSION__ > 320 + #define LOC(x) layout(location = x) + #define ATT in + #define M_OUT out + #define L_OUT out + #else + #define LOC(x) + #define ATT attribute + #define M_OUT varying + #define L_OUT varying + #endif +#endif +/* +#ifdef GLES + #if __VERSION__ >290 + uniform vec4 matrix_1; + uniform vec4 matrix_2; + uniform vec4 uv_transform; + + layout(location = 0) in vec2 positions; + layout(location = 1) in vec2 tex_coords; + + out mediump vec2 uv; + #else + uniform vec4 matrix_1; + uniform vec4 matrix_2; + uniform vec4 uv_transform; + + attribute vec2 positions; + attribute vec2 tex_coords; + + varying mediump vec2 uv; + #endif +#else + #if __VERSION__ > 320 + uniform vec4 matrix_1; + uniform vec4 matrix_2; + uniform vec4 uv_transform; + + layout(location = 0) in vec2 positions; + layout(location = 1) in vec2 tex_coords; + + out vec2 uv; + #else + uniform vec4 matrix_1; + uniform vec4 matrix_2; + uniform vec4 uv_transform; + + attribute vec2 positions; + attribute vec2 tex_coords; + + varying vec2 uv; + #endif +#endif*/ + +//#define VBO_BATCH 1 + +//M_OUT vec2 uv; +//L_OUT vec4 color; +M_OUT vec2 pos; +M_OUT float edge; +//flat M_OUT vec2 fpos; + +LOC(0) ATT vec2 positions; +//LOC(1) ATT vec2 tex_coords; + +#ifdef VBO_BATCH + LOC(2) ATT float depth; + LOC(3) ATT vec4 vcolor; +#else + uniform vec4 matrix_1; + uniform vec4 matrix_2; + //uniform vec4 uv_transform; + //uniform vec4 vcolor; + + //float depth = matrix_2.z; +#endif + +void main() { + //#ifdef VBO_BATCH + // vec3 position = vec3(positions*4.0,1.0); + // uv = tex_coords; + //#else + //edge = mix(0.1, 0.96, (matrix_2.z / 256)); + edge = (matrix_1.w - matrix_2.z) / matrix_1.w;//matrix_2.z;//clamp((matrix_2,0.0,1.0); + edge *= edge; + pos = positions * 2.0;// / matrix_2.zw * 2; + //fpos = positions * matrix_2.xy; + vec3 position = mat3(matrix_1.x,matrix_1.y,0,matrix_1.z,matrix_1.w,0,matrix_2.xy,1.0) * vec3(positions,1.0); + // uv = tex_coords * uv_transform.zw + uv_transform.xy; + //#endif + + //color = vcolor * 2.0; + + gl_Position = vec4(position.xy,0,1.0); + +} diff --git a/demos/assets/textures/atlas.png b/demos/assets/textures/atlas.png index a799eb7706feddc336f6f1e6e75f780dcbea5467..eea925e600dde7ea3133047620c037882fdd4b2c 100644 GIT binary patch literal 42614 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 diff --git a/demos/compile_android.py b/demos/compile_android.py index 2e3f967..75d6106 100644 --- a/demos/compile_android.py +++ b/demos/compile_android.py @@ -38,7 +38,7 @@ def compile(sources, output): clean = 0 ldc_flags = '--d-version=SDL_209 --d-version=BindSDL_Image --d-version=MM_USE_POSIX_THREADS ' #import_paths = ['source','tests'] -import_paths = ['external','external/sources', 'external/imports', 'external/wasm_imports', '../source', 'utils/source', 'simple/source'] +import_paths = ['external/android','external/sources', 'external/imports', 'external/wasm_imports', '../source', 'utils/source', 'simple/source'] build_tests = 0 for arg in sys.argv[1:]: diff --git a/demos/external/sources/glad/gl/loader.d b/demos/external/sources/glad/gl/loader.d index d640c61..8bc904f 100644 --- a/demos/external/sources/glad/gl/loader.d +++ b/demos/external/sources/glad/gl/loader.d @@ -35,14 +35,14 @@ bool open_gl() @nogc { return false; } else { version(OSX) { - enum const(char)*[] NAMES = [ + enum const(char)*[4] NAMES = [ "../Frameworks/OpenGL.framework/OpenGL", "/Library/Frameworks/OpenGL.framework/OpenGL", "/System/Library/Frameworks/OpenGL.framework/OpenGL", "/System/Library/Frameworks/OpenGL.framework/Versions/Current/OpenGL" ]; } else { - enum const(char)*[] NAMES = ["libGL.so.1", "libGL.so"]; + enum const(char)*[2] NAMES = ["libGL.so.1", "libGL.so"]; } foreach(name; NAMES) { diff --git a/demos/source/app.d b/demos/source/app.d index 003922b..808426f 100644 --- a/demos/source/app.d +++ b/demos/source/app.d @@ -4,10 +4,11 @@ import bindbc.sdl; import cimgui.cimgui; +import game_core.basic; import game_core.job_updater; -import bubel.ecs.manager; import bubel.ecs.core; +import bubel.ecs.manager; import bubel.ecs.std; import ecs_utils.gfx.renderer; @@ -21,6 +22,7 @@ import glad.gl.gles2; import glad.gl.loader; import gui.manager; +import gui.tool_circle; extern (C) : @@ -50,7 +52,7 @@ struct Launcher bool function() loop; void function() end; void function(SDL_Event*) event; - void function(vec2, Tool, int) tool; + //void function(vec2, Tool, int, bool) tool; float scalling; ivec2 window_size = ivec2(1024,768); Renderer renderer; @@ -65,9 +67,12 @@ struct Launcher vec2 render_position; Tool used_tool; - int tool_size = 0; + int tool_size = 100; float tool_repeat = 0; float repeat_time = 0; + bool tool_show = true; + bool tool_mode = true; + ToolCircle* tool_circle; bool swap_interval = true; @@ -93,7 +98,7 @@ struct Launcher float draw_time = 0; } - void switchDemo(void function() start, bool function() loop, void function() end, void function(SDL_Event*) event, void function(vec2, Tool, int) tool, const (char)* tips) + void switchDemo(void function() start, bool function() loop, void function() end, void function(SDL_Event*) event, const (char)* tips) { gui_manager.clear(); //launcher.ent @@ -117,7 +122,97 @@ struct Launcher this.end = end; this.event = event; this.tips = tips; - this.tool = tool; + //this.tool = tool; + } + + void processTool(vec2 position, bool mode) + { + static struct Iterator + { + float size2; + vec2 position; + ComponentRef[] add_comps; + ushort[] rem_comps; + + void removeEntity(IteratorSystem.EntitiesData data) + { + foreach(i;0..data.length) + { + vec2 rel_vec = data.location[i] - position; + float length = rel_vec.x * rel_vec.x + rel_vec.y * rel_vec.y; + if(length < size2)gEM.removeEntity(data.entity[i].id); + } + } + + void addComponent(IteratorSystem.EntitiesData data) + { + foreach(i;0..data.length) + { + vec2 rel_vec = data.location[i] - position; + float length = rel_vec.x * rel_vec.x + rel_vec.y * rel_vec.y; + if(length < size2)gEM.addComponents(data.entity[i].id, add_comps); + } + } + + void removeComponent(IteratorSystem.EntitiesData data) + { + foreach(i;0..data.length) + { + vec2 rel_vec = data.location[i] - position; + float length = rel_vec.x * rel_vec.x + rel_vec.y * rel_vec.y; + if(length < size2)gEM.removeComponents(data.entity[i].id, rem_comps); + } + } + } + + float half_size = tool_size * 0.5; + float size2 = half_size * half_size; + Iterator iterator; + iterator.size2 = size2; + iterator.position = position; + + switch(used_tool) + { + case Tool.entity_spawner: + if(mode) + { + if(gui_manager.templates.length == 0)return; + EntityTemplate* tmpl = gui_manager.getSelectedTemplate(); + CLocation* location = tmpl.getComponent!CLocation; + if(location) + { + position += randomCircularSample() * half_size; + if(position.y < 16)position.y = 16; + else if(position.y > 299)position.y = 299; + *location = position; + } + manager.addEntity(tmpl); + } + else + { + manager.callEntitiesFunction!IteratorSystem(&iterator.removeEntity); + } + break; + case Tool.component_manipulator: + { + if(gui_manager.components.length == 0)return; + if(mode) + { + ComponentRef[1] comps = [gui_manager.getSelectedComponent()]; + iterator.add_comps = comps; + manager.callEntitiesFunction!IteratorSystem(&iterator.addComponent); + } + else + { + ushort[1] comps = [gui_manager.getSelectedComponent().component_id]; + iterator.rem_comps = comps; + manager.callEntitiesFunction!IteratorSystem(&iterator.removeComponent); + } + } + break; + default: + break; + } } bool getKeyState(SDL_Scancode key) @@ -184,6 +279,28 @@ struct CleanSystem } } +struct IteratorSystem +{ + mixin ECS.System!1; + + struct EntitiesData + { + uint length; + const (Entity)[] entity; + CLocation[] location; + } + + bool onBegin() + { + return false; + } + + void onUpdate(EntitiesData) + { + + } +} + void mainLoop(void* arg) { __gshared double time = 0; @@ -237,9 +354,12 @@ void mainLoop(void* arg) case SDL_BUTTON_MIDDLE:launcher.mouse.middle = true;break; default:break; } - if(launcher.tool && event.button.button == SDL_BUTTON_LEFT && launcher.tool_repeat == 0 && !igIsWindowHovered(ImGuiHoveredFlags_AnyWindow)) + if(!igIsAnyItemHovered())igSetWindowFocus(); + if(!igIsWindowHovered(ImGuiHoveredFlags_AnyWindow) && !igIsWindowFocused(ImGuiFocusedFlags_AnyWindow)) { - launcher.tool(vec2(event.button.x, launcher.window_size.y - event.button.y) * launcher.scalling - launcher.render_position, launcher.used_tool, launcher.tool_size); + launcher.repeat_time = 0; + if(event.button.button == SDL_BUTTON_LEFT)launcher.processTool(vec2(event.button.x, launcher.window_size.y - event.button.y) * launcher.scalling - launcher.render_position,launcher.tool_mode); + else if(event.button.button == SDL_BUTTON_RIGHT)launcher.processTool(vec2(event.button.x, launcher.window_size.y - event.button.y) * launcher.scalling - launcher.render_position,!launcher.tool_mode); } } else if(event.type == SDL_MOUSEBUTTONUP) @@ -255,17 +375,52 @@ void mainLoop(void* arg) else if(event.type == SDL_MOUSEMOTION) { launcher.mouse.position = vec2(event.motion.x, launcher.window_size.y - event.motion.y); + }else if(event.type == SDL_MOUSEWHEEL) + { + if(!igIsAnyItemHovered() && !igIsWindowHovered(ImGuiHoveredFlags_AnyWindow)) + { + if(SDL_GetModState() & KMOD_CTRL) + { + float sign = 1; + if(event.wheel.y < 0)sign = -1; + float val = sign * event.wheel.y * launcher.tool_repeat * 0.25; + if(val < 0.1)val = 0.1; + launcher.tool_repeat -= sign * val; + if(launcher.tool_repeat < 0)launcher.tool_repeat = 0; + else if(launcher.tool_repeat > 1000)launcher.tool_repeat = 1000; + } + else + { + int sign = 1; + if(event.wheel.y < 0)sign = -1; + int val = sign * event.wheel.y * launcher.tool_size / 4; + if(val < 1)val = 1; + launcher.tool_size -= sign * val; + if(launcher.tool_size < 1)launcher.tool_size = 1; + else if(launcher.tool_size > 256)launcher.tool_size = 256; + } + } } } - if(launcher.tool && launcher.tool_repeat != 0 && launcher.mouse.left && !igIsWindowHovered(ImGuiHoveredFlags_AnyWindow) && !igIsWindowFocused(ImGuiFocusedFlags_AnyWindow)) + if(launcher.tool_repeat != 0 && (launcher.mouse.left || launcher.mouse.right) && !igIsWindowHovered(ImGuiHoveredFlags_AnyWindow) && !igIsWindowFocused(ImGuiFocusedFlags_AnyWindow)) { + bool mode = launcher.tool_mode; + if(launcher.mouse.right)mode = !mode; float range = 500.0 / cast(float)launcher.tool_repeat; launcher.repeat_time += launcher.delta_time; - while(launcher.repeat_time > range) + if(launcher.used_tool != Tool.entity_spawner || !mode) { - launcher.repeat_time -= range; - launcher.tool((launcher.mouse.position*launcher.scalling)-launcher.render_position, launcher.used_tool, launcher.tool_size); + if(launcher.repeat_time > range)launcher.processTool((launcher.mouse.position*launcher.scalling)-launcher.render_position, mode); + while(launcher.repeat_time > range)launcher.repeat_time -= range; + } + else + { + while(launcher.repeat_time > range) + { + launcher.repeat_time -= range; + launcher.processTool((launcher.mouse.position*launcher.scalling)-launcher.render_position, mode); + } } } @@ -300,17 +455,22 @@ void mainLoop(void* arg) if(igMenuItemBool("Simpe",null,false,true)) { import demos.simple; - launcher.switchDemo(&simpleStart,&simpleLoop,&simpleEnd,&simpleEvent,&simpleTool,Simple.tips); + launcher.switchDemo(&simpleStart,&simpleLoop,&simpleEnd,&simpleEvent,Simple.tips); } if(igMenuItemBool("Snake",null,false,true)) { import demos.snake; - launcher.switchDemo(&snakeStart,&snakeLoop,&snakeEnd,&snakeEvent,&snakeTool,Snake.tips); + launcher.switchDemo(&snakeStart,&snakeLoop,&snakeEnd,&snakeEvent,Snake.tips); } if(igMenuItemBool("Space invaders",null,false,true)) { import demos.space_invaders; - launcher.switchDemo(&spaceInvadersStart,&spaceInvadersLoop,&spaceInvadersEnd,&spaceInvadersEvent,&spaceInvadersTool,SpaceInvaders.tips); + launcher.switchDemo(&spaceInvadersStart,&spaceInvadersLoop,&spaceInvadersEnd,&spaceInvadersEvent,SpaceInvaders.tips); + } + if(igMenuItemBool("Particles",null,false,true)) + { + import demos.particles; + launcher.switchDemo(&particlesStart,&particlesLoop,&particlesEnd,&particlesEvent,ParticlesDemo.tips); } igEndMenu(); } @@ -463,20 +623,22 @@ void mainLoop(void* arg) if(launcher.show_demo_wnd) { igSetNextWindowPos(ImVec2(launcher.window_size.x - 260, 30), ImGuiCond_Once, ImVec2(0,0)); - igSetNextWindowSize(ImVec2(250, 500), ImGuiCond_Once); + igSetNextWindowSize(ImVec2(250, launcher.window_size.y - 60), ImGuiCond_Once); if(igBegin("Demo",&launcher.show_demo_wnd,0)) { ImDrawList* draw_list = igGetWindowDrawList(); - //igBeginGroup(); + igBeginGroup(); launcher.gui_manager.gui(); - //igEndGroup(); + igEndGroup(); + ImDrawList_AddRect(draw_list, igGetItemRectMin(), ImVec2(igGetWindowPos().x+igGetWindowWidth()-2,igGetItemRectMax().y), igColorConvertFloat4ToU32(ImVec4(0.4,0.4,0.4,0.4)), 4, ImDrawCornerFlags_All, 1); + //ImDrawList_AddRect(draw_list, igGetItemRectMin(), igGetItemRectMax(), igColorConvertFloat4ToU32(ImVec4(0.4,0.4,0.4,0.4)), -1, 0, 1); //igBeginChildFrame(1,ImVec2(0,-1),ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_ChildWindow); //igBeginChild("Tool frame",ImVec2(-1,-1),true,0); - if(igCollapsingHeader("Tool##ToolHeader", ImGuiTreeNodeFlags_SpanAvailWidth)) + igBeginGroup(); + if(igCollapsingHeader("Tool##ToolHeader", ImGuiTreeNodeFlags_SpanAvailWidth | ImGuiTreeNodeFlags_DefaultOpen)) { igIndent(8); - igBeginGroup(); if(igBeginCombo("Tool",tool_strings[launcher.used_tool],0)) { if(igSelectable("Entity spawner",false,0,ImVec2(0,0))) @@ -493,14 +655,20 @@ void mainLoop(void* arg) } igEndCombo(); } + igCheckbox("Show Tool", &launcher.tool_show); + //igSelectable("Selectabe",false,ImGuiSelectableFlags_None,ImVec2(0,0)); + if(igRadioButtonBool("Add", launcher.tool_mode))launcher.tool_mode = true; + igSameLine(0,0); + if(igRadioButtonBool("Remove", !launcher.tool_mode))launcher.tool_mode = false; igSliderInt("Tool size", &launcher.tool_size, 0, 256, null); igSliderFloat("Tool repeat", &launcher.tool_repeat, 0, 1024, null, 4); launcher.gui_manager.toolGui(); - igEndGroup(); - ImDrawList_AddRect(draw_list, igGetItemRectMin(), igGetItemRectMax(), igColorConvertFloat4ToU32(ImVec4(0.4,0.4,0.4,0.4)), 4, ImDrawCornerFlags_All, 1); igUnindent(8); } + igEndGroup(); + ImDrawList_AddRect(draw_list, igGetItemRectMin(), ImVec2(igGetWindowPos().x+igGetWindowWidth()-2,igGetItemRectMax().y), igColorConvertFloat4ToU32(ImVec4(0.4,0.4,0.4,0.4)), 2, ImDrawCornerFlags_All, 1); + //igEndChild(); //igEndChildFrame(); @@ -516,7 +684,8 @@ void mainLoop(void* arg) if(launcher.show_profile_wnd) { - igSetNextWindowPos(ImVec2(launcher.window_size.x - 260, launcher.window_size.y - 280), ImGuiCond_Once, ImVec2(0,0)); + //igSetNextWindowPos(ImVec2(launcher.window_size.x - 260, launcher.window_size.y - 280), ImGuiCond_Once, ImVec2(0,0)); + igSetNextWindowPos(ImVec2(8, launcher.window_size.y - 258), ImGuiCond_Once, ImVec2(0,0)); igSetNextWindowSize(ImVec2(250, 250), ImGuiCond_Once); if(igBegin("Profile",&launcher.show_profile_wnd,0)) { @@ -577,6 +746,10 @@ void mainLoop(void* arg) launcher.renderer.present(); draw_time = launcher.getTime() - draw_time; + //import std.stdio; + //printf("Scalling: %f",launcher.scalling); + if(launcher.tool_show)launcher.tool_circle.draw(&launcher.renderer, (launcher.mouse.position*launcher.scalling)-launcher.render_position, cast(float)launcher.tool_size, launcher.renderer.view_size.y*6*launcher.scalling); + __gshared float plot_time = 0; __gshared uint plot_samples = 0; plot_time += launcher.delta_time; @@ -703,6 +876,7 @@ int app_main(int argc, char** argv) SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 0); launcher.window = SDL_CreateWindow("Simple", SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED, launcher.window_size.x, launcher.window_size.y, SDL_WINDOW_SHOWN | SDL_WINDOW_OPENGL | SDL_WINDOW_RESIZABLE); + SDL_MaximizeWindow(launcher.window); launcher.gl_context = SDL_GL_CreateContext(launcher.window); launcher.context = igCreateContext(null); @@ -778,8 +952,11 @@ int app_main(int argc, char** argv) launcher.manager.registerPass("clean"); + launcher.manager.registerComponent!CLocation; + launcher.manager.registerSystem!CountSystem(10000); launcher.manager.registerSystem!CleanSystem(0,"clean"); + launcher.manager.registerSystem!IteratorSystem(0,"clean"); launcher.manager.endRegister(); @@ -796,8 +973,10 @@ int app_main(int argc, char** argv) { import demos.simple; import demos.space_invaders; - // launcher.switchDemo(&spaceInvadersStart,&spaceInvadersLoop,&spaceInvadersEnd,&spaceInvadersEvent,&spaceInvadersTool,SpaceInvaders.tips); - launcher.switchDemo(&simpleStart,&simpleLoop,&simpleEnd,&simpleEvent,&simpleTool,Simple.tips); + import demos.particles; + // launcher.switchDemo(&spaceInvadersStart,&spaceInvadersLoop,&spaceInvadersEnd,&spaceInvadersEvent,SpaceInvaders.tips); + // launcher.switchDemo(&particlesStart,&particlesLoop,&particlesEnd,&particlesEvent,ParticlesDemo.tips); + launcher.switchDemo(&simpleStart,&simpleLoop,&simpleEnd,&simpleEvent,Simple.tips); } int key_num; @@ -842,7 +1021,7 @@ void loadGFX() Texture.__loadBackend(); Renderer.__loadBackend(); - GfxConfig.materials = Mallocator.makeArray!Material(1); + GfxConfig.materials = Mallocator.makeArray!Material(3); GfxConfig.meshes = Mallocator.makeArray!Mesh(1); float[16] vertices = [-0.5,-0.5, 0,1, -0.5,0.5, 0,0, 0.5,-0.5, 1,1, 0.5,0.5, 1,0]; @@ -889,7 +1068,84 @@ void loadGFX() GfxConfig.materials[0].data.bindings[0] = GfxConfig.materials[0].getLocation("tex"); + Shader vsh2; + vsh2.create(); + vsh2.load("assets/shaders/circle.vp"); + vsh2.compile(); + + Shader fsh2; + fsh2.create(); + fsh2.load("assets/shaders/circle.fp"); + fsh2.compile(); + + GfxConfig.materials[1].create(); + GfxConfig.materials[1].data.blend_mode = Material.BlendMode.mixed; + GfxConfig.materials[1].data.mode = Material.TransformMode.position; + Material.ShaderModule[1] modules2 = [Material.ShaderModule(vsh2,fsh2)]; + GfxConfig.materials[1].attachModules(modules2); + //GfxConfig.materials[0]. + //GfxConfig.materials[0].load(load_data.materials[i].str); + GfxConfig.materials[1].compile(); + GfxConfig.materials[1].bindAttribLocation("positions",0); + //GfxConfig.materials[1].bindAttribLocation("tex_coords",1); + //GfxConfig.materials[1].bindAttribLocation("depth",2); + //GfxConfig.materials[1].bindAttribLocation("vcolor",3); + GfxConfig.materials[1].link(); + + /* import std.stdio; + writeln("positions ",glGetAttribLocation(GfxConfig.materials[0].data.modules[0].gl_handle,"positions")); + writeln("tex_coords ",glGetAttribLocation(GfxConfig.materials[0].data.modules[0].gl_handle,"tex_coords")); + writeln("depth ",glGetAttribLocation(GfxConfig.materials[0].data.modules[0].gl_handle,"depth")); + writeln("vcolor ",glGetAttribLocation(GfxConfig.materials[0].data.modules[0].gl_handle,"vcolor"));*/ + + GfxConfig.materials[1].data.uniforms = Mallocator.makeArray!(Material.Uniform)(2); + GfxConfig.materials[1].data.uniforms[0] = Material.Uniform(Material.Type.float4, GfxConfig.materials[1].getLocation("matrix_1"), 0); + GfxConfig.materials[1].data.uniforms[1] = Material.Uniform(Material.Type.float4, GfxConfig.materials[1].getLocation("matrix_2"), 16); + //GfxConfig.materials[1].data.uniforms[2] = Material.Uniform(Material.Type.float4, GfxConfig.materials[0].getLocation("uv_transform"), 32); + //GfxConfig.materials[1].data.bindings = Mallocator.makeArray!(int)(1); + //GfxConfig.materials[1].data.bindings[0] = GfxConfig.materials[0].getLocation("tex"); + + + + + + + GfxConfig.materials[2].create(); + GfxConfig.materials[2].data.blend_mode = Material.BlendMode.opaque; + GfxConfig.materials[2].data.mode = Material.TransformMode.position; + //Material.ShaderModule[1] modules = [Material.ShaderModule(vsh,fsh)]; + GfxConfig.materials[2].attachModules(modules); + //GfxConfig.materials[0]. + //GfxConfig.materials[0].load(load_data.materials[i].str); + GfxConfig.materials[2].compile(); + GfxConfig.materials[2].bindAttribLocation("positions",0); + GfxConfig.materials[2].bindAttribLocation("tex_coords",1); + GfxConfig.materials[2].bindAttribLocation("depth",2); + GfxConfig.materials[2].bindAttribLocation("vcolor",3); + GfxConfig.materials[2].link(); + + /* import std.stdio; + writeln("positions ",glGetAttribLocation(GfxConfig.materials[0].data.modules[0].gl_handle,"positions")); + writeln("tex_coords ",glGetAttribLocation(GfxConfig.materials[0].data.modules[0].gl_handle,"tex_coords")); + writeln("depth ",glGetAttribLocation(GfxConfig.materials[0].data.modules[0].gl_handle,"depth")); + writeln("vcolor ",glGetAttribLocation(GfxConfig.materials[0].data.modules[0].gl_handle,"vcolor"));*/ + + GfxConfig.materials[2].data.uniforms = Mallocator.makeArray!(Material.Uniform)(3); + GfxConfig.materials[2].data.uniforms[0] = Material.Uniform(Material.Type.float4, GfxConfig.materials[0].getLocation("matrix_1"), 0); + GfxConfig.materials[2].data.uniforms[1] = Material.Uniform(Material.Type.float4, GfxConfig.materials[0].getLocation("matrix_2"), 16); + GfxConfig.materials[2].data.uniforms[2] = Material.Uniform(Material.Type.float4, GfxConfig.materials[0].getLocation("uv_transform"), 32); + GfxConfig.materials[2].data.bindings = Mallocator.makeArray!(int)(1); + GfxConfig.materials[2].data.bindings[0] = GfxConfig.materials[0].getLocation("tex"); + GfxConfig.materials[2].data.blend_mode = Material.BlendMode.additive; + + + + + + /*glUseProgram(0); glBindBuffer(GL_ARRAY_BUFFER, 0); glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);*/ + + launcher.tool_circle = Mallocator.make!ToolCircle; } \ No newline at end of file diff --git a/demos/source/demos/particles.d b/demos/source/demos/particles.d new file mode 100644 index 0000000..6ed8ea7 --- /dev/null +++ b/demos/source/demos/particles.d @@ -0,0 +1,545 @@ +module demos.particles; + +import app; + +import bindbc.sdl; + +import cimgui.cimgui; + +import bubel.ecs.attributes; +import bubel.ecs.core; +import bubel.ecs.entity; +import bubel.ecs.manager; +import bubel.ecs.std; + +import ecs_utils.gfx.texture; +import ecs_utils.math.vector; +import ecs_utils.utils; + +import game_core.basic; + +import gui.attributes; + +extern(C): + +private enum float px = 1.0/512.0; + +/*####################################################################################################################### +------------------------------------------------ Components ------------------------------------------------------------------ +#######################################################################################################################*/ + +/*struct CLocation +{ + mixin ECS.Component; + + alias location this; + + vec2 location; +}*/ + +struct CTexCoords +{ + mixin ECS.Component; + + vec4 value; +} + +struct CColor +{ + mixin ECS.Component; + + alias value this; + + @GUIColor uint value = uint.max; +} + +struct CVelocity +{ + mixin ECS.Component; + + alias value this; + + vec2 value = vec2(0); +} + +struct CForceRange +{ + mixin ECS.Component; + + vec2 range = vec2(20,200); +} + +struct CAttractor +{ + mixin ECS.Component; + + //alias value this; + float strength = 0.2; +} + +struct CVortex +{ + mixin ECS.Component; + + float strength = 0.6; +} + +struct CDamping +{ + mixin ECS.Component; + + alias power this; + + @GUIRange(0,9) ubyte power = 0; +} + +struct CGravity +{ + mixin ECS.Component; +} + +struct CParticleLife +{ + mixin ECS.Component; + + this(float life_in_secs) + { + life = cast(int)(life_in_secs * 1000_000); + } + + alias life this; + + int life = 1000000; +} + +/*####################################################################################################################### +------------------------------------------------ Systems ------------------------------------------------------------------ +#######################################################################################################################*/ + +struct DrawSystem +{ + mixin ECS.System!32; + + struct EntitiesData + { + uint length; + //uint thread_id; + uint job_id; + //@readonly CTexCoords[] coords; + @readonly CLocation[] locations; + + @optional @readonly CColor[] color; + } + + void onUpdate(EntitiesData data) + { + if(launcher.renderer.prepared_items >= launcher.renderer.MaxObjects)return;//simple leave loop if max visible objects count was reached + + if(!data.color) + { + foreach(i; 0..data.length) + { + launcher.renderer.draw(particles_demo.texture, data.locations[i], vec2(2,2), vec4(246,64,2,2)*px, 0, 0x80808080, 0, 2, 0, data.job_id); + } + } + else + { + foreach(i; 0..data.length) + { + launcher.renderer.draw(particles_demo.texture, data.locations[i], vec2(2,2), vec4(246,64,2,2)*px, 0, data.color[i].value, 0, 2, 0, data.job_id); + } + } + + } +} + +struct MoveSystem +{ + mixin ECS.System!64; + + struct EntitiesData + { + uint length; + CLocation[] locations; + @readonly CVelocity[] velocity; + } + + void onUpdate(EntitiesData data) + { + foreach(i; 0..data.length) + { + data.locations[i] += data.velocity[i] * launcher.delta_time; + } + } +} + +struct MouseAttractSystem +{ + mixin ECS.System!64; + + struct EntitiesData + { + uint length; + @readonly CLocation[] locations; + CVelocity[] velocity; + } + + vec2 mouse_pos; + + bool onBegin() + { + if(!launcher.getKeyState(SDL_SCANCODE_SPACE))return false; + mouse_pos = launcher.mouse.position; + mouse_pos = vec2(mouse_pos.x, mouse_pos.y) * launcher.scalling - launcher.render_position; + return true; + } + + void onUpdate(EntitiesData data) + { + float speed = launcher.delta_time * 0.01; + foreach(i;0..data.length) + { + vec2 rel_pos = mouse_pos - data.locations[i]; + float len2 = rel_pos.x * rel_pos.x + rel_pos.y * rel_pos.y; + if(len2 < 0.1)len2 = 0.1; + data.velocity[i] = data.velocity[i] + rel_pos / len2 * speed; + } + } +} + +struct AttractSystem +{ + mixin ECS.System!64; + + struct EntitiesData + { + uint length; + @readonly CLocation[] locations; + CVelocity[] velocity; + } + + struct Updater + { + AttractSystem.EntitiesData data; + + void onUpdate(AttractorIterator.EntitiesData adata) + { + float speed = launcher.delta_time * 0.00004; + if(adata.vortex) + { + foreach(i;0..data.length) + { + foreach(j;0..adata.length) + { + vec2 rel_pos = data.locations[i] - adata.locations[j]; + float len2 = rel_pos.length2(); + float inv_len = rsqrt(len2); + + if(1 < adata.force_range[j].range.y*inv_len) + { + float dist = (adata.force_range[j].range.y - 0.4)*inv_len - 1; + + vec2 vec = rel_pos * inv_len; + vec2 cvec = vec2(-vec.y,vec.x); + + float sign = -1; + if(1 < adata.force_range[j].range.x*inv_len)sign = 1; + + float str = adata.attractor[j].strength * sign; + float vortex_str = adata.vortex[j].strength; + data.velocity[i] = data.velocity[i] + (rel_pos * str + cvec * vortex_str) * speed * dist; + } + } + } + } + else + { + foreach(i;0..data.length) + { + foreach(j;0..adata.length) + { + vec2 rel_pos = data.locations[i] - adata.locations[j]; + float len2 = rel_pos.length2(); + float inv_len = rsqrt(len2); + + if(1 < adata.force_range[j].range.y*inv_len) + { + float dist = (adata.force_range[j].range.y - 0.4)*inv_len - 1; + + vec2 vec = rel_pos; + + float sign = -1; + if(1 < adata.force_range[j].range.x*inv_len)sign = 1; + + float str = adata.attractor[j].strength * speed * dist * sign; + data.velocity[i] = data.velocity[i] + vec * str; + } + } + } + } + } + } + + void onUpdate(EntitiesData data) + { + Updater updater; + updater.data = data; + launcher.manager.callEntitiesFunction!AttractorIterator(&updater.onUpdate); + } +} + +struct AttractorIterator +{ + mixin ECS.System!1; + + struct EntitiesData + { + uint length; + @readonly CLocation[] locations; + @readonly CAttractor[] attractor; + @readonly CForceRange[] force_range; + @optional @readonly CVortex[] vortex; + } + + bool onBegin() + { + return false; + } + + void onUpdate(EntitiesData data) + { + + } +} + +struct PlayAreaSystem +{ + mixin ECS.System!32; + + struct EntitiesData + { + uint length; + Entity[] entity; + @readonly CLocation[] locations; + } + + void onUpdate(EntitiesData data) + { + foreach(i; 0..data.length) + { + if(data.locations[i].x > 400)launcher.manager.removeEntity(data.entity[i].id); + else if(data.locations[i].x < 0)launcher.manager.removeEntity(data.entity[i].id); + if(data.locations[i].y > 300)launcher.manager.removeEntity(data.entity[i].id); + else if(data.locations[i].y < 0)launcher.manager.removeEntity(data.entity[i].id); + } + } +} + +struct DampingSystem +{ + mixin ECS.System!32; + + struct EntitiesData + { + uint length; + const (Entity)[] entity; + @readonly CDamping[] damping; + CVelocity[] velocity; + } + + float[10] damp = 0; + + bool onBegin() + { + foreach(i;0..10) + { + damp[i] = powf((0.99 - cast(float)i * 0.01),launcher.delta_time*0.1); + } + + return true; + } + + void onUpdate(EntitiesData data) + { + foreach(i; 0..data.length) + { + data.velocity[i] = data.velocity[i] * damp[data.damping[i]]; + } + } +} + +struct ParticleLifeSystem +{ + mixin ECS.System!32; + + struct EntitiesData + { + uint length; + const (Entity)[] entity; + CParticleLife[] life; + } + + int delta_time; + + bool onBegin() + { + delta_time = cast(int)(launcher.delta_time * 1000); + return true; + } + + void onUpdate(EntitiesData data) + { + foreach(i; 0..data.length) + { + data.life[i] -= delta_time; + if(data.life[i] < 0)launcher.manager.removeEntity(data.entity[i].id); + } + } +} + +struct GravitySystem +{ + mixin ECS.System!32; + + struct EntitiesData + { + uint length; + const (Entity)[] entity; + @readonly CGravity[] gravity; + CVelocity[] velocity; + } + + void onUpdate(EntitiesData data) + { + float delta_time = launcher.delta_time * 0.00_092; + foreach(i; 0..data.length) + { + data.velocity[i].y -= delta_time; + } + } +} + +/*####################################################################################################################### +------------------------------------------------ Functions ------------------------------------------------------------------ +#######################################################################################################################*/ + +struct ParticlesDemo +{ + __gshared const (char)* tips = "Use \"space\" to spwan entities.\n\nSystems can be enabled/disabled from \"Simple\" window."; + + Texture texture; +} + +__gshared ParticlesDemo* particles_demo; + +void particlesStart() +{ + particles_demo = Mallocator.make!ParticlesDemo; + + particles_demo.texture.create(); + particles_demo.texture.load("assets/textures/atlas.png"); + + launcher.manager.beginRegister(); + + launcher.manager.registerComponent!CLocation; + launcher.manager.registerComponent!CTexCoords; + launcher.manager.registerComponent!CColor; + launcher.manager.registerComponent!CVelocity; + launcher.manager.registerComponent!CAttractor; + launcher.manager.registerComponent!CDamping; + launcher.manager.registerComponent!CGravity; + launcher.manager.registerComponent!CVortex; + launcher.manager.registerComponent!CParticleLife; + launcher.manager.registerComponent!CForceRange; + + launcher.manager.registerSystem!MoveSystem(0); + launcher.manager.registerSystem!DrawSystem(100); + launcher.manager.registerSystem!PlayAreaSystem(102); + launcher.manager.registerSystem!AttractSystem(-1); + launcher.manager.registerSystem!MouseAttractSystem(1); + launcher.manager.registerSystem!DampingSystem(101); + launcher.manager.registerSystem!ParticleLifeSystem(-10); + launcher.manager.registerSystem!GravitySystem(-2); + + launcher.manager.registerSystem!AttractorIterator(-1); + + launcher.manager.endRegister(); + + launcher.gui_manager.addSystem(MoveSystem.system_id,"Move System"); + launcher.gui_manager.addSystem(DrawSystem.system_id,"Draw System"); + launcher.gui_manager.addSystem(PlayAreaSystem.system_id,"Play Area System"); + launcher.gui_manager.addSystem(AttractSystem.system_id,"Attract System"); + launcher.gui_manager.addSystem(MouseAttractSystem.system_id,"Mouse Attract System"); + launcher.gui_manager.addSystem(DampingSystem.system_id,"Damping System"); + launcher.gui_manager.addSystem(ParticleLifeSystem.system_id,"Particle Life System"); + + launcher.gui_manager.addComponent(CColor(),"Color (white)"); + launcher.gui_manager.addComponent(CColor(0xFF101540),"Color (red)"); + launcher.gui_manager.addComponent(CColor(0xFF251010),"Color (blue)"); + launcher.gui_manager.addComponent(CColor(0xFF102010),"Color (green)"); + launcher.gui_manager.addComponent(CAttractor(0.1),"Attractor (str 0.1)"); + launcher.gui_manager.addComponent(CForceRange(vec2(5,40)),"ForceRange (5,40)"); + launcher.gui_manager.addComponent(CVelocity(),"Velocity"); + launcher.gui_manager.addComponent(CDamping(),"Damping"); + launcher.gui_manager.addComponent(CVortex(),"Vortex"); + launcher.gui_manager.addComponent(CParticleLife(),"Particle Life"); + launcher.gui_manager.addComponent(CGravity(),"Gravity"); + + EntityTemplate* tmpl; + EntityTemplate* base_tmpl = launcher.manager.allocateTemplate([CLocation.component_id, CColor.component_id, CVelocity.component_id, CDamping.component_id].staticArray); + launcher.gui_manager.addTemplate(base_tmpl,"Particle"); + tmpl = launcher.manager.allocateTemplate(base_tmpl); + tmpl.getComponent!CColor().value = 0xFF251010; + launcher.gui_manager.addTemplate(tmpl,"Particle (blue)"); + tmpl = launcher.manager.allocateTemplate(base_tmpl); + tmpl.getComponent!CColor().value = 0xFF102010; + launcher.gui_manager.addTemplate(tmpl,"Particle (green)"); + tmpl = launcher.manager.allocateTemplate(base_tmpl); + tmpl.getComponent!CColor().value = 0xFF101540; + launcher.gui_manager.addTemplate(tmpl,"Particle (red)"); + // tmpl = launcher.manager.allocateTemplate(tmpl, [CDamping.component_id].staticArray); + // launcher.gui_manager.addTemplate(tmpl,"Particle (damping)"); + tmpl = launcher.manager.allocateTemplate(tmpl); + tmpl.getComponent!CDamping().power = 4; + launcher.gui_manager.addTemplate(tmpl,"Particle (damping!)"); + tmpl = launcher.manager.allocateTemplate([CAttractor.component_id, CLocation.component_id, CForceRange.component_id].staticArray); + launcher.gui_manager.addTemplate(tmpl,"Attractor"); + tmpl = launcher.manager.allocateTemplate(tmpl, [CVortex.component_id].staticArray); + launcher.gui_manager.addTemplate(tmpl,"Vortex"); + tmpl = launcher.manager.allocateTemplate(tmpl); + tmpl.getComponent!CVortex().strength = -0.6; + launcher.gui_manager.addTemplate(tmpl,"Vortex (reversed)"); + +} + +void particlesEnd() +{ + particles_demo.texture.destroy(); + + //launcher.manager.freeTemplate(simple.tmpl); + Mallocator.dispose(particles_demo); +} + +void particlesEvent(SDL_Event* event) +{ +} + +bool particlesLoop() +{ + launcher.render_position = (vec2(launcher.window_size.x,launcher.window_size.y)*launcher.scalling - vec2(400,300)) * 0.5; + + launcher.manager.begin(); + if(launcher.multithreading) + { + launcher.job_updater.begin(); + launcher.manager.updateMT(); + launcher.job_updater.call(); + } + else + { + launcher.manager.update(); + } + launcher.manager.end(); + + return true; +} \ No newline at end of file diff --git a/demos/source/demos/simple.d b/demos/source/demos/simple.d index 313cc00..ac3b3f7 100644 --- a/demos/source/demos/simple.d +++ b/demos/source/demos/simple.d @@ -4,46 +4,38 @@ import app; import bindbc.sdl; -import cimgui.cimgui; - import bubel.ecs.attributes; import bubel.ecs.core; import bubel.ecs.entity; import bubel.ecs.manager; import bubel.ecs.std; +import cimgui.cimgui; + import ecs_utils.gfx.texture; import ecs_utils.math.vector; import ecs_utils.utils; +import game_core.basic; + extern(C): -struct Simple -{ - __gshared const (char)* tips = "Use \"space\" to spwan entities.\n\nSystems can be enabled/disabled from \"Simple\" window."; +/*####################################################################################################################### +------------------------------------------------ Components ------------------------------------------------------------------ +#######################################################################################################################*/ - EntityTemplate* tmpl; - Texture texture; - - bool move_system = true; - bool draw_system = true; -} - -struct CLocation +/*struct CLocation { mixin ECS.Component; alias location this; vec2 location; -} +}*/ -struct CTexture -{ - mixin ECS.Component; - - Texture tex; -} +/*####################################################################################################################### +------------------------------------------------ Systems ------------------------------------------------------------------ +#######################################################################################################################*/ struct DrawSystem { @@ -54,7 +46,6 @@ struct DrawSystem uint length; //uint thread_id; uint job_id; - @readonly CTexture[] textures; @readonly CLocation[] locations; } @@ -63,7 +54,7 @@ struct DrawSystem if(launcher.renderer.prepared_items >= launcher.renderer.MaxObjects)return;//simple leave loop if max visible objects count was reached foreach(i; 0..data.length) { - launcher.renderer.draw(data.textures[i].tex, data.locations[i].location, vec2(16,16), vec4(0,0,1,1), cast(ushort)(data.locations[i].y), 0x80808080, 0, 0, 0, data.job_id); + launcher.renderer.draw(simple.texture, data.locations[i], vec2(16,16), vec4(0,0,1,1), cast(ushort)(data.locations[i].y), 0x80808080, 0, 0, 0, data.job_id); // launcher.renderer.draw(data.textures[i].tex, data.locations[i].location, vec2(16,16), vec4(0,0,1,1), 0, 0x80808080, 0, 0, 0, data.job_id); //draw(renderer, data.textures[i].tex, data.locations[i], vec2(32,32), vec4(0,0,1,1)); } @@ -85,12 +76,24 @@ struct MoveSystem { foreach(i; 0..data.length) { - data.locations[i].location.y = data.locations[i].location.y + 1; - if(data.locations[i].location.y > 300)data.locations[i].location.y = 0; + data.locations[i].y = data.locations[i].y + 1; + if(data.locations[i].y > 300)data.locations[i].y = 0; } } } +/*####################################################################################################################### +------------------------------------------------ Functions ------------------------------------------------------------------ +#######################################################################################################################*/ + +struct Simple +{ + __gshared const (char)* tips = "Use \"space\" to spwan entities.\n\nSystems can be enabled/disabled from \"Simple\" window."; + + EntityTemplate* tmpl; + Texture texture; +} + __gshared Simple* simple; void simpleStart() @@ -103,7 +106,6 @@ void simpleStart() launcher.manager.beginRegister(); launcher.manager.registerComponent!CLocation; - launcher.manager.registerComponent!CTexture; launcher.manager.registerSystem!MoveSystem(0); launcher.manager.registerSystem!DrawSystem(1); @@ -113,19 +115,17 @@ void simpleStart() launcher.gui_manager.addSystem(MoveSystem.system_id,"Move System"); launcher.gui_manager.addSystem(DrawSystem.system_id,"Draw System"); - ushort[2] components = [CLocation.component_id, CTexture.component_id]; + ushort[1] components = [CLocation.component_id]; simple.tmpl = launcher.manager.allocateTemplate(components); - CTexture* tex_comp = simple.tmpl.getComponent!CTexture; - tex_comp.tex = simple.texture; - CLocation* loc_comp = simple.tmpl.getComponent!CLocation; + //CLocation* loc_comp = simple.tmpl.getComponent!CLocation; launcher.gui_manager.addTemplate(simple.tmpl, "Basic"); foreach(i; 0..10) foreach(j; 0..10) { - loc_comp.location = vec2(i*16+64,j*16+64); - launcher.manager.addEntity(simple.tmpl); + //loc_comp.value = vec2(i*16+64,j*16+64); + launcher.manager.addEntity(simple.tmpl,[CLocation(vec2(i*16+64,j*16+64)).ref_].staticArray); } } @@ -140,46 +140,15 @@ void simpleEnd() Mallocator.dispose(simple); } -void simpleTool(vec2 position, Tool tool, int size) -{ - switch(tool) - { - case Tool.entity_spawner: - { - EntityTemplate* tmpl = launcher.gui_manager.getSelectedTemplate(); - CLocation* location = tmpl.getComponent!CLocation; - if(location) - { - position.x += (randomf - 0.5) * size; - position.y += (randomf - 0.5) * size; - if(position.x > 400)position.x -= 400; - else if(position.x < 0)position.x += 400; - if(position.y > 300)position.y -= 300; - else if(position.y < 0)position.y += 300; - *location = position; - } - launcher.manager.addEntity(tmpl); - } - break; - default: - break; - } -} - void simpleEvent(SDL_Event* event) { - /*if(event.type == event.button) - { - vec2 position = vec2(event.button.x, event.button.y); - - }*/ } void spawnEntity() { - CLocation* loc_comp = simple.tmpl.getComponent!CLocation; - loc_comp.location = vec2(randomf() * 400,0); - launcher.manager.addEntity(simple.tmpl); + //CLocation* loc_comp = simple.tmpl.getComponent!CLocation; + //loc_comp.value = vec2(randomf() * 400,0); + launcher.manager.addEntity(simple.tmpl,[CLocation(vec2(randomf() * 400,0)).ref_].staticArray); } bool simpleLoop() @@ -188,7 +157,7 @@ bool simpleLoop() if(launcher.getKeyState(SDL_SCANCODE_SPACE)) { - foreach(i;0..1)spawnEntity(); + foreach(i;0..20)spawnEntity(); } launcher.manager.begin(); diff --git a/demos/source/demos/snake.d b/demos/source/demos/snake.d index 01e9a36..f46009c 100644 --- a/demos/source/demos/snake.d +++ b/demos/source/demos/snake.d @@ -4,8 +4,6 @@ import app; import bindbc.sdl; -import cimgui.cimgui; - import bubel.ecs.attributes; import bubel.ecs.core; import bubel.ecs.entity; @@ -13,10 +11,14 @@ import bubel.ecs.manager; import bubel.ecs.std; import bubel.ecs.vector; +import cimgui.cimgui; + import ecs_utils.gfx.texture; import ecs_utils.math.vector; import ecs_utils.utils; +import game_core.basic; + //import std.array : staticArray; enum float px = 1.0/512.0; @@ -31,22 +33,6 @@ struct MapElement apple = 1, wall = 2, snake = 3, - - /* snake_head_up = 5, - snake_head_down = 6, - snake_head_left = 7, - snake_head_right = 8, - snake_tail_up = 9, - snake_tail_down = 10, - snake_tail_left = 11, - snake_tail_right = 12, - snake_turn_ld = 13, - snake_turn_lu = 14, - snake_turn_rd = 15, - snake_turn_ru = 16, - snake_vertical = 17, - snake_horizontal = 18*/ - } Type type; EntityID id; @@ -129,10 +115,7 @@ struct Snake } if(base_pos.x == random_pos.x && base_pos.y == random_pos.y)return; } - //CILocation* location = apple_tmpl.getComponent!CILocation; - //*location = random_pos; - //Entity* apple = - launcher.manager.addEntity(apple_tmpl,[CILocation(random_pos).ref_].staticArray); + launcher.manager.addEntity(apple_tmpl,[CLocation(cast(vec2)(random_pos)*16).ref_].staticArray); } } @@ -158,14 +141,14 @@ struct CILocation ivec2 location; } -struct CLocation -{ - mixin ECS.Component; +// struct CLocation +// { +// mixin ECS.Component; - alias location this; +// alias location this; - vec2 location = vec2(0,0); -} +// vec2 location = vec2(0,0); +// } struct CSnake { @@ -264,8 +247,8 @@ struct AppleSystem struct EntitiesData { uint length; - @readonly Entity[] entities; - @readonly CApple[] movement; + @readonly Entity[] entity; + @readonly CApple[] apple; @readonly CILocation[] location; } @@ -273,7 +256,17 @@ struct AppleSystem { foreach(i;0..data.length) { - snake.element(MapElement(MapElement.Type.apple,data.entities[i].id),data.location[i]); + if(snake.element(data.location[i]).id == EntityID())snake.element(MapElement(MapElement.Type.apple,data.entity[i].id),data.location[i]); + else launcher.manager.removeEntity(data.entity[i].id); + } + } + + void onRemoveEntity(EntitiesData data) + { + foreach(i;0..data.length) + { + if(snake.element(data.location[i].location).id == data.entity[i].id) + snake.element(MapElement(MapElement.Type.empty, EntityID()),data.location[i].location); } } } @@ -315,7 +308,7 @@ struct ParticleMovementSystem { foreach(i;0..data.length) { - data.location[i].location -= data.movement[i].velocity; + data.location[i] -= data.movement[i].velocity; } } } @@ -357,7 +350,7 @@ struct AnimationRenderSystem { foreach(i;0..data.length) { - launcher.renderer.draw(snake.texture, cast(vec2)cast(ivec2)data.location[i].location, vec2(16,16), data.animation[i].frames[cast(int)(data.animation[i].time)], -1, 0x80808080); + launcher.renderer.draw(snake.texture, cast(vec2)cast(ivec2)data.location[i], vec2(16,16), data.animation[i].frames[cast(int)(data.animation[i].time)], -1, 0x80808080); } } } @@ -477,7 +470,12 @@ struct MoveSystem break; case MapElement.Type.apple: launcher.manager.removeEntity(snake.element(data.location[i].location).id); - if(data.snakes[i].parts.length < 100)data.snakes[i].parts.add(new_location); + if(data.snakes[i].parts.length >= 99) + { + snake.addApple(); + goto case(MapElement.Type.empty); + } + data.snakes[i].parts.add(new_location); if(data.snakes[i].parts.length > 1) { @@ -506,7 +504,40 @@ struct MoveSystem } } } - + } +} + +struct SnakeSystem +{ + mixin ECS.System!1; + + struct EntitiesData + { + uint length; + Entity[] entity; + @readonly CSnake[] snake; + @readonly CILocation[] location; + } + + void onAddSystem(EntitiesData data) + { + foreach(i;0..data.length) + { + if(snake.element(data.location[i]).id == EntityID())snake.element(MapElement(MapElement.Type.snake,data.entity[i].id),data.location[i]); + else launcher.manager.removeEntity(data.entity[i].id); + } + } + + void onRemoveEntity(EntitiesData data) + { + foreach(i;0..data.length) + { + if(snake.element(data.location[i].location).id == data.entity[i].id) + snake.element(MapElement(MapElement.Type.empty, EntityID()),data.location[i].location); + foreach(part; data.snake[i].parts.array) + if(snake.element(part).id == data.entity[i].id) + snake.element(MapElement(MapElement.Type.empty, EntityID()),part); + } } } @@ -750,6 +781,35 @@ struct CleanSystem } } +struct CopyLocationSystem +{ + mixin ECS.System!32; + + struct EntitiesData + { + uint length; + const (Entity)[] entity; + CLocation[] location; + @readonly CILocation[] ilocation; + } + + void onAddEntity(EntitiesData data) + { + foreach(i;0..data.length) + { + data.ilocation[i] = cast(ivec2)(data.location[i] / 16); + } + } + + void onUpdate(EntitiesData data) + { + foreach(i;0..data.length) + { + data.location[i] = cast(vec2)(data.ilocation[i] * 16); + } + } +} + __gshared Snake* snake; void snakeStart() @@ -776,7 +836,6 @@ void snakeStart() launcher.manager.registerSystem!MoveSystem(0,"fixed"); launcher.manager.registerSystem!InputSystem(-100); launcher.manager.registerSystem!FixSnakeDirectionSystem(-1,"fixed"); - launcher.manager.registerSystem!AppleSystem(-1,"fixed"); launcher.manager.registerSystem!AnimationRenderSystem(100); launcher.manager.registerSystem!AnimationSystem(-1); launcher.manager.registerSystem!ParticleSystem(-1); @@ -784,8 +843,20 @@ void snakeStart() launcher.manager.registerSystem!DrawAppleSystem(99); launcher.manager.registerSystem!DrawSnakeSystem(101); + launcher.manager.registerSystem!CopyLocationSystem(100); + //launcher.manager.registerSystem!AppleRemoveSystem(100); + launcher.manager.registerSystem!AppleSystem(101); + launcher.manager.registerSystem!SnakeSystem(101); + launcher.manager.endRegister(); + launcher.gui_manager.addComponent(CApple(),"Apple"); + launcher.gui_manager.addComponent(CSnake(),"Snake"); + launcher.gui_manager.addComponent(CParticle(1000),"Particle (1s)"); + launcher.gui_manager.addComponent(CParticleVector(vec2(0,1)),"Particle Vector (UP)"); + launcher.gui_manager.addComponent(CInput(),"Input"); + launcher.gui_manager.addComponent(CMovement(CMovement.Direction.up),"Movement (UP)"); + launcher.gui_manager.addSystem(MoveSystem.system_id,"Move System"); launcher.gui_manager.addSystem(InputSystem.system_id,"Input System"); launcher.gui_manager.addSystem(FixSnakeDirectionSystem.system_id,"Fix Direction System"); @@ -797,15 +868,13 @@ void snakeStart() snake.snake_destroy_particle_frames = Mallocator.makeArray([vec4(64,144,16,16)*px,vec4(80,144,16,16)*px,vec4(96,144,16,16)*px,vec4(112,144,16,16)*px].staticArray); { - ushort[4] components = [CILocation.component_id, CSnake.component_id, CMovement.component_id, CInput.component_id]; + ushort[5] components = [CILocation.component_id, CSnake.component_id, CMovement.component_id, CInput.component_id, CLocation.component_id]; snake.snake_tmpl = launcher.manager.allocateTemplate(components); - //CILocation* loc_comp = snake.snake_tmpl.getComponent!CILocation; - //*loc_comp = ivec2(2,2); launcher.manager.addEntity(snake.snake_tmpl,[CILocation(ivec2(2,2)).ref_].staticArray); } { - snake.snake_destroy_particle = launcher.manager.allocateTemplate([CLocation.component_id, CParticle.component_id, CParticleVector.component_id, CAnimation.component_id].staticArray); + snake.snake_destroy_particle = launcher.manager.allocateTemplate([CLocation.component_id, CParticle.component_id, CParticleVector.component_id, CAnimation.component_id, CLocation.component_id].staticArray); CAnimation* canim = snake.snake_destroy_particle.getComponent!CAnimation; canim.frames = snake.snake_destroy_particle_frames; CParticle* particle = snake.snake_destroy_particle.getComponent!CParticle; @@ -813,7 +882,7 @@ void snakeStart() } { - ushort[2] components = [CILocation.component_id, CApple.component_id]; + ushort[3] components = [CILocation.component_id, CApple.component_id, CLocation.component_id]; snake.apple_tmpl = launcher.manager.allocateTemplate(components); snake.addApple(); } @@ -824,13 +893,6 @@ void snakeStart() MoveSystem* move_system = launcher.manager.getSystem!MoveSystem(); move_system.setTemplates(); - - /*foreach(i; 0..10) - foreach(j; 0..10) - { - loc_compation = vec2(i*32+64,j*32+64); - launcher.manager.addEntity(simple.tmpl); - }*/ } void snakeEnd() @@ -839,39 +901,6 @@ void snakeEnd() Mallocator.dispose(snake); } -void snakeTool(vec2 position, Tool tool, int size) -{ - switch(tool) - { - case Tool.entity_spawner: - { - EntityTemplate* tmpl = launcher.gui_manager.getSelectedTemplate(); - CLocation* location = tmpl.getComponent!CLocation; - if(location) - { - position.x += (randomf() - 0.5) * size; - position.y += (randomf() - 0.5) * size; - *location = position; - } - CILocation* ilocation = tmpl.getComponent!CILocation; - if(ilocation) - { - position.x += (randomf() - 0.5) * size; - position.y += (randomf() - 0.5) * size; - ivec2 ipos; - ipos.x = cast(int)(position.x / 16); - ipos.y = cast(int)(position.y / 16); - *ilocation = ipos; - if(snake.element(ipos).type != MapElement.Type.empty)return; - } - launcher.manager.addEntity(tmpl); - } - break; - default: - break; - } -} - void snakeEvent(SDL_Event* event) { @@ -881,16 +910,16 @@ bool snakeLoop() { launcher.render_position = (vec2(launcher.window_size.x,launcher.window_size.y)*launcher.scalling - vec2(288,288)) * 0.5; - /*if(launcher.show_demo_wnd) - { - igSetNextWindowPos(ImVec2(800 - 260, 30), ImGuiCond_Once, ImVec2(0,0)); - igSetNextWindowSize(ImVec2(250, 0), ImGuiCond_Once); - if(igBegin("Snake",&launcher.show_demo_wnd,0)) - { + // if(launcher.show_demo_wnd) + // { + // igSetNextWindowPos(ImVec2(800 - 260, 30), ImGuiCond_Once, ImVec2(0,0)); + // igSetNextWindowSize(ImVec2(250, 0), ImGuiCond_Once); + // if(igBegin("Snake",&launcher.show_demo_wnd,0)) + // { - } - igEnd(); - }*/ + // } + // igEnd(); + // } launcher.manager.begin(); @@ -912,7 +941,5 @@ bool snakeLoop() launcher.manager.end(); - //snake.drawMap(); - return true; } \ No newline at end of file diff --git a/demos/source/demos/space_invaders.d b/demos/source/demos/space_invaders.d index 45dd06d..ce2cd6a 100644 --- a/demos/source/demos/space_invaders.d +++ b/demos/source/demos/space_invaders.d @@ -4,25 +4,26 @@ import app; import bindbc.sdl; -import cimgui.cimgui; - import bubel.ecs.attributes; import bubel.ecs.core; import bubel.ecs.entity; import bubel.ecs.manager; import bubel.ecs.std; +import cimgui.cimgui; + import ecs_utils.gfx.texture; import ecs_utils.math.vector; import ecs_utils.utils; +import game_core.basic; + //import std.math : PI; -enum PI = 3.141592653589793238462643383279502884197169399375105820; //import std.array : staticArray; -enum float px = 1.0/512.0; +private enum float px = 1.0/512.0; extern(C): @@ -114,14 +115,14 @@ enum Direction : byte ------------------------------------------------ Components ------------------------------------------------------------------ #######################################################################################################################*/ -struct CLocation +/*struct CLocation { mixin ECS.Component; alias value this; vec2 value = vec2(0); -} +}*/ struct CScale { @@ -273,7 +274,7 @@ struct CTargetParent mixin ECS.Component; EntityID parent; - vec2 rel_pos; + vec2 rel_pos = vec2(0,0); } @@ -699,7 +700,7 @@ struct ShipWeaponSystem Entity[] entity; CInit[] init; //CShip[] ship; - //CChildren[] children; + CChildren[] children; } struct Ship @@ -1062,7 +1063,7 @@ struct CollisionSystem } } -struct LaserShootingSystem +struct ShootingSystem { mixin ECS.System!32; @@ -1187,7 +1188,7 @@ struct LaserShootingSystem else fire_velocity.value = vec2(0,0); fire_location.value = data.location[i]; - if(data.shoot_direction[i].direction == Direction.down) + if(data.shoot_direction && data.shoot_direction[i].direction == Direction.down) { fire_rotation.value = PI; //fire_location.value.y -= 16; @@ -1270,7 +1271,7 @@ struct DampingSystem } } -struct LaserCollisionSystem +struct BulletsCollisionSystem { mixin ECS.System!32; @@ -1423,9 +1424,12 @@ struct UpgradeSystem if(ship) { CChildren* children = entity.getComponent!CChildren; - foreach(child;children.childern) + if(children) { - launcher.manager.sendEvent(child,EUpgrade()); + foreach(child;children.childern) + { + launcher.manager.sendEvent(child,EUpgrade()); + } } } } @@ -1742,7 +1746,7 @@ struct ShootWaveSystem CLocation* location = entity.getComponent!CLocation; CGuild* guild = entity.getComponent!CGuild; - //LaserShootingSystem.bullet_tmpl + //ShootingSystem.bullet_tmpl EntityTemplate* tmpl = space_invaders.bullet_tmpl[wave.bullet_type]; foreach(dir;dirs) { @@ -2180,12 +2184,6 @@ struct CShipIterator //void handleEvent(Entity* entity, ) }//*/ -extern(C) float sqrtf(float x) @nogc nothrow @system; -extern(C) float acosf(float x) @nogc nothrow @system; -extern(C) float sinf(float x) @nogc nothrow @system; -extern(C) float cosf(float x) @nogc nothrow @system; -extern(C) float powf(float x, float y) @nogc nothrow @system; - /** *System is responsible for movement of objects with CInput component. *In this example every entity has same speed when using movement system. @@ -2348,9 +2346,9 @@ void spaceInvadersStart() launcher.manager.registerSystem!InputMovementSystem(-100); launcher.manager.registerSystem!MovementSystem(-99); launcher.manager.registerSystem!ClampPositionSystem(-90); - launcher.manager.registerSystem!LaserShootingSystem(0); + launcher.manager.registerSystem!ShootingSystem(0); launcher.manager.registerSystem!ChangeDirectionSystem(0); - launcher.manager.registerSystem!LaserCollisionSystem(-70); + launcher.manager.registerSystem!BulletsCollisionSystem(-70); launcher.manager.registerSystem!ShootGridManager(-80); launcher.manager.registerSystem!ShootGridCleaner(-101); launcher.manager.registerSystem!HitPointsSystem(0); @@ -2374,13 +2372,49 @@ void spaceInvadersStart() launcher.manager.endRegister(); + launcher.gui_manager.addComponent(CInput(),"Input"); + launcher.gui_manager.addComponent(CShip(),"Ship"); + launcher.gui_manager.addComponent(CEnemy(),"Enemy"); + launcher.gui_manager.addComponent(CAutoShoot(),"Auto Shoot"); + launcher.gui_manager.addComponent(CWeapon(0, CWeapon.Type.laser),"Weapon (laser)"); + launcher.gui_manager.addComponent(CVelocity(vec2(0,0)),"Velocity (0,0)"); + launcher.gui_manager.addComponent(CBullet(),"Bullet (dmg1)"); + launcher.gui_manager.addComponent(CSideMove(),"Side Move"); + launcher.gui_manager.addComponent(CSideMove(0),"Side Move (g1)"); + launcher.gui_manager.addComponent(CSideMove(1),"Side Move (g2)"); + launcher.gui_manager.addComponent(CShootGrid(),"Shoot Grid"); + launcher.gui_manager.addComponent(CGuild(),"Guild (Player)"); + launcher.gui_manager.addComponent(CGuild(1),"Guild (Enemy)"); + launcher.gui_manager.addComponent(CHitPoints(10),"Hit Points (10)"); + launcher.gui_manager.addComponent(CHitMark(),"Hit Mark"); + launcher.gui_manager.addComponent(CUpgrade(CUpgrade.Upgrade.laser),"Upgrade (laser)"); + launcher.gui_manager.addComponent(CParticle(1000),"Particle (1s)"); + //launcher.gui_manager.addComponent(CMaxHitPoints(),"Max Hit Points"); + launcher.gui_manager.addComponent(CDamping(0),"Damping (0)"); + launcher.gui_manager.addComponent(CDamping(4),"Damping (4)"); + launcher.gui_manager.addComponent(CDamping(8),"Damping (8)"); + launcher.gui_manager.addComponent(CTargetParent(),"Target Parent"); + launcher.gui_manager.addComponent(CTargetPlayerShip(),"Target Player Ship"); + launcher.gui_manager.addComponent(CTarget(),"Target"); + launcher.gui_manager.addComponent(CChildren(),"Children"); + launcher.gui_manager.addComponent(CWeaponLocation(vec2(0,16)),"Weapon Location (0,16)"); + launcher.gui_manager.addComponent(CInit(CInit.Type.space_ship),"Init (Ship)"); + launcher.gui_manager.addComponent(CInit(CInit.Type.boss),"Init (Boss)"); + launcher.gui_manager.addComponent(CInit(CInit.Type.tower),"Init (Tower)"); + launcher.gui_manager.addComponent(CBoss(),"Boss"); + launcher.gui_manager.addComponent(CColliderScale(),"Collider Scale"); + launcher.gui_manager.addComponent(CParticleEmitter(),"Particle Emitter"); + launcher.gui_manager.addComponent(CParticleEmitterTime(),"Particle Emitter Time"); + //launcher.gui_manager.addComponent(CSpawnUponDeath(),"Spawn Upon Death"); + launcher.gui_manager.addComponent(CShootWaveUponDeath(CWeapon.Type.canon),"Wave Upon Death"); + launcher.gui_manager.addSystem(DrawSystem.system_id,"Draw System"); launcher.gui_manager.addSystem(InputMovementSystem.system_id,"Input Movement"); - launcher.gui_manager.addSystem(LaserShootingSystem.system_id,"Laser Shooting"); + launcher.gui_manager.addSystem(ShootingSystem.system_id,"Shooting System"); launcher.gui_manager.addSystem(MovementSystem.system_id,"Movement System"); launcher.gui_manager.addSystem(ClampPositionSystem.system_id,"Clamp Position System"); launcher.gui_manager.addSystem(ChangeDirectionSystem.system_id,"Change Direction System"); - launcher.gui_manager.addSystem(LaserCollisionSystem.system_id,"Laser Collision System"); + launcher.gui_manager.addSystem(BulletsCollisionSystem.system_id,"Bullets Collision System"); launcher.gui_manager.addSystem(ShootGridManager.system_id,"Shoot Grid Manager"); launcher.gui_manager.addSystem(ShootGridCleaner.system_id,"Shoot Grid Cleaner"); launcher.gui_manager.addSystem(HitPointsSystem.system_id,"Hit Points System"); @@ -2560,7 +2594,7 @@ void spaceInvadersEnd() { /*launcher.manager.getSystem(DrawSystem.system_id).disable(); launcher.manager.getSystem(InputMovementSystem.system_id).disable(); - launcher.manager.getSystem(LaserShootingSystem.system_id).disable(); + launcher.manager.getSystem(ShootingSystem.system_id).disable(); launcher.manager.getSystem(MovementSystem.system_id).disable(); launcher.manager.getSystem(ClampPositionSystem.system_id).disable(); launcher.manager.getSystem(ShootGridCleaner.system_id).disable();*/ @@ -2570,35 +2604,6 @@ void spaceInvadersEnd() space_invaders = null; } -void spaceInvadersTool(vec2 position, Tool tool, int size) -{ - switch(tool) - { - case Tool.entity_spawner: - { - EntityTemplate* tmpl = launcher.gui_manager.getSelectedTemplate(); - CLocation* location = tmpl.getComponent!CLocation; - if(location) - { - position.x += (randomf - 0.5) * size; - position.y += (randomf - 0.5) * size; - if(position.y < 16)position.y = 16; - else if(position.y > 299)position.y = 299; - *location = position; - } - CWeapon* laser_weapon = tmpl.getComponent!CWeapon; - if(laser_weapon) - { - laser_weapon.shoot_time = randomf * CWeapon.levels[laser_weapon.level - 1].reload_time; - } - launcher.manager.addEntity(tmpl); - } - break; - default: - break; - } -} - void spaceInvadersEvent(SDL_Event* event) { diff --git a/demos/source/game_core/basic.d b/demos/source/game_core/basic.d new file mode 100644 index 0000000..993a5aa --- /dev/null +++ b/demos/source/game_core/basic.d @@ -0,0 +1,14 @@ +module game_core.basic; + +import bubel.ecs.core; + +import ecs_utils.math.vector; + +struct CLocation +{ + mixin ECS.Component; + + alias value this; + + vec2 value = vec2(0); +} \ No newline at end of file diff --git a/demos/source/gui/attributes.d b/demos/source/gui/attributes.d new file mode 100644 index 0000000..53605c2 --- /dev/null +++ b/demos/source/gui/attributes.d @@ -0,0 +1,20 @@ +module gui.attributes; + +enum GUIColor = "GUIColor"; + +struct GUIRange +{ + union + { + struct + { + int min; + int max; + } + struct + { + float minf; + float maxf; + } + } +} \ No newline at end of file diff --git a/demos/source/gui/component.d b/demos/source/gui/component.d index bbd7ec5..b19f069 100644 --- a/demos/source/gui/component.d +++ b/demos/source/gui/component.d @@ -1,6 +1,61 @@ -module gui.components; +module gui.component; + +import ecs_utils.utils; struct ComponentGUI { - -} \ No newline at end of file + const (char)* name; + void* data; + ushort component_id; +} + +struct ComponentEditGUI +{ + const (char)* name; + VariableGUI[] variables; + uint used; +} + +struct VariableGUI +{ + struct Int + { + int min; + int max; + } + + struct Float + { + float min; + float max; + } + + struct Enum + { + const (char)[][] strings; + } + + enum Type + { + byte_, + ubyte_, + short_, + ushort_, + int_, + uint_, + float_, + enum_, + color, + vec2, + ivec2 + } + Type type; + const (char)* name; + ushort offset; + union + { + Int int_; + Float float_; + Enum enum_; + } +} diff --git a/demos/source/gui/manager.d b/demos/source/gui/manager.d index 4d452ad..b7c8678 100644 --- a/demos/source/gui/manager.d +++ b/demos/source/gui/manager.d @@ -4,22 +4,32 @@ import app; import cimgui.cimgui; +import bubel.ecs.entity; +import bubel.ecs.manager; import bubel.ecs.std; import bubel.ecs.system; import bubel.ecs.vector; -import bubel.ecs.entity; +import ecs_utils.math.vector; + +import gui.attributes; +import gui.component; import gui.system; import gui.template_; +import std.traits; + extern(C): struct GUIManager { Vector!SystemGUI systems; + Vector!ComponentGUI components; Vector!TemplateGUI templates; + Vector!ComponentEditGUI edit_components; uint selected_tempalte = 0; + uint selected_component = 0; void clear() { @@ -27,10 +37,22 @@ struct GUIManager { launcher.manager.freeTemplate(tmpl.tmpl); } + foreach(comp; components) + { + free(comp.data); + } + foreach(ref comp; edit_components) + { + if(comp.variables)Mallocator.dispose(comp.variables); + comp.variables = null; + comp.used = 0; + } systems.clear(); templates.clear(); + components.clear(); selected_tempalte = 0; + selected_component = 0; } EntityTemplate* getSelectedTemplate() @@ -39,6 +61,12 @@ struct GUIManager else return null; } + ComponentRef getSelectedComponent() + { + if(components.length > selected_component)return ComponentRef(components[selected_component].data, components[selected_component].component_id); + else return ComponentRef(null, ushort.max); + } + void addSystem(ushort id, const (char)* name, bool enabled = true) { System* system = launcher.manager.getSystem(id); @@ -56,9 +84,112 @@ struct GUIManager templates.add(TemplateGUI(name, tmpl)); } + // void addComponent(ComponentRef comp, const (char)* name) + // { + // uint size = EntityManager.instance.components[comp.component_id].size; + // void* data = malloc(size); + // memcpy(data, comp.ptr, size); + // components.add(ComponentGUI(name, data, comp.component_id)); + // } + + void addComponent(T)(T comp, const (char)* name) + { + static assert(hasStaticMember!(T,"component_id")); + uint size = EntityManager.instance.components[comp.component_id].size; + void* data = malloc(size); + memcpy(data, &comp, size); + components.add(ComponentGUI(name, data, comp.component_id)); + + if(edit_components.length <= comp.component_id) + { + edit_components.length = comp.component_id+1;//.extend(comp.component_id + 1); + } + //edit_components[comp.component_id] = ComponentEditGUI(name); + ComponentEditGUI comp_edit; + comp_edit.name = T.stringof; + //enum fields = __traits(allMembers, T); + alias fields = FieldNameTuple!T; + //pragma(msg,fields); + comp_edit.variables = Mallocator.makeArray!VariableGUI(fields.length); + foreach(member_str; fields) + { + alias member = __traits(getMember, T, member_str); + alias member_type = typeof(member); + //pragma(msg,member); + //pragma(msg,member_type); + //pragma(msg,__traits(getMember, T, member).offsetof); + ushort offset = member.offsetof;//cast(ushort)__traits(getMember, T, member).offsetof; + static if(__traits(isIntegral,member_type)) + { + static if(__traits(isUnsigned, member_type)) + { + static if(hasUDA!(member,GUIColor)) + { + comp_edit.variables[comp_edit.used++] = VariableGUI(VariableGUI.Type.color,member_str,offset); + } + else switch(member_type.sizeof) + { + case 1: comp_edit.variables[comp_edit.used++] = VariableGUI(VariableGUI.Type.ubyte_,member_str,offset);break; + case 2: comp_edit.variables[comp_edit.used++] = VariableGUI(VariableGUI.Type.ushort_,member_str,offset);break; + case 4: comp_edit.variables[comp_edit.used++] = VariableGUI(VariableGUI.Type.uint_,member_str,offset);break; + default:break; + } + static if(hasUDA!(member,GUIRange)) + { + comp_edit.variables[comp_edit.used-1].int_.min = getUDAs!(member,GUIRange)[0].min; + comp_edit.variables[comp_edit.used-1].int_.max = getUDAs!(member,GUIRange)[0].max; + } + else + { + comp_edit.variables[comp_edit.used-1].int_.min = 0; + comp_edit.variables[comp_edit.used-1].int_.max = int.max; + } + } + else + { + switch(member_type.sizeof) + { + case 1: comp_edit.variables[comp_edit.used++] = VariableGUI(VariableGUI.Type.byte_,member_str,offset);break; + case 2: comp_edit.variables[comp_edit.used++] = VariableGUI(VariableGUI.Type.short_,member_str,offset);break; + case 4: comp_edit.variables[comp_edit.used++] = VariableGUI(VariableGUI.Type.int_,member_str,offset);break; + default:break; + } + static if(hasUDA!(member,GUIRange)) + { + comp_edit.variables[comp_edit.used-1].int_.min = getUDAs!(member,GUIRange)[0].min; + comp_edit.variables[comp_edit.used-1].int_.max = getUDAs!(member,GUIRange)[1].max; + } + { + comp_edit.variables[comp_edit.used-1].int_.min = int.min; + comp_edit.variables[comp_edit.used-1].int_.max = int.max; + } + } + } + else static if(__traits(isScalar,member_type)) + { + switch(member_type.sizeof) + { + case 4:comp_edit.variables[comp_edit.used++] = VariableGUI(VariableGUI.Type.float_,member_str,offset);break; + case 8: + default:break; + } + static if(hasUDA!(member,GUIRange)) + { + comp_edit.variables[comp_edit.used-1].float_.min = getUDAs!(member,GUIRange)[0].minf; + comp_edit.variables[comp_edit.used-1].float_.max = getUDAs!(member,GUIRange)[1].maxf; + } + { + comp_edit.variables[comp_edit.used-1].float_.min = -float.max; + comp_edit.variables[comp_edit.used-1].float_.max = float.max; + } + } + } + edit_components[comp.component_id] = comp_edit; + } + void gui() { - if(igCollapsingHeader("Systems", ImGuiTreeNodeFlags_Framed | ImGuiTreeNodeFlags_SpanFullWidth | ImGuiTreeNodeFlags_FramePadding)) + if(igCollapsingHeader("Systems", ImGuiTreeNodeFlags_SpanFullWidth | ImGuiTreeNodeFlags_DefaultOpen)) { bool true_ = true; igIndent(8); @@ -74,25 +205,177 @@ struct GUIManager } } - void toolGui() + static vec4 colorUintToVec4(uint color) { - if(templates.length) + // color = *cast(uint*)(comp_ptr+var.offset); + return vec4(cast(float)(color & 0xFF) / 255, + cast(float)(color >> 8 & 0xFF) / 255, + cast(float)(color >> 16 & 0xFF) / 255, + cast(float)(color >> 24 & 0xFF) / 255); + } + + static uint colorVec4ToUint(vec4 color) + { + return cast(uint)(color.x * 255) | + cast(uint)(color.y * 255) << 8 | + cast(uint)(color.z * 255) << 16 | + cast(uint)(color.w * 255) << 24; + } + + static bool igDragScalarClamp(const(char)* label, ImGuiDataType data_type, void* v, float v_speed, const(void)* v_min, const(void)* v_max, const(char)* format, float power) + { + ubyte[8] v_backup;// = *v; + final switch(data_type) { - if(igBeginCombo("Template",templates[selected_tempalte].name,0)) - { - foreach(i, tmpl; templates) - { - if(igSelectable(tmpl.name,false,0,ImVec2(0,0))) - { - selected_tempalte = cast(uint)i; - } - } - igEndCombo(); - } + case ImGuiDataType_S8:memcpy(v_backup.ptr, v, 1);break; + case ImGuiDataType_S16:memcpy(v_backup.ptr, v, 2);break; + case ImGuiDataType_S32:memcpy(v_backup.ptr, v, 4);break; + case ImGuiDataType_U8:memcpy(v_backup.ptr, v, 1);break; + case ImGuiDataType_U16:memcpy(v_backup.ptr, v, 2);break; + case ImGuiDataType_U32:memcpy(v_backup.ptr, v, 4);break; + case ImGuiDataType_Float:memcpy(v_backup.ptr, v, 4);break; } - else + if (!igDragScalar(label, data_type, v, v_speed, v_min, v_max, format, power)) + return false; + + final switch(data_type) { - if(igBeginCombo("Template",null,0))igEndCombo(); + case ImGuiDataType_S8: + if(*cast(byte*)v < *cast(byte*)v_min)*cast(byte*)v = *cast(byte*)v_min; + else if(*cast(byte*)v > *cast(byte*)v_max)*cast(byte*)v = *cast(byte*)v_max; + return *cast(byte*)v != *cast(byte*)v_backup.ptr; + case ImGuiDataType_S16: + if(*cast(short*)v < *cast(short*)v_min)*cast(short*)v = *cast(short*)v_min; + else if(*cast(short*)v > *cast(short*)v_max)*cast(short*)v = *cast(short*)v_max; + return *cast(short*)v != *cast(short*)v_backup.ptr; + case ImGuiDataType_S32: + if(*cast(int*)v < *cast(int*)v_min)*cast(int*)v = *cast(int*)v_min; + else if(*cast(int*)v > *cast(int*)v_max)*cast(int*)v = *cast(int*)v_max; + return *cast(int*)v != *cast(int*)v_backup.ptr; + case ImGuiDataType_U8: + if(*cast(ubyte*)v < *cast(ubyte*)v_min)*cast(ubyte*)v = *cast(ubyte*)v_min; + else if(*cast(ubyte*)v > *cast(ubyte*)v_max)*cast(ubyte*)v = *cast(ubyte*)v_max; + return *cast(ubyte*)v != *cast(ubyte*)v_backup.ptr; + case ImGuiDataType_U16: + if(*cast(ushort*)v < *cast(ushort*)v_min)*cast(ushort*)v = *cast(ushort*)v_min; + else if(*cast(ushort*)v > *cast(ushort*)v_max)*cast(ushort*)v = *cast(ushort*)v_max; + return *cast(ushort*)v != *cast(ushort*)v_backup.ptr; + case ImGuiDataType_U32: + if(*cast(uint*)v < *cast(uint*)v_min)*cast(uint*)v = *cast(uint*)v_min; + else if(*cast(uint*)v > *cast(uint*)v_max)*cast(uint*)v = *cast(uint*)v_max; + return *cast(uint*)v != *cast(uint*)v_backup.ptr; + case ImGuiDataType_Float: + if(*cast(float*)v < *cast(float*)v_min)*cast(float*)v = *cast(float*)v_min; + else if(*cast(float*)v > *cast(float*)v_max)*cast(float*)v = *cast(float*)v_max; + return *cast(float*)v != *cast(float*)v_backup.ptr; } } + + void entityComponentsGUI() + { + EntityTemplate* tmpl = templates[selected_tempalte].tmpl; + EntityManager.EntityInfo* info = tmpl.info; + void* data_ptr = tmpl.entity_data.ptr; + vec4 color; + foreach(comp_id; info.components) + { + if(comp_id >= edit_components.length)break; + void* comp_ptr = data_ptr + info.tmpl_deltas[comp_id]; + if(edit_components[comp_id].used) + { + if(igCollapsingHeader(edit_components[comp_id].name, ImGuiTreeNodeFlags_SpanAvailWidth | ImGuiTreeNodeFlags_DefaultOpen)) + { + igIndent(8); + foreach(ref VariableGUI var;edit_components[comp_id].variables[0 .. edit_components[comp_id].used]) + { + switch(var.type) + { + case VariableGUI.Type.byte_: + igDragScalarClamp(var.name, ImGuiDataType_S8, comp_ptr+var.offset, 1, cast(void*)&var.int_.min, cast(void*)&var.int_.max, null, 1); + break; + case VariableGUI.Type.ubyte_: + igDragScalarClamp(var.name, ImGuiDataType_U8, comp_ptr+var.offset, 1, cast(void*)&var.int_.min, cast(void*)&var.int_.max, null, 1); + break; + case VariableGUI.Type.short_: + igDragScalarClamp(var.name, ImGuiDataType_S16, comp_ptr+var.offset, 1, cast(void*)&var.int_.min, cast(void*)&var.int_.max, null, 1); + break; + case VariableGUI.Type.ushort_: + igDragScalarClamp(var.name, ImGuiDataType_U16, comp_ptr+var.offset, 1, cast(void*)&var.int_.min, cast(void*)&var.int_.max, null, 1); + break; + case VariableGUI.Type.int_: + igDragScalarClamp(var.name, ImGuiDataType_S32, comp_ptr+var.offset, 1, cast(void*)&var.int_.min, cast(void*)&var.int_.max, null, 1); + break; + case VariableGUI.Type.uint_: + igDragScalarClamp(var.name, ImGuiDataType_U32, comp_ptr+var.offset, 1, cast(void*)&var.int_.min, cast(void*)&var.int_.max, null, 1); + break; + case VariableGUI.Type.float_: + igDragScalarClamp(var.name, ImGuiDataType_Float, comp_ptr+var.offset, 1, cast(void*)&var.float_.min, cast(void*)&var.float_.max, null, 1); + break; + case VariableGUI.Type.color: + color = colorUintToVec4(*cast(uint*)(comp_ptr+var.offset)); + if(igColorEdit4(var.name, color.data, ImGuiColorEditFlags_None)) + *cast(uint*)(comp_ptr+var.offset) = colorVec4ToUint(color); + break; + default:break; + } + } + igUnindent(8); + } + } + } + } + + void toolGui() + { + ImGuiStyle * style = igGetStyle(); + ImVec4 col = style.Colors[ImGuiCol_Header]; + style.Colors[ImGuiCol_Header] = style.Colors[ImGuiCol_TextSelectedBg]; + //style. + //ImDrawList* draw_list = igGetWindowDrawList(); + final switch(launcher.used_tool) + { + case Tool.entity_spawner: + if(templates.length) + { + { + if(igListBoxHeaderInt("Template",cast(int)templates.length,cast(int)templates.length)) + { + foreach(i, tmpl; templates) + { + if(igSelectable(tmpl.name,selected_tempalte == i,ImGuiSelectableFlags_AllowDoubleClick,ImVec2(0,0))) + { + selected_tempalte = cast(uint)i; + } + } + igListBoxFooter(); + } + } + } + style.Colors[ImGuiCol_Header] = col; + entityComponentsGUI(); + break; + case Tool.component_manipulator: + if(components.length) + { + if(igListBoxHeaderInt("Components",cast(int)components.length,cast(int)components.length)) + { + { + foreach(i, comp; components) + { + if(igSelectable(comp.name,selected_component == i,0,ImVec2(0,0))) + { + selected_component = cast(uint)i; + } + } + igListBoxFooter(); + } + } + } + break; + case Tool.selector: + break; + } + + style.Colors[ImGuiCol_Header] = col; + } } \ No newline at end of file diff --git a/demos/source/gui/tool_circle.d b/demos/source/gui/tool_circle.d new file mode 100644 index 0000000..c43ade3 --- /dev/null +++ b/demos/source/gui/tool_circle.d @@ -0,0 +1,52 @@ +module gui.tool_circle; + +import ecs_utils.gfx.buffer; +import ecs_utils.gfx.shader; +import ecs_utils.gfx.config; +import ecs_utils.gfx.renderer; + +import ecs_utils.math.vector; + +version(WebAssembly)import glad.gl.gles2; +else version(Android)import glad.gl.gles2; +else import glad.gl.gl; + +struct ToolCircle +{ + //Buffer vbo; + //Buffer ibo; + + uint material_id = 1; + uint mesh_id = 0; + + /*/void generate() + { + ushort[] + }*/ + + void draw(Renderer* renderer, vec2 position, float size, float edge = 1) + { + position = position * renderer.view_size + renderer.view_pos; + vec2 sizes = renderer.view_size * size; + vec2 sizes2 = vec2(edge,0); + + import core.stdc.string; + ubyte[32] uniform_block; + void* ptr = uniform_block.ptr; + *cast(float*)(ptr) = sizes.x; + *cast(float*)(ptr+4) = 0; + *cast(float*)(ptr+8) = 0; + *cast(float*)(ptr+12) = sizes.y; + memcpy(ptr+16,position.data.ptr,8); + memcpy(ptr+24,sizes2.data.ptr,8); + glEnableVertexAttribArray(0); + + GfxConfig.meshes[mesh_id].bind(); + GfxConfig.materials[material_id].bind(); + GfxConfig.materials[material_id].pushBindings(); + GfxConfig.materials[material_id].pushUniforms(uniform_block.ptr); + //glDisable(GL_DEPTH_TEST); + + glDrawElements(GL_TRIANGLES,6,GL_UNSIGNED_SHORT,null); + } +} \ No newline at end of file diff --git a/demos/utils/source/ecs_utils/gfx/buffer.d b/demos/utils/source/ecs_utils/gfx/buffer.d index bfdad62..76cab50 100644 --- a/demos/utils/source/ecs_utils/gfx/buffer.d +++ b/demos/utils/source/ecs_utils/gfx/buffer.d @@ -68,7 +68,7 @@ struct Buffer void map(BindTarget target) nothrow { bind(target); - version(Android){}else data.map_ptr = glMapBuffer(target,GL_WRITE_ONLY); + version(Android){}else version(WebAssembly){}else data.map_ptr = glMapBuffer(target,GL_WRITE_ONLY); } void map(uint offset, uint size, BindTarget target, uint flags = MapFlagBits.write | MapFlagBits.flush_explict | MapFlagBits.invalidate_buffer) nothrow diff --git a/demos/utils/source/ecs_utils/gfx/material.d b/demos/utils/source/ecs_utils/gfx/material.d index 1bde9c8..0fca5fb 100644 --- a/demos/utils/source/ecs_utils/gfx/material.d +++ b/demos/utils/source/ecs_utils/gfx/material.d @@ -78,6 +78,25 @@ struct Material void bind() nothrow { + final switch(data.blend_mode) + { + case BlendMode.mixed: + glDepthMask(0); + glEnable(GL_BLEND); + glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); + break; + case BlendMode.opaque: + glDepthMask(1); + glDisable(GL_BLEND); + break; + case BlendMode.additive: + glDepthMask(0); + glEnable(GL_BLEND); + glBlendFunc(GL_SRC_ALPHA, GL_ONE); + //glBlendFunc(GL_ONE, GL_ONE); + break; + } + glUseProgram(data.modules[0].gl_handle); } diff --git a/demos/utils/source/ecs_utils/gfx/renderer.d b/demos/utils/source/ecs_utils/gfx/renderer.d index 93d009e..4e34b54 100644 --- a/demos/utils/source/ecs_utils/gfx/renderer.d +++ b/demos/utils/source/ecs_utils/gfx/renderer.d @@ -67,8 +67,10 @@ struct Renderer enum max_items = batch_size;//963; byte[] batch_vertices; ushort[] batch_indices; - void* memory; + void* memory = null; uint items = 0; + uint material_id; + uint texture_id; } Mutex* get_block_mutex; @@ -128,11 +130,11 @@ struct Renderer block_stack_mutex.unlock();*/ foreach(ref Thread thread; threads) { - foreach(VertexBlock block; thread.blocks) + foreach(VertexBlock block; thread.filled_blocks) { allocator.freeBlock(block.memory); } - thread.blocks.clear(); + thread.filled_blocks.clear(); } render_blocks = 0; current_block = 0; @@ -144,13 +146,13 @@ struct Renderer { foreach(ref Thread thread; threads) { - foreach(VertexBlock block; thread.blocks) + foreach(VertexBlock block; thread.filled_blocks) { uint items = block.items; if(items + item_id >= MaxObjects)items = MaxObjects - item_id; batch_vbo[0].bufferSubData(Buffer.BindTarget.array,items*4*14,item_id*4*14,block.batch_vertices.ptr); batch_ibo[0].bufferSubData(Buffer.BindTarget.element_array,items*2*6,item_id*2*6,block.batch_indices.ptr); - draw_list.add(DrawCall(item_id,items)); + draw_list.add(DrawCall(block.texture_id,block.material_id,item_id,items)); item_id += items; } //thread.blocks.clear(); @@ -173,8 +175,15 @@ struct Renderer foreach(i, ref Thread thread; threads) { //pushBlock(thread.block); - thread.blocks.add(thread.block); - thread.block = getBlock(); + foreach(ref block; thread.blocks) + { + if(block.items > 0) + { + thread.filled_blocks.add(block); + block.items = 0; + } + } + //thread.blocks = getBlock(); } } @@ -182,8 +191,8 @@ struct Renderer { //Vector!VertexBlock block; RenderData[] render_list; - VertexBlock block; - Vector!VertexBlock blocks; + VertexBlock[] blocks; + Vector!VertexBlock filled_blocks; } Thread[] threads; @@ -225,6 +234,8 @@ struct Renderer struct DrawCall { + uint texture_id; + uint material_id; uint start; uint count; } @@ -249,6 +260,7 @@ struct Renderer void initialize() { + import ecs_utils.gfx.config; //this.technique = __ecs_used_technique; __initialize(this); @@ -261,7 +273,8 @@ struct Renderer threads = Mallocator.makeArray!Thread(32); foreach(ref Thread thread;threads) { - thread.block = getBlock(); + //thread.blocks = getBlock(); + thread.blocks = Mallocator.makeArray!VertexBlock(GfxConfig.materials.length); } } @@ -494,12 +507,33 @@ struct Renderer private static void __draw_gl_vbo_batch(ref Renderer this_, Texture tex, vec2 pos, vec2 size, vec4 coords, short depth, uint color, float angle, uint material_id, uint mesh_id, uint thread_id = 0) { import ecs_utils.gfx.config; - short[3] mem = [depth, *cast(short*)&color, *(cast(short*)&color + 1)]; + //import core.stdc.string; with(this_) { //if(item_id >= MaxObjects)return; //pos += view_pos; + Thread* thread = &threads[thread_id]; + VertexBlock* block; + assert(thread.blocks.length > material_id); + block = &thread.blocks[material_id]; + if(block.items == 0) + { + thread.blocks[material_id] = getBlock(); + block = &thread.blocks[material_id]; + block.material_id = material_id; + } + else if(block.items >= VertexBlock.max_items) + { + //pushBlock(thread.block); + prepared_items += block.items; + thread.filled_blocks.add(*block); + thread.blocks[material_id] = getBlock(); + block = &thread.blocks[material_id]; + block.material_id = material_id; + } + + short[3] mem = [depth, *cast(short*)&color, *(cast(short*)&color + 1)]; pos.x = pos.x * view_size.x + view_pos.x; pos.y = pos.y * view_size.y + view_pos.y;//*/ @@ -513,8 +547,8 @@ struct Renderer memcpy(ptr+16,pos.data.ptr,8); memcpy(ptr+32,coords.data.ptr,16);*/ - short[] verts = cast(short[])threads[thread_id].block.batch_vertices; - uint item_id = threads[thread_id].block.items; + short[] verts = cast(short[])block.batch_vertices; + uint item_id = block.items; if(angle == 0) { @@ -619,7 +653,7 @@ struct Renderer uint ind_id = (item_id % batch_size)*4; - ushort[] indices = threads[thread_id].block.batch_indices; + ushort[] indices = block.batch_indices; indices[item_id*6] = cast(ushort)(GfxConfig.meshes[mesh_id].indices[0] + ind_id); indices[item_id*6+1] = cast(ushort)(GfxConfig.meshes[mesh_id].indices[1] + ind_id); @@ -634,14 +668,7 @@ struct Renderer //render_list[item_id].mesh_id = mesh_id; //data_index += 1;//data_offset; - threads[thread_id].block.items++; - if(threads[thread_id].block.items >= VertexBlock.max_items) - { - //pushBlock(threads[thread_id].block); - prepared_items += threads[thread_id].block.items; - threads[thread_id].blocks.add(threads[thread_id].block); - threads[thread_id].block = getBlock(); - } + block.items++; } } @@ -659,6 +686,7 @@ struct Renderer { glClearColor(0,0,0,0); glViewport(0,0,this_.resolution.x,this_.resolution.y); + glDepthMask(1); glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); //glDisable(GL_DEPTH_TEST); glEnable(GL_DEPTH_TEST); @@ -828,9 +856,9 @@ struct Renderer //uint items = item_id/batch_size+1; foreach(i; 0..draw_list.length) { - if(material_id != render_list[i].material_id) + if(material_id != draw_list[i].material_id) { - material_id = render_list[i].material_id; + material_id = draw_list[i].material_id; GfxConfig.materials[material_id].bind(); float[3*4] data = [1,0,0,1,0,0,0,0,0,0,1,1]; GfxConfig.materials[material_id].pushUniforms(data.ptr); diff --git a/demos/utils/source/ecs_utils/math/vector.d b/demos/utils/source/ecs_utils/math/vector.d index a8f14f8..adb3a36 100644 --- a/demos/utils/source/ecs_utils/math/vector.d +++ b/demos/utils/source/ecs_utils/math/vector.d @@ -1,5 +1,7 @@ module ecs_utils.math.vector; +import ecs_utils.utils; + struct vec2 { this(float v) @nogc nothrow @@ -71,6 +73,26 @@ struct vec2 } else static assert(0, "Operator "~op~" not implemented"); } + + float length2() + { + return x*x + y*y; + } + + float length() + { + return sqrtf(length2); + } + + float fastSqrLength() + { + return rsqrt(length2); + } + + vec2 normalize() + { + return this * fastSqrLength(); + } } struct vec4 diff --git a/demos/utils/source/ecs_utils/utils.d b/demos/utils/source/ecs_utils/utils.d index 144d24f..da5453f 100644 --- a/demos/utils/source/ecs_utils/utils.d +++ b/demos/utils/source/ecs_utils/utils.d @@ -2,13 +2,24 @@ module ecs_utils.utils; extern(C): -int randomRange(int min, int max) +import ecs_utils.math.vector; + +enum PI = 3.141592653589793238462643383279502884197169399375105820; + +extern(C) float sqrtf(float x) @nogc nothrow @system; +extern(C) float acosf(float x) @nogc nothrow @system; +extern(C) float sinf(float x) @nogc nothrow @system; +extern(C) float cosf(float x) @nogc nothrow @system; +extern(C) float powf(float x, float y) @nogc nothrow @system; +extern(C) float fabs(float x) @nogc nothrow @system; + +int randomRange(int min, int max) nothrow @nogc @trusted { int range = max - min; return rand() % range - min; } -float randomf() +float randomf() nothrow @nogc @trusted { const float scale = 1.0 / 32_767.0; return cast(float)(rand() & 0x007FFF) * scale; @@ -21,6 +32,38 @@ float randomRangef(float min, float max) return rand()%4096; }*/ +float mix(float x, float y, float a) +{ + //return x*a + y*(a-1); + //return x*a + y*a - y; + return x*(a+y) - y; +} + +float rsqrt(float number) +{ + long i; + float x2, y; + const float threehalfs = 1.5F; + + x2 = number * 0.5F; + y = number; + i = * cast( long * ) &y; // evil floating point bit level hacking + i = 0x5f3759df - ( i >> 1 ); // what the fuck? + y = * cast( float * ) &i; + y = y * ( threehalfs - ( x2 * y * y ) ); // 1st iteration + + return y; +} + +vec2 randomCircularSample() nothrow @nogc @trusted +{ + float angle = 2 * PI * randomf; + float radius = sqrtf(randomf); + float s = sinf(angle); + float c = cosf(angle); + return vec2(c,s)*radius; +} + version(GNU) { public import core.stdc.stdio : printf; @@ -34,7 +77,7 @@ else extern(C) int printf(scope const char* format, ...) @nogc nothrow @system; public import std.array : staticArray; } -extern(C) int rand(); +extern(C) int rand() nothrow @nogc @trusted; version(D_BetterC) {