├── Goworld.app └── Contents │ ├── Frameworks │ └── .gitkeep │ ├── Resources │ └── vulkan │ │ └── icd.d │ │ └── molten_vk.json │ ├── MacOS │ └── launch │ └── Info.plist ├── test ├── .gitignore ├── deferred.png ├── forward.png └── test_suite_test.go ├── .clangd ├── gui ├── widget │ ├── checkbox │ │ ├── style.go │ │ └── checkbox.go │ ├── icon │ │ ├── style.go │ │ └── icon.go │ ├── palette │ │ └── styles.go │ ├── button │ │ ├── style.go │ │ └── button_suite_test.go │ ├── draw_args.go │ ├── widget.go │ ├── test_widget.go │ ├── textbox │ │ └── style.go │ ├── quad.go │ ├── menu │ │ └── menu_test.go │ ├── util.go │ ├── window │ │ ├── style.go │ │ └── modal │ │ │ ├── modal.go │ │ │ └── inputbox.go │ └── image │ │ └── style.go ├── style │ ├── state.go │ ├── font.go │ ├── auto.go │ ├── radius.go │ ├── style_suite_test.go │ ├── border.go │ ├── color.go │ ├── none.go │ ├── position.go │ ├── fixed.go │ ├── rect.go │ ├── percent.go │ ├── column_test.go │ └── row_test.go ├── test │ ├── gui_suite_test.go │ └── layout_test.go ├── node │ ├── dump.go │ ├── util.go │ ├── pool.go │ └── renderer.go ├── events.go ├── hooks │ ├── state.go │ ├── hooks_test.go │ └── hooks.go └── fragment.go ├── render ├── sync │ ├── mutex.go │ ├── semaphore.go │ └── fence.go ├── device │ ├── address.go │ ├── memcpy.go │ ├── ext_darwin.go │ ├── ext_linux.go │ └── queue.go ├── instance │ ├── layer_release.go │ ├── layer_debug.go │ ├── ext_linux.go │ └── ext_darwin.go ├── texture │ ├── slot.go │ ├── const.go │ └── ref.go ├── image │ ├── format.go │ ├── view.go │ └── loader.go ├── descriptor │ ├── descriptor_suite_test.go │ ├── set_mock.go │ ├── set.go │ ├── descriptor.go │ └── descriptor_struct_test.go ├── vertex │ ├── index_type.go │ ├── primitives.go │ ├── pointers.go │ ├── pointer.go │ ├── cull_mode.go │ ├── quad.go │ ├── triangle.go │ └── mesh_generated.go ├── renderpass │ ├── args.go │ ├── subpass.go │ └── attachment │ │ ├── attachment.go │ │ ├── blend.go │ │ ├── color_attachment.go │ │ └── depth_attachment.go ├── shader │ ├── ref.go │ ├── stage.go │ └── details.go ├── buffer │ ├── util.go │ ├── item.go │ ├── buffer_suite_test.go │ ├── allocator.go │ └── array.go ├── vkerror │ └── errors.go ├── command │ ├── recorder.go │ ├── work_channel.go │ └── pool.go ├── swapchain │ └── context.go ├── framebuffer │ └── array.go ├── font │ ├── glyph.go │ └── font_suite_test.go ├── color │ ├── color_suite_test.go │ └── palette.go ├── pipeline │ ├── args.go │ └── layout.go ├── noise │ └── white_noise.go └── material │ └── def.go ├── docs └── img │ ├── screenshot190507.png │ ├── screenshot200926.png │ ├── screenshot220227.png │ ├── screenshot230206.png │ └── screenshot230305.png ├── core ├── time │ └── time.go ├── object │ ├── predicates.go │ ├── events.go │ ├── key.go │ ├── scene.go │ ├── dict.go │ ├── ghost.go │ ├── registry.go │ ├── array.go │ ├── reference_test.go │ ├── property_test.go │ └── reference.go ├── draw │ ├── pass.go │ ├── camera.go │ ├── viewport.go │ └── args.go ├── input │ ├── keys │ │ ├── modifier.go │ │ ├── action.go │ │ ├── util.go │ │ ├── statemap.go │ │ ├── handler.go │ │ └── event.go │ ├── handler.go │ ├── mouse │ │ ├── button.go │ │ ├── action.go │ │ ├── statemap.go │ │ └── utils.go │ └── debug.go ├── light │ ├── type.go │ └── light.go ├── script │ └── script.go └── events │ └── event.go ├── assets ├── builtin │ ├── editor │ │ └── sprites │ │ │ └── light.png │ ├── textures │ │ ├── uv_checker.png │ │ └── color_grading │ │ │ └── none.png │ ├── fonts │ │ ├── SourceSansPro-Bold.ttf │ │ ├── MaterialIcons-Regular.ttf │ │ ├── SourceSansPro-Italic.ttf │ │ └── SourceSansPro-Regular.ttf │ └── shaders │ │ ├── pass │ │ ├── depth.json │ │ ├── lines.json │ │ ├── shadow.json │ │ ├── ui_quad.json │ │ ├── depth.fs.glsl │ │ ├── output.json │ │ ├── output.fs.glsl │ │ ├── blur.json │ │ ├── shadow.fs.glsl │ │ ├── postprocess.json │ │ ├── blur.vs.glsl │ │ ├── light.vs.glsl │ │ ├── output.vs.glsl │ │ ├── ssao.json │ │ ├── ssao.vs.glsl │ │ ├── postprocess.vs.glsl │ │ ├── light.json │ │ ├── lines.fs.glsl │ │ ├── lines.vs.glsl │ │ ├── shadow.vs.glsl │ │ ├── blur.fs.glsl │ │ ├── depth.vs.glsl │ │ ├── ui_quad.vs.glsl │ │ └── postprocess.fs.glsl │ │ ├── deferred │ │ ├── deferred.json │ │ ├── deferred.fs.glsl │ │ └── deferred.vs.glsl │ │ ├── forward │ │ ├── sprite.json │ │ ├── forward.json │ │ ├── forward.vs.glsl │ │ ├── sprite.fs.glsl │ │ ├── forward.fs.glsl │ │ └── sprite.vs.glsl │ │ └── lib │ │ ├── ui.glsl │ │ └── objects.glsl ├── fs │ ├── filesystem.go │ ├── local_fs.go │ └── layered_fs.go ├── mesh.go ├── shader.go ├── builtin_fs.go ├── texture.go ├── hash.go └── assets.go ├── .clang-format ├── math ├── byte4 │ └── byte4.go ├── mat4 │ ├── operations.go │ ├── translation.go │ └── project_test.go ├── spline │ ├── spline_suite_test.go │ └── linear.go ├── shape │ └── sphere.go ├── ivec2 │ └── ivec2.go ├── vec2 │ ├── array.go │ └── operations.go ├── vec3 │ └── array.go ├── vec4 │ ├── array.go │ └── operations.go ├── random │ └── random.go ├── quat │ └── quat_suite_test.go └── noise.go ├── util ├── util_suite_test.go ├── uuid.go ├── timer.go ├── sync_map.go ├── align.go └── align_test.go ├── editor ├── context.go ├── editor.go ├── propedit │ ├── registry.go │ ├── transform_editor.go │ ├── color_editor.go │ ├── bool_editor.go │ ├── container.go │ ├── int_editor.go │ └── float_editor.go ├── builtin │ └── physics_world.go └── inspector.go ├── physics ├── layer.go ├── raycast.go ├── collision_mesh_test.go ├── callback.go ├── collision_mesh.go ├── sphere.go └── box.go ├── engine ├── window │ ├── glfw │ │ ├── init.go │ │ └── util.go │ └── window.go ├── app │ ├── editor.go │ ├── args.go │ ├── interrupter.go │ ├── frame.go │ └── app.go ├── renderer.go ├── profiling.go ├── uniform │ ├── object.go │ ├── object_buffer.go │ ├── light.go │ ├── camera.go │ └── light_buffer.go ├── pool.go ├── cache │ └── shader_cache.go ├── graph │ └── post_node.go └── pass │ ├── shadow_cache.go │ └── pass.go ├── .gitignore ├── geometry ├── lines │ ├── line.go │ ├── immediate.go │ └── lines.go └── sprite │ └── material.go └── LICENSE /Goworld.app/Contents/Frameworks/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /test/.gitignore: -------------------------------------------------------------------------------- 1 | /**_actual.png 2 | /**_failure.png 3 | -------------------------------------------------------------------------------- /.clangd: -------------------------------------------------------------------------------- 1 | CompileFlags: 2 | Add: [-I/usr/local/include/bullet] 3 | -------------------------------------------------------------------------------- /gui/widget/checkbox/style.go: -------------------------------------------------------------------------------- 1 | package checkbox 2 | 3 | var DefaultStyle = Style{} 4 | -------------------------------------------------------------------------------- /render/sync/mutex.go: -------------------------------------------------------------------------------- 1 | package sync 2 | 3 | import "sync" 4 | 5 | type Mutex = sync.Mutex 6 | -------------------------------------------------------------------------------- /test/deferred.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/johanhenriksson/goworld/HEAD/test/deferred.png -------------------------------------------------------------------------------- /test/forward.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/johanhenriksson/goworld/HEAD/test/forward.png -------------------------------------------------------------------------------- /docs/img/screenshot190507.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/johanhenriksson/goworld/HEAD/docs/img/screenshot190507.png -------------------------------------------------------------------------------- /docs/img/screenshot200926.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/johanhenriksson/goworld/HEAD/docs/img/screenshot200926.png -------------------------------------------------------------------------------- /docs/img/screenshot220227.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/johanhenriksson/goworld/HEAD/docs/img/screenshot220227.png -------------------------------------------------------------------------------- /docs/img/screenshot230206.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/johanhenriksson/goworld/HEAD/docs/img/screenshot230206.png -------------------------------------------------------------------------------- /docs/img/screenshot230305.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/johanhenriksson/goworld/HEAD/docs/img/screenshot230305.png -------------------------------------------------------------------------------- /render/device/address.go: -------------------------------------------------------------------------------- 1 | package device 2 | 3 | type Address uint64 4 | 5 | const InvalidAddress = Address(0) 6 | -------------------------------------------------------------------------------- /render/instance/layer_release.go: -------------------------------------------------------------------------------- 1 | //go:build release 2 | 3 | package instance 4 | 5 | var layers = []string{} 6 | -------------------------------------------------------------------------------- /core/time/time.go: -------------------------------------------------------------------------------- 1 | package time 2 | 3 | var Now Time 4 | 5 | type Time struct { 6 | Time float32 7 | Delta float32 8 | } 9 | -------------------------------------------------------------------------------- /gui/style/state.go: -------------------------------------------------------------------------------- 1 | package style 2 | 3 | type State struct { 4 | Focused bool 5 | Hovered bool 6 | Pressed bool 7 | } 8 | -------------------------------------------------------------------------------- /core/object/predicates.go: -------------------------------------------------------------------------------- 1 | package object 2 | 3 | func Is[K any](c Component) bool { 4 | _, ok := c.(K) 5 | return ok 6 | } 7 | -------------------------------------------------------------------------------- /assets/builtin/editor/sprites/light.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/johanhenriksson/goworld/HEAD/assets/builtin/editor/sprites/light.png -------------------------------------------------------------------------------- /assets/builtin/textures/uv_checker.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/johanhenriksson/goworld/HEAD/assets/builtin/textures/uv_checker.png -------------------------------------------------------------------------------- /render/texture/slot.go: -------------------------------------------------------------------------------- 1 | package texture 2 | 3 | type Slot string 4 | 5 | const Diffuse = Slot("diffuse") 6 | const Normal = Slot("normal") 7 | -------------------------------------------------------------------------------- /.clang-format: -------------------------------------------------------------------------------- 1 | BasedOnStyle: Google 2 | IndentWidth: 4 3 | ColumnLimit: 120 4 | 5 | DerivePointerAlignment: false 6 | PointerAlignment: Left 7 | -------------------------------------------------------------------------------- /assets/builtin/fonts/SourceSansPro-Bold.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/johanhenriksson/goworld/HEAD/assets/builtin/fonts/SourceSansPro-Bold.ttf -------------------------------------------------------------------------------- /assets/builtin/fonts/MaterialIcons-Regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/johanhenriksson/goworld/HEAD/assets/builtin/fonts/MaterialIcons-Regular.ttf -------------------------------------------------------------------------------- /assets/builtin/fonts/SourceSansPro-Italic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/johanhenriksson/goworld/HEAD/assets/builtin/fonts/SourceSansPro-Italic.ttf -------------------------------------------------------------------------------- /assets/builtin/fonts/SourceSansPro-Regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/johanhenriksson/goworld/HEAD/assets/builtin/fonts/SourceSansPro-Regular.ttf -------------------------------------------------------------------------------- /assets/builtin/textures/color_grading/none.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/johanhenriksson/goworld/HEAD/assets/builtin/textures/color_grading/none.png -------------------------------------------------------------------------------- /render/image/format.go: -------------------------------------------------------------------------------- 1 | package image 2 | 3 | import "github.com/vkngwrapper/core/v2/core1_0" 4 | 5 | const FormatRGBA8Unorm = core1_0.FormatR8G8B8A8UnsignedNormalized 6 | -------------------------------------------------------------------------------- /render/instance/layer_debug.go: -------------------------------------------------------------------------------- 1 | //go:build !release 2 | 3 | package instance 4 | 5 | var layers = []string{ 6 | "VK_LAYER_KHRONOS_validation", 7 | //"VK_LAYER_LUNARG_api_dump", 8 | } 9 | -------------------------------------------------------------------------------- /assets/builtin/shaders/pass/depth.json: -------------------------------------------------------------------------------- 1 | { 2 | "Inputs": {}, 3 | "Bindings": { 4 | "Camera": 0, 5 | "Objects": 1, 6 | "Lights": 2, 7 | "Textures": 3 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /assets/builtin/shaders/pass/lines.json: -------------------------------------------------------------------------------- 1 | { 2 | "Inputs": {}, 3 | "Bindings": { 4 | "Camera": 0, 5 | "Objects": 1, 6 | "Lights": 2, 7 | "Textures": 3 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /assets/builtin/shaders/pass/shadow.json: -------------------------------------------------------------------------------- 1 | { 2 | "Inputs": {}, 3 | "Bindings": { 4 | "Camera": 0, 5 | "Objects": 1, 6 | "Lights": 2, 7 | "Textures": 3 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /core/object/events.go: -------------------------------------------------------------------------------- 1 | package object 2 | 3 | type EnableHandler interface { 4 | Component 5 | OnEnable() 6 | } 7 | 8 | type DisableHandler interface { 9 | Component 10 | OnDisable() 11 | } 12 | -------------------------------------------------------------------------------- /gui/style/font.go: -------------------------------------------------------------------------------- 1 | package style 2 | 3 | import () 4 | 5 | type Font struct { 6 | Name string 7 | Size int 8 | } 9 | 10 | func (f Font) ApplyFont(fw FontWidget) { 11 | fw.SetFont(f) 12 | } 13 | -------------------------------------------------------------------------------- /render/device/memcpy.go: -------------------------------------------------------------------------------- 1 | package device 2 | 3 | import "unsafe" 4 | 5 | func Memcpy(dst, src unsafe.Pointer, n int) int { 6 | copy(unsafe.Slice((*byte)(dst), n), unsafe.Slice((*byte)(src), n)) 7 | return n 8 | } 9 | -------------------------------------------------------------------------------- /math/byte4/byte4.go: -------------------------------------------------------------------------------- 1 | package byte4 2 | 3 | // T is a 4-component vector of uint8 (bytes) 4 | type T struct { 5 | X, Y, Z, W byte 6 | } 7 | 8 | func New(x, y, z, w byte) T { 9 | return T{x, y, z, w} 10 | } 11 | -------------------------------------------------------------------------------- /gui/style/auto.go: -------------------------------------------------------------------------------- 1 | package style 2 | 3 | type Auto struct{} 4 | 5 | func (a Auto) ApplyWidth(fw FlexWidget) { fw.Flex().StyleSetWidthAuto() } 6 | func (a Auto) ApplyHeight(fw FlexWidget) { fw.Flex().StyleSetHeightAuto() } 7 | -------------------------------------------------------------------------------- /math/mat4/operations.go: -------------------------------------------------------------------------------- 1 | package mat4 2 | 3 | // Ident returns a new 4x4 identity matrix 4 | func Ident() T { 5 | return T{ 6 | 1, 0, 0, 0, 7 | 0, 1, 0, 0, 8 | 0, 0, 1, 0, 9 | 0, 0, 0, 1, 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /assets/builtin/shaders/deferred/deferred.json: -------------------------------------------------------------------------------- 1 | { 2 | "Inputs": {}, 3 | "Bindings": { 4 | "Camera": 0, 5 | "Objects": 1, 6 | "Textures": 2 7 | }, 8 | "Textures": [ 9 | "diffuse" 10 | ] 11 | } 12 | -------------------------------------------------------------------------------- /assets/builtin/shaders/pass/ui_quad.json: -------------------------------------------------------------------------------- 1 | { 2 | "Inputs": { 3 | "position": { 4 | "Index": 0, 5 | "Type": "float" 6 | } 7 | }, 8 | "Bindings": { 9 | "Config": 0, 10 | "Quads": 1, 11 | "Textures": 2 12 | } 13 | } -------------------------------------------------------------------------------- /assets/builtin/shaders/pass/depth.fs.glsl: -------------------------------------------------------------------------------- 1 | #version 450 2 | 3 | #include "lib/common.glsl" 4 | 5 | IN(0, vec3, position) 6 | OUT(0, vec4, position) 7 | 8 | void main() 9 | { 10 | out_position = vec4(in_position, 1); 11 | } 12 | -------------------------------------------------------------------------------- /assets/builtin/shaders/forward/sprite.json: -------------------------------------------------------------------------------- 1 | { 2 | "Inputs": {}, 3 | "Bindings": { 4 | "Camera": 0, 5 | "Objects": 1, 6 | "Lights": 2, 7 | "Textures": 3 8 | }, 9 | "Textures": [ 10 | "diffuse" 11 | ] 12 | } 13 | -------------------------------------------------------------------------------- /assets/builtin/shaders/forward/forward.json: -------------------------------------------------------------------------------- 1 | { 2 | "Inputs": {}, 3 | "Bindings": { 4 | "Camera": 0, 5 | "Objects": 1, 6 | "Lights": 2, 7 | "Textures": 3 8 | }, 9 | "Textures": [ 10 | "diffuse" 11 | ] 12 | } 13 | -------------------------------------------------------------------------------- /gui/test/gui_suite_test.go: -------------------------------------------------------------------------------- 1 | package test 2 | 3 | import ( 4 | "testing" 5 | 6 | . "github.com/onsi/ginkgo/v2" 7 | . "github.com/onsi/gomega" 8 | ) 9 | 10 | func TestGUI(t *testing.T) { 11 | RegisterFailHandler(Fail) 12 | RunSpecs(t, "gui") 13 | } 14 | -------------------------------------------------------------------------------- /assets/builtin/shaders/pass/output.json: -------------------------------------------------------------------------------- 1 | { 2 | "Inputs": { 3 | "position": { 4 | "Index": 0, 5 | "Type": "float" 6 | }, 7 | "tex": { 8 | "Index": 2, 9 | "Type": "float" 10 | } 11 | }, 12 | "Bindings": { 13 | "Output": 0 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /util/util_suite_test.go: -------------------------------------------------------------------------------- 1 | package util_test 2 | 3 | import ( 4 | . "github.com/onsi/ginkgo/v2" 5 | . "github.com/onsi/gomega" 6 | 7 | "testing" 8 | ) 9 | 10 | func TestUtils(t *testing.T) { 11 | RegisterFailHandler(Fail) 12 | RunSpecs(t, "util") 13 | } 14 | -------------------------------------------------------------------------------- /gui/style/radius.go: -------------------------------------------------------------------------------- 1 | package style 2 | 3 | type RadiusProp interface { 4 | ApplyRadius(RadiusWidget) 5 | } 6 | 7 | type RadiusWidget interface { 8 | SetRadius(px float32) 9 | } 10 | 11 | func (p Px) ApplyRadius(w RadiusWidget) { 12 | w.SetRadius(float32(p)) 13 | } 14 | -------------------------------------------------------------------------------- /gui/widget/icon/style.go: -------------------------------------------------------------------------------- 1 | package icon 2 | 3 | import ( 4 | . "github.com/johanhenriksson/goworld/gui/style" 5 | ) 6 | 7 | type Style struct { 8 | Hover Hover 9 | Size int 10 | Color ColorProp 11 | } 12 | 13 | type Hover struct { 14 | Color ColorProp 15 | } 16 | -------------------------------------------------------------------------------- /Goworld.app/Contents/Resources/vulkan/icd.d/molten_vk.json: -------------------------------------------------------------------------------- 1 | { 2 | "file_format_version": "1.0.0", 3 | "ICD": { 4 | "library_path": "../../../Frameworks/libMoltenVK.dylib", 5 | "api_version": "1.2.0", 6 | "is_portability_driver": true 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /assets/builtin/shaders/pass/output.fs.glsl: -------------------------------------------------------------------------------- 1 | #version 450 2 | 3 | #include "lib/common.glsl" 4 | 5 | IN(0, vec2, texcoord) 6 | OUT(0, vec4, color) 7 | SAMPLER(0, diffuse) 8 | 9 | void main() 10 | { 11 | out_color = vec4(texture(tex_diffuse, in_texcoord).rgb, 1); 12 | } 13 | -------------------------------------------------------------------------------- /gui/style/style_suite_test.go: -------------------------------------------------------------------------------- 1 | package style_test 2 | 3 | import ( 4 | "testing" 5 | 6 | . "github.com/onsi/ginkgo/v2" 7 | . "github.com/onsi/gomega" 8 | ) 9 | 10 | func TestStyle(t *testing.T) { 11 | RegisterFailHandler(Fail) 12 | RunSpecs(t, "gui/style") 13 | } 14 | -------------------------------------------------------------------------------- /assets/fs/filesystem.go: -------------------------------------------------------------------------------- 1 | package fs 2 | 3 | import "fmt" 4 | 5 | var ErrNotFound = fmt.Errorf("not found") 6 | var ErrImmutable = fmt.Errorf("immutable") 7 | 8 | type Filesystem interface { 9 | Read(key string) ([]byte, error) 10 | Write(key string, data []byte) error 11 | } 12 | -------------------------------------------------------------------------------- /math/spline/spline_suite_test.go: -------------------------------------------------------------------------------- 1 | package spline_test 2 | 3 | import ( 4 | . "github.com/onsi/ginkgo/v2" 5 | . "github.com/onsi/gomega" 6 | 7 | "testing" 8 | ) 9 | 10 | func TestSpline(t *testing.T) { 11 | RegisterFailHandler(Fail) 12 | RunSpecs(t, "math/spline") 13 | } 14 | -------------------------------------------------------------------------------- /assets/builtin/shaders/pass/blur.json: -------------------------------------------------------------------------------- 1 | { 2 | "Inputs": { 3 | "position": { 4 | "Index": 0, 5 | "Type": "float" 6 | }, 7 | "tex": { 8 | "Index": 2, 9 | "Type": "float" 10 | } 11 | }, 12 | "Bindings": { 13 | "Input": 0 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /editor/context.go: -------------------------------------------------------------------------------- 1 | package editor 2 | 3 | import ( 4 | "github.com/johanhenriksson/goworld/core/camera" 5 | "github.com/johanhenriksson/goworld/core/object" 6 | ) 7 | 8 | type Context struct { 9 | Objects object.Pool 10 | Camera *camera.Camera 11 | Scene object.Object 12 | } 13 | -------------------------------------------------------------------------------- /physics/layer.go: -------------------------------------------------------------------------------- 1 | package physics 2 | 3 | type Mask uint32 4 | 5 | const All = Mask(0xFFFFFFFF) 6 | const None = Mask(0) 7 | 8 | func Layers(layers ...Mask) Mask { 9 | mask := None 10 | for _, layer := range layers { 11 | mask = mask | layer 12 | } 13 | return mask 14 | } 15 | -------------------------------------------------------------------------------- /assets/builtin/shaders/pass/shadow.fs.glsl: -------------------------------------------------------------------------------- 1 | #version 450 2 | 3 | #include "lib/common.glsl" 4 | #include "lib/lighting.glsl" 5 | 6 | IN(0, float, depth) 7 | 8 | void main() 9 | { 10 | // exponential depth 11 | gl_FragDepth = exp(SHADOW_POWER * in_depth) / exp(SHADOW_POWER); 12 | } 13 | -------------------------------------------------------------------------------- /core/draw/pass.go: -------------------------------------------------------------------------------- 1 | package draw 2 | 3 | import ( 4 | "github.com/johanhenriksson/goworld/core/object" 5 | "github.com/johanhenriksson/goworld/render/command" 6 | ) 7 | 8 | type Pass interface { 9 | Name() string 10 | Record(command.Recorder, Args, object.Component) 11 | Destroy() 12 | } 13 | -------------------------------------------------------------------------------- /render/descriptor/descriptor_suite_test.go: -------------------------------------------------------------------------------- 1 | package descriptor_test 2 | 3 | import ( 4 | "testing" 5 | 6 | . "github.com/onsi/ginkgo/v2" 7 | . "github.com/onsi/gomega" 8 | ) 9 | 10 | func TestDescriptor(t *testing.T) { 11 | RegisterFailHandler(Fail) 12 | RunSpecs(t, "render/descriptor") 13 | } 14 | -------------------------------------------------------------------------------- /assets/builtin/shaders/pass/postprocess.json: -------------------------------------------------------------------------------- 1 | { 2 | "Inputs": { 3 | "position": { 4 | "Index": 0, 5 | "Type": "float" 6 | }, 7 | "tex": { 8 | "Index": 2, 9 | "Type": "float" 10 | } 11 | }, 12 | "Bindings": { 13 | "Input": 0, 14 | "LUT": 1 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /gui/node/dump.go: -------------------------------------------------------------------------------- 1 | package node 2 | 3 | import "fmt" 4 | 5 | func Dump(node T) { 6 | dump(node, "") 7 | } 8 | 9 | func dump(node T, indent string) { 10 | fmt.Printf("%s%s\n", indent, node.Key()) 11 | indent = indent + " " 12 | for _, child := range node.Children() { 13 | dump(child, indent) 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /gui/widget/palette/styles.go: -------------------------------------------------------------------------------- 1 | package palette 2 | 3 | import ( 4 | . "github.com/johanhenriksson/goworld/gui/style" 5 | "github.com/johanhenriksson/goworld/gui/widget/rect" 6 | ) 7 | 8 | var SwatchStyle = rect.Style{ 9 | Grow: Grow(0), 10 | Shrink: Shrink(0), 11 | Basis: Pct(20), 12 | Height: Px(20), 13 | } 14 | -------------------------------------------------------------------------------- /core/input/keys/modifier.go: -------------------------------------------------------------------------------- 1 | package keys 2 | 3 | import "github.com/go-gl/glfw/v3.3/glfw" 4 | 5 | type Modifier glfw.ModifierKey 6 | 7 | const ( 8 | NoMod = Modifier(0) 9 | Shift = Modifier(glfw.ModShift) 10 | Ctrl = Modifier(glfw.ModControl) 11 | Alt = Modifier(glfw.ModAlt) 12 | Super = Modifier(glfw.ModSuper) 13 | ) 14 | -------------------------------------------------------------------------------- /engine/window/glfw/init.go: -------------------------------------------------------------------------------- 1 | package glfw 2 | 3 | import ( 4 | "runtime" 5 | 6 | "github.com/go-gl/glfw/v3.3/glfw" 7 | ) 8 | 9 | func init() { 10 | // glfw event handling must run on the main OS thread 11 | runtime.LockOSThread() 12 | 13 | // init glfw 14 | if err := glfw.Init(); err != nil { 15 | panic(err) 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /engine/app/editor.go: -------------------------------------------------------------------------------- 1 | package app 2 | 3 | import ( 4 | "github.com/johanhenriksson/goworld/core/object" 5 | "github.com/johanhenriksson/goworld/editor" 6 | _ "github.com/johanhenriksson/goworld/editor/builtin" 7 | ) 8 | 9 | func RunEditor(args Args, scenefunc object.SceneFunc) { 10 | Run(args, editor.WrapScene(scenefunc)) 11 | } 12 | -------------------------------------------------------------------------------- /render/vertex/index_type.go: -------------------------------------------------------------------------------- 1 | package vertex 2 | 3 | import "github.com/vkngwrapper/core/v2/core1_0" 4 | 5 | func IndexType(size int) core1_0.IndexType { 6 | switch size { 7 | case 2: 8 | return core1_0.IndexTypeUInt16 9 | case 4: 10 | return core1_0.IndexTypeUInt32 11 | default: 12 | panic("illegal index size") 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /engine/renderer.go: -------------------------------------------------------------------------------- 1 | package engine 2 | 3 | import ( 4 | "image" 5 | 6 | "github.com/johanhenriksson/goworld/core/object" 7 | ) 8 | 9 | type RendererFunc func(App, Target) Renderer 10 | 11 | type Renderer interface { 12 | Draw(scene object.Object, time, delta float32) 13 | Recreate() 14 | Screengrab() *image.RGBA 15 | Destroy() 16 | } 17 | -------------------------------------------------------------------------------- /assets/builtin/shaders/pass/blur.vs.glsl: -------------------------------------------------------------------------------- 1 | #version 450 2 | 3 | #include "lib/common.glsl" 4 | 5 | IN(0, vec3, position) 6 | IN(2, vec2, tex) 7 | OUT(0, vec2, texcoord) 8 | 9 | out gl_PerVertex 10 | { 11 | vec4 gl_Position; 12 | }; 13 | 14 | void main() 15 | { 16 | out_texcoord = in_tex; 17 | gl_Position = vec4(in_position, 1); 18 | } 19 | -------------------------------------------------------------------------------- /assets/builtin/shaders/pass/light.vs.glsl: -------------------------------------------------------------------------------- 1 | #version 450 2 | 3 | #include "lib/common.glsl" 4 | 5 | IN(0, vec3, position) 6 | IN(2, vec2, tex) 7 | OUT(0, vec2, texcoord) 8 | 9 | out gl_PerVertex 10 | { 11 | vec4 gl_Position; 12 | }; 13 | 14 | void main() 15 | { 16 | out_texcoord = in_tex; 17 | gl_Position = vec4(in_position, 1); 18 | } 19 | -------------------------------------------------------------------------------- /assets/builtin/shaders/pass/output.vs.glsl: -------------------------------------------------------------------------------- 1 | #version 450 2 | 3 | #include "lib/common.glsl" 4 | 5 | IN(0, vec3, position) 6 | IN(2, vec2, tex) 7 | OUT(0, vec2, texcoord) 8 | 9 | out gl_PerVertex 10 | { 11 | vec4 gl_Position; 12 | }; 13 | 14 | void main() 15 | { 16 | out_texcoord = in_tex; 17 | gl_Position = vec4(in_position, 1); 18 | } 19 | -------------------------------------------------------------------------------- /assets/builtin/shaders/pass/ssao.json: -------------------------------------------------------------------------------- 1 | { 2 | "Inputs": { 3 | "position": { 4 | "Index": 0, 5 | "Type": "float" 6 | }, 7 | "tex": { 8 | "Index": 2, 9 | "Type": "float" 10 | } 11 | }, 12 | "Bindings": { 13 | "Params": 0, 14 | "Position": 1, 15 | "Normal": 2, 16 | "Noise": 3 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /assets/builtin/shaders/pass/ssao.vs.glsl: -------------------------------------------------------------------------------- 1 | #version 450 2 | 3 | #include "lib/common.glsl" 4 | 5 | IN(0, vec3, position) 6 | IN(2, vec2, tex) 7 | OUT(0, vec2, texcoord) 8 | 9 | out gl_PerVertex 10 | { 11 | vec4 gl_Position; 12 | }; 13 | 14 | void main() 15 | { 16 | out_texcoord = in_tex; 17 | gl_Position = vec4(in_position, 1); 18 | } 19 | -------------------------------------------------------------------------------- /gui/node/util.go: -------------------------------------------------------------------------------- 1 | package node 2 | 3 | import ( 4 | "github.com/samber/lo" 5 | ) 6 | 7 | func If(condition bool, n T) T { 8 | if condition { 9 | return n 10 | } 11 | return nil 12 | } 13 | 14 | func Map[K any](items []K, transform func(K) T) []T { 15 | return lo.Map(items, func(it K, _ int) T { 16 | return transform(it) 17 | }) 18 | } 19 | -------------------------------------------------------------------------------- /assets/builtin/shaders/pass/postprocess.vs.glsl: -------------------------------------------------------------------------------- 1 | #version 450 2 | 3 | #include "lib/common.glsl" 4 | 5 | IN(0, vec3, position) 6 | IN(2, vec2, tex) 7 | OUT(0, vec2, texcoord) 8 | 9 | out gl_PerVertex 10 | { 11 | vec4 gl_Position; 12 | }; 13 | 14 | void main() 15 | { 16 | out_texcoord = in_tex; 17 | gl_Position = vec4(in_position, 1); 18 | } 19 | -------------------------------------------------------------------------------- /assets/mesh.go: -------------------------------------------------------------------------------- 1 | package assets 2 | 3 | import ( 4 | "github.com/johanhenriksson/goworld/assets/fs" 5 | "github.com/johanhenriksson/goworld/render/vertex" 6 | ) 7 | 8 | type Mesh interface { 9 | Asset 10 | 11 | // LoadMesh is called by mesh caches and loaders, and should return the mesh data. 12 | LoadMesh(assets fs.Filesystem) vertex.Mesh 13 | } 14 | -------------------------------------------------------------------------------- /core/light/type.go: -------------------------------------------------------------------------------- 1 | package light 2 | 3 | // Type indicates which kind of light. Point, Directional etc 4 | type Type uint32 5 | 6 | const ( 7 | // PointLight is a normal light casting rays in all directions. 8 | TypePoint Type = 1 9 | 10 | // DirectionalLight is a directional light source, casting parallell rays. 11 | TypeDirectional Type = 2 12 | ) 13 | -------------------------------------------------------------------------------- /render/renderpass/args.go: -------------------------------------------------------------------------------- 1 | package renderpass 2 | 3 | import ( 4 | "github.com/johanhenriksson/goworld/render/renderpass/attachment" 5 | ) 6 | 7 | type Args struct { 8 | Name string 9 | ColorAttachments []attachment.Color 10 | DepthAttachment *attachment.Depth 11 | 12 | Subpasses []Subpass 13 | Dependencies []SubpassDependency 14 | } 15 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | goworld 2 | chunks/ 3 | profiling/ 4 | .vscode/ 5 | .idea/* 6 | .DS_Store 7 | TODO.md 8 | __debug_bin 9 | *.test 10 | *.cap 11 | 12 | # temproarily ignore models 13 | assets/models/ 14 | 15 | # ignore screenshots etc 16 | /*.png 17 | 18 | Goworld.app/Contents/Resources/ 19 | Goworld.app/Contents/MacOS/ 20 | Goworld.app/Contents/Frameworks/ 21 | 22 | assets/maps/ 23 | -------------------------------------------------------------------------------- /assets/shader.go: -------------------------------------------------------------------------------- 1 | package assets 2 | 3 | import ( 4 | "github.com/johanhenriksson/goworld/assets/fs" 5 | "github.com/johanhenriksson/goworld/render/device" 6 | "github.com/johanhenriksson/goworld/render/shader" 7 | ) 8 | 9 | type Shader interface { 10 | Asset 11 | 12 | LoadShader(fs.Filesystem, *device.Device) *shader.Shader 13 | } 14 | 15 | var _ Shader = shader.Ref("") 16 | -------------------------------------------------------------------------------- /geometry/lines/line.go: -------------------------------------------------------------------------------- 1 | package lines 2 | 3 | import ( 4 | "github.com/johanhenriksson/goworld/math/vec3" 5 | "github.com/johanhenriksson/goworld/render/color" 6 | ) 7 | 8 | type Line struct { 9 | Start vec3.T 10 | End vec3.T 11 | Color color.T 12 | } 13 | 14 | // L creates a new line segment 15 | func L(start, end vec3.T, color color.T) Line { 16 | return Line{start, end, color} 17 | } 18 | -------------------------------------------------------------------------------- /render/vertex/primitives.go: -------------------------------------------------------------------------------- 1 | package vertex 2 | 3 | import "github.com/vkngwrapper/core/v2/core1_0" 4 | 5 | type Primitive core1_0.PrimitiveTopology 6 | 7 | const ( 8 | Triangles Primitive = Primitive(core1_0.PrimitiveTopologyTriangleList) 9 | Lines = Primitive(core1_0.PrimitiveTopologyLineList) 10 | Points = Primitive(core1_0.PrimitiveTopologyPointList) 11 | ) 12 | -------------------------------------------------------------------------------- /Goworld.app/Contents/MacOS/launch: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | cd $(dirname "$0") 3 | log=/tmp/goworld.log 4 | echo starting goworld > $log 5 | export MVK_CONFIG_USE_METAL_ARGUMENT_BUFFERS=1 6 | export VK_LAYER_ENABLES=VK_VALIDATION_FEATURE_ENABLE_SYNCHRONIZATION_VALIDATION_EXT 7 | export ASSET_PATH=Resources 8 | export PATH=$PATH:$(pwd) 9 | ./goworld >> $log 2>&1 10 | echo goworld exited with code $? >> $log 11 | -------------------------------------------------------------------------------- /core/input/handler.go: -------------------------------------------------------------------------------- 1 | package input 2 | 3 | import ( 4 | "github.com/johanhenriksson/goworld/core/input/keys" 5 | "github.com/johanhenriksson/goworld/core/input/mouse" 6 | ) 7 | 8 | type Handler interface { 9 | KeyHandler 10 | MouseHandler 11 | } 12 | 13 | type KeyHandler interface { 14 | KeyEvent(keys.Event) 15 | } 16 | 17 | type MouseHandler interface { 18 | MouseEvent(mouse.Event) 19 | } 20 | -------------------------------------------------------------------------------- /gui/events.go: -------------------------------------------------------------------------------- 1 | package gui 2 | 3 | import ( 4 | "github.com/johanhenriksson/goworld/core/input/keys" 5 | "github.com/johanhenriksson/goworld/core/input/mouse" 6 | ) 7 | 8 | // Dummy mouse handler that consumes the event and does nothing. 9 | func ConsumeMouse(ev mouse.Event) { 10 | ev.Consume() 11 | } 12 | 13 | // Dummy key handler that consumes the event and does nothing. 14 | func ConsumeKey(ev keys.Event) {} 15 | -------------------------------------------------------------------------------- /math/shape/sphere.go: -------------------------------------------------------------------------------- 1 | package shape 2 | 3 | import "github.com/johanhenriksson/goworld/math/vec3" 4 | 5 | type Sphere struct { 6 | Center vec3.T 7 | Radius float32 8 | } 9 | 10 | func (s *Sphere) IntersectsSphere(other *Sphere) bool { 11 | sepAxis := s.Center.Sub(other.Center) 12 | radiiSum := s.Radius + other.Radius 13 | intersects := sepAxis.LengthSqr() < (radiiSum * radiiSum) 14 | return intersects 15 | } 16 | -------------------------------------------------------------------------------- /assets/builtin/shaders/pass/light.json: -------------------------------------------------------------------------------- 1 | { 2 | "Inputs": { 3 | "position": { 4 | "Index": 0, 5 | "Type": "float" 6 | }, 7 | "tex": { 8 | "Index": 2, 9 | "Type": "float" 10 | } 11 | }, 12 | "Bindings": { 13 | "Camera": 0, 14 | "Lights": 1, 15 | "Diffuse": 2, 16 | "Normal": 3, 17 | "Position": 4, 18 | "Occlusion": 5, 19 | "Shadow": 6 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /util/uuid.go: -------------------------------------------------------------------------------- 1 | package util 2 | 3 | import ( 4 | "math/rand" 5 | ) 6 | 7 | var idCharset = []byte("abcdefghijklmnopqrstuvxyzABCDEFGHIJKLMNOPQRSTUVXYZ0123456789") 8 | 9 | func NewUUID(length int) string { 10 | id := make([]byte, length) 11 | charsetLen := int64(len(idCharset)) 12 | for i := 0; i < length; i++ { 13 | ch := rand.Int63n(charsetLen) 14 | id[i] = idCharset[ch] 15 | } 16 | return string(id) 17 | } 18 | -------------------------------------------------------------------------------- /gui/widget/button/style.go: -------------------------------------------------------------------------------- 1 | package button 2 | 3 | import ( 4 | . "github.com/johanhenriksson/goworld/gui/style" 5 | ) 6 | 7 | type Style struct { 8 | Hover Hover 9 | TextColor ColorProp 10 | BgColor ColorProp 11 | Padding PaddingProp 12 | Margin MarginProp 13 | Border BorderProp 14 | Radius RadiusProp 15 | } 16 | 17 | type Hover struct { 18 | TextColor ColorProp 19 | BgColor ColorProp 20 | } 21 | -------------------------------------------------------------------------------- /engine/profiling.go: -------------------------------------------------------------------------------- 1 | package engine 2 | 3 | import ( 4 | "fmt" 5 | "log" 6 | "net/http" 7 | _ "net/http/pprof" 8 | ) 9 | 10 | func RunProfilingServer(port int) { 11 | if err := http.ListenAndServe(fmt.Sprintf("localhost:%d", port), nil); err != nil { 12 | log.Println("failed to launch profiling http server on port", port) 13 | } else { 14 | log.Printf("pprof server available at http://localhost:%d\n", port) 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /core/object/key.go: -------------------------------------------------------------------------------- 1 | package object 2 | 3 | import ( 4 | "math/rand" 5 | "strconv" 6 | ) 7 | 8 | func Key(prefix string, object Component) string { 9 | p := len(prefix) 10 | buffer := make([]byte, p+1, p+9) 11 | copy(buffer, []byte(prefix)) 12 | buffer[p] = '-' 13 | dst := strconv.AppendUint(buffer, uint64(object.ID()), 10) 14 | return string(dst) 15 | } 16 | 17 | func ID() uint { 18 | return uint(rand.Int63n(0xFFFFFFFF)) 19 | } 20 | -------------------------------------------------------------------------------- /render/descriptor/set_mock.go: -------------------------------------------------------------------------------- 1 | package descriptor 2 | 3 | import ( 4 | "github.com/vkngwrapper/core/v2/core1_0" 5 | ) 6 | 7 | type SetMock struct { 8 | } 9 | 10 | var _ Set = &SetMock{} 11 | 12 | func (s *SetMock) Ptr() core1_0.DescriptorSet { 13 | return nil 14 | } 15 | 16 | func (s *SetMock) Write(write core1_0.WriteDescriptorSet) { 17 | } 18 | 19 | func (s *SetMock) Destroy() {} 20 | 21 | func (s *SetMock) adopt(Descriptor) {} 22 | -------------------------------------------------------------------------------- /util/timer.go: -------------------------------------------------------------------------------- 1 | package util 2 | 3 | import ( 4 | "log" 5 | "time" 6 | ) 7 | 8 | var timers = map[string]time.Time{} 9 | 10 | func Timer(name string) { 11 | timers[name] = time.Now() 12 | } 13 | 14 | func Elapsed(name string) float32 { 15 | if start, exists := timers[name]; exists { 16 | dt := float32(time.Since(start).Seconds()) 17 | log.Printf("Elapsed %s=%.2fms\n", name, dt*1000) 18 | return dt 19 | } 20 | return 0 21 | } 22 | -------------------------------------------------------------------------------- /render/vertex/pointers.go: -------------------------------------------------------------------------------- 1 | package vertex 2 | 3 | import ( 4 | "strings" 5 | 6 | "github.com/samber/lo" 7 | ) 8 | 9 | type Pointers []Pointer 10 | 11 | func (ps Pointers) BufferString() string { 12 | names := lo.Map(ps, func(p Pointer, _ int) string { return p.Name }) 13 | return strings.Join(names, ",") 14 | } 15 | 16 | func (ps Pointers) Stride() int { 17 | if len(ps) == 0 { 18 | return 0 19 | } 20 | return ps[0].Stride 21 | } 22 | -------------------------------------------------------------------------------- /test/test_suite_test.go: -------------------------------------------------------------------------------- 1 | package test 2 | 3 | import ( 4 | "log" 5 | 6 | . "github.com/onsi/ginkgo/v2" 7 | . "github.com/onsi/gomega" 8 | 9 | "testing" 10 | 11 | "github.com/johanhenriksson/goworld/math/random" 12 | ) 13 | 14 | func TestSuiteTest(t *testing.T) { 15 | random.Seed(0) 16 | log.SetOutput(GinkgoWriter) 17 | 18 | // todo: clean up old failure/actual images 19 | 20 | RegisterFailHandler(Fail) 21 | RunSpecs(t, "test") 22 | } 23 | -------------------------------------------------------------------------------- /render/instance/ext_linux.go: -------------------------------------------------------------------------------- 1 | package instance 2 | 3 | import ( 4 | "github.com/vkngwrapper/extensions/v2/ext_debug_utils" 5 | "github.com/vkngwrapper/extensions/v2/khr_get_physical_device_properties2" 6 | "github.com/vkngwrapper/extensions/v2/khr_surface" 7 | ) 8 | 9 | var extensions = []string{ 10 | khr_surface.ExtensionName, 11 | khr_get_physical_device_properties2.ExtensionName, 12 | ext_debug_utils.ExtensionName, 13 | 14 | "VK_KHR_xcb_surface", 15 | } 16 | -------------------------------------------------------------------------------- /assets/builtin/shaders/pass/lines.fs.glsl: -------------------------------------------------------------------------------- 1 | #version 450 2 | 3 | #include "lib/common.glsl" 4 | 5 | IN(0, vec3, color) 6 | OUT(0, vec4, color) 7 | 8 | float FogDensity = 0.04; 9 | 10 | void main() 11 | { 12 | float depth = gl_FragCoord.z / gl_FragCoord.w - 0.2; 13 | 14 | // Calculate the fog factor 15 | float fogFactor = exp(-depth * FogDensity); 16 | fogFactor = clamp(fogFactor, 0.0, 1.0); 17 | 18 | out_color = vec4(in_color, fogFactor); 19 | } 20 | -------------------------------------------------------------------------------- /render/vertex/pointer.go: -------------------------------------------------------------------------------- 1 | package vertex 2 | 3 | import ( 4 | "github.com/johanhenriksson/goworld/render/types" 5 | ) 6 | 7 | type Pointer struct { 8 | Name string 9 | Binding int 10 | Source types.Type 11 | Destination types.Type 12 | Elements int 13 | Stride int 14 | Offset int 15 | Normalize bool 16 | } 17 | 18 | func (p *Pointer) Bind(binding int, kind types.Type) { 19 | p.Binding = binding 20 | p.Destination = kind 21 | } 22 | -------------------------------------------------------------------------------- /engine/uniform/object.go: -------------------------------------------------------------------------------- 1 | package uniform 2 | 3 | import ( 4 | "structs" 5 | 6 | "github.com/johanhenriksson/goworld/math/mat4" 7 | "github.com/johanhenriksson/goworld/render/device" 8 | ) 9 | 10 | const MaxTextures = 16 11 | 12 | type TextureId uint32 13 | type TextureIds [MaxTextures]TextureId 14 | 15 | type Object struct { 16 | _ structs.HostLayout 17 | 18 | Model mat4.T 19 | Textures TextureIds 20 | 21 | Vertices device.Address 22 | Indices device.Address 23 | } 24 | -------------------------------------------------------------------------------- /Goworld.app/Contents/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleExecutable 6 | launch 7 | CFBundleIconFile 8 | icon.icns 9 | CFBundleIdentifier 10 | se.devion.goworld 11 | NSHighResolutionCapable 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /math/ivec2/ivec2.go: -------------------------------------------------------------------------------- 1 | package ivec2 2 | 3 | var Zero = T{} 4 | var One = T{X: 1, Y: 1} 5 | var UnitX = T{X: 1} 6 | var UnitY = T{Y: 1} 7 | 8 | type T struct { 9 | X int 10 | Y int 11 | } 12 | 13 | func New(x, y int) T { 14 | return T{ 15 | X: x, 16 | Y: y, 17 | } 18 | } 19 | 20 | func (v T) Add(v2 T) T { 21 | return T{ 22 | X: v.X + v2.X, 23 | Y: v.Y + v2.Y, 24 | } 25 | } 26 | 27 | func (v T) Sub(v2 T) T { 28 | return T{ 29 | X: v.X - v2.X, 30 | Y: v.Y - v2.Y, 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /render/vertex/cull_mode.go: -------------------------------------------------------------------------------- 1 | package vertex 2 | 3 | import "github.com/vkngwrapper/core/v2/core1_0" 4 | 5 | const ( 6 | CullNone = CullMode(core1_0.CullModeFlags(0)) 7 | CullFront = CullMode(core1_0.CullModeFront) 8 | CullBack = CullMode(core1_0.CullModeBack) 9 | ) 10 | 11 | type CullMode core1_0.CullModeFlags 12 | 13 | func (c CullMode) flags() core1_0.CullModeFlags { 14 | return core1_0.CullModeFlags(c) 15 | } 16 | 17 | func (c CullMode) String() string { 18 | return c.flags().String() 19 | } 20 | -------------------------------------------------------------------------------- /core/draw/camera.go: -------------------------------------------------------------------------------- 1 | package draw 2 | 3 | import ( 4 | "github.com/johanhenriksson/goworld/math/mat4" 5 | "github.com/johanhenriksson/goworld/math/vec3" 6 | ) 7 | 8 | type Camera struct { 9 | Proj mat4.T 10 | View mat4.T 11 | ViewProj mat4.T 12 | ProjInv mat4.T 13 | ViewInv mat4.T 14 | ViewProjInv mat4.T 15 | Position vec3.T 16 | Forward vec3.T 17 | Viewport Viewport 18 | Near float32 19 | Far float32 20 | Aspect float32 21 | Fov float32 22 | } 23 | -------------------------------------------------------------------------------- /core/input/mouse/button.go: -------------------------------------------------------------------------------- 1 | package mouse 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/go-gl/glfw/v3.3/glfw" 7 | ) 8 | 9 | type Button glfw.MouseButton 10 | 11 | const ( 12 | Button1 Button = Button(glfw.MouseButton1) 13 | Button2 = Button(glfw.MouseButton2) 14 | Button3 = Button(glfw.MouseButton3) 15 | Button4 = Button(glfw.MouseButton4) 16 | Button5 = Button(glfw.MouseButton5) 17 | ) 18 | 19 | func (b Button) String() string { 20 | return fmt.Sprintf("Button %d", int(b)+1) 21 | } 22 | -------------------------------------------------------------------------------- /core/draw/viewport.go: -------------------------------------------------------------------------------- 1 | package draw 2 | 3 | import "github.com/johanhenriksson/goworld/math/vec2" 4 | 5 | type Viewport struct { 6 | Width int 7 | Height int 8 | Scale float32 9 | } 10 | 11 | func (s Viewport) Aspect() float32 { 12 | return float32(s.Width) / float32(s.Height) 13 | } 14 | 15 | func (s Viewport) Size() vec2.T { 16 | return vec2.NewI(s.Width, s.Height) 17 | } 18 | 19 | func (s Viewport) NormalizeCursor(cursor vec2.T) vec2.T { 20 | return cursor.Div(s.Size()).Sub(vec2.New(0.5, 0.5)).Scaled(2) 21 | } 22 | -------------------------------------------------------------------------------- /math/vec2/array.go: -------------------------------------------------------------------------------- 1 | package vec2 2 | 3 | import "unsafe" 4 | 5 | // Array holds an array of 2-component vectors 6 | type Array []T 7 | 8 | // Elements returns the number of elements in the array 9 | func (a Array) Elements() int { 10 | return len(a) 11 | } 12 | 13 | // Size return the byte size of an element 14 | func (a Array) Size() int { 15 | return 8 16 | } 17 | 18 | // Pointer returns an unsafe pointer to the first element in the array 19 | func (a Array) Pointer() unsafe.Pointer { 20 | return unsafe.Pointer(&a[0]) 21 | } 22 | -------------------------------------------------------------------------------- /render/device/ext_darwin.go: -------------------------------------------------------------------------------- 1 | package device 2 | 3 | import ( 4 | "github.com/vkngwrapper/extensions/v2/khr_buffer_device_address" 5 | "github.com/vkngwrapper/extensions/v2/khr_maintenance3" 6 | "github.com/vkngwrapper/extensions/v2/khr_portability_subset" 7 | "github.com/vkngwrapper/extensions/v2/khr_swapchain" 8 | ) 9 | 10 | var deviceExtensions = []string{ 11 | khr_swapchain.ExtensionName, 12 | khr_buffer_device_address.ExtensionName, 13 | khr_portability_subset.ExtensionName, 14 | khr_maintenance3.ExtensionName, 15 | } 16 | -------------------------------------------------------------------------------- /render/device/ext_linux.go: -------------------------------------------------------------------------------- 1 | package device 2 | 3 | import ( 4 | "github.com/vkngwrapper/extensions/v2/khr_buffer_device_address" 5 | "github.com/vkngwrapper/extensions/v2/khr_maintenance3" 6 | "github.com/vkngwrapper/extensions/v2/khr_portability_subset" 7 | "github.com/vkngwrapper/extensions/v2/khr_swapchain" 8 | ) 9 | 10 | var deviceExtensions = []string{ 11 | khr_swapchain.ExtensionName, 12 | khr_buffer_device_address.ExtensionName, 13 | khr_portability_subset.ExtensionName, 14 | khr_maintenance3.ExtensionName, 15 | } 16 | -------------------------------------------------------------------------------- /math/vec3/array.go: -------------------------------------------------------------------------------- 1 | package vec3 2 | 3 | import "unsafe" 4 | 5 | // Array holds an array of 3-component vectors 6 | type Array []T 7 | 8 | // Elements returns the number of elements in the array 9 | func (a Array) Elements() int { 10 | return len(a) 11 | } 12 | 13 | // Size return the byte size of an element 14 | func (a Array) Size() int { 15 | return 12 16 | } 17 | 18 | // Pointer returns an unsafe pointer to the first element in the array 19 | func (a Array) Pointer() unsafe.Pointer { 20 | return unsafe.Pointer(&a[0]) 21 | } 22 | -------------------------------------------------------------------------------- /math/vec4/array.go: -------------------------------------------------------------------------------- 1 | package vec4 2 | 3 | import "unsafe" 4 | 5 | // Array holds an array of 4-component vectors 6 | type Array []T 7 | 8 | // Elements returns the number of elements in the array 9 | func (a Array) Elements() int { 10 | return len(a) 11 | } 12 | 13 | // Size return the byte size of an element 14 | func (a Array) Size() int { 15 | return 16 16 | } 17 | 18 | // Pointer returns an unsafe pointer to the first element in the array 19 | func (a Array) Pointer() unsafe.Pointer { 20 | return unsafe.Pointer(&a[0]) 21 | } 22 | -------------------------------------------------------------------------------- /core/draw/args.go: -------------------------------------------------------------------------------- 1 | package draw 2 | 3 | import ( 4 | "github.com/johanhenriksson/goworld/math/mat4" 5 | ) 6 | 7 | // Args holds the arguments used to perform a draw pass. 8 | // Includes the various transformation matrices and position of the camera. 9 | type Args struct { 10 | Frame int 11 | Time float32 12 | Delta float32 13 | Camera Camera 14 | Transform mat4.T 15 | } 16 | 17 | // Apply the effects of a transform 18 | func (d Args) Apply(t mat4.T) Args { 19 | d.Transform = d.Transform.Mul(&t) 20 | return d 21 | } 22 | -------------------------------------------------------------------------------- /core/script/script.go: -------------------------------------------------------------------------------- 1 | package script 2 | 3 | import "github.com/johanhenriksson/goworld/core/object" 4 | 5 | type T interface { 6 | object.Component 7 | } 8 | 9 | type script struct { 10 | object.Component 11 | fn Behavior 12 | } 13 | 14 | type Behavior func(scene, self object.Component, dt float32) 15 | 16 | func New(pool object.Pool, fn Behavior) T { 17 | return object.NewComponent(pool, &script{ 18 | fn: fn, 19 | }) 20 | } 21 | 22 | func (s *script) Update(scene object.Component, dt float32) { 23 | s.fn(scene, s, dt) 24 | } 25 | -------------------------------------------------------------------------------- /gui/widget/draw_args.go: -------------------------------------------------------------------------------- 1 | package widget 2 | 3 | import ( 4 | "github.com/johanhenriksson/goworld/core/draw" 5 | "github.com/johanhenriksson/goworld/engine/cache" 6 | "github.com/johanhenriksson/goworld/math/vec3" 7 | "github.com/johanhenriksson/goworld/render/command" 8 | ) 9 | 10 | type Renderer[W T] interface { 11 | Draw(DrawArgs, W) 12 | } 13 | 14 | type DrawArgs struct { 15 | Time float32 16 | Delta float32 17 | Commands command.Recorder 18 | Textures cache.SamplerCache 19 | Viewport draw.Viewport 20 | Position vec3.T 21 | } 22 | -------------------------------------------------------------------------------- /render/shader/ref.go: -------------------------------------------------------------------------------- 1 | package shader 2 | 3 | import ( 4 | "github.com/johanhenriksson/goworld/assets/fs" 5 | "github.com/johanhenriksson/goworld/render/device" 6 | ) 7 | 8 | type ref struct { 9 | name string 10 | } 11 | 12 | func Ref(name string) *ref { 13 | return &ref{name: name} 14 | } 15 | 16 | func (r *ref) Key() string { 17 | return r.name 18 | } 19 | 20 | func (r *ref) Version() int { 21 | return 1 22 | } 23 | 24 | func (r *ref) LoadShader(assets fs.Filesystem, dev *device.Device) *Shader { 25 | return New(dev, assets, r.name) 26 | } 27 | -------------------------------------------------------------------------------- /physics/raycast.go: -------------------------------------------------------------------------------- 1 | package physics 2 | 3 | import ( 4 | "github.com/johanhenriksson/goworld/math/vec3" 5 | ) 6 | 7 | type RaycastHit struct { 8 | Shape Shape 9 | Point vec3.T 10 | Normal vec3.T 11 | } 12 | 13 | func (w *World) Raycast(from, to vec3.T, mask Mask) (hit RaycastHit, exists bool) { 14 | result, didHit := world_raycast(w.handle, from, to, mask) 15 | if didHit { 16 | exists = true 17 | hit = RaycastHit{ 18 | Shape: restoreShape(result.shape), 19 | Point: result.point, 20 | Normal: result.normal, 21 | } 22 | } 23 | return 24 | } 25 | -------------------------------------------------------------------------------- /render/texture/const.go: -------------------------------------------------------------------------------- 1 | package texture 2 | 3 | import "github.com/vkngwrapper/core/v2/core1_0" 4 | 5 | type Filter core1_0.Filter 6 | 7 | const FilterNearest = Filter(core1_0.FilterNearest) 8 | const FilterLinear = Filter(core1_0.FilterLinear) 9 | 10 | type Wrap core1_0.SamplerAddressMode 11 | 12 | const WrapClamp = Wrap(core1_0.SamplerAddressModeClampToEdge) 13 | const WrapRepeat = Wrap(core1_0.SamplerAddressModeRepeat) 14 | const WrapMirror = Wrap(core1_0.SamplerAddressModeMirroredRepeat) 15 | const WrapBorder = Wrap(core1_0.SamplerAddressModeClampToBorder) 16 | -------------------------------------------------------------------------------- /render/vertex/quad.go: -------------------------------------------------------------------------------- 1 | package vertex 2 | 3 | import ( 4 | "github.com/johanhenriksson/goworld/math/vec2" 5 | "github.com/johanhenriksson/goworld/math/vec3" 6 | ) 7 | 8 | // Full-screen quad helper 9 | func ScreenQuad(key string) Mesh { 10 | return NewTriangles(key, []Vertex{ 11 | T(vec3.New(-1, -1, 0), vec3.Zero, vec2.New(0, 0)), 12 | T(vec3.New(1, 1, 0), vec3.Zero, vec2.New(1, 1)), 13 | T(vec3.New(-1, 1, 0), vec3.Zero, vec2.New(0, 1)), 14 | T(vec3.New(1, -1, 0), vec3.Zero, vec2.New(1, 0)), 15 | }, []uint32{ 16 | 0, 1, 2, 17 | 0, 3, 1, 18 | }) 19 | } 20 | -------------------------------------------------------------------------------- /core/input/keys/action.go: -------------------------------------------------------------------------------- 1 | package keys 2 | 3 | import ( 4 | "github.com/go-gl/glfw/v3.3/glfw" 5 | ) 6 | 7 | type Action glfw.Action 8 | 9 | const ( 10 | Press Action = Action(glfw.Press) 11 | Release = Action(glfw.Release) 12 | Repeat = Action(glfw.Repeat) 13 | Char = Action(3) 14 | ) 15 | 16 | func (a Action) String() string { 17 | switch a { 18 | case Press: 19 | return "Press" 20 | case Release: 21 | return "Release" 22 | case Repeat: 23 | return "Repeat" 24 | case Char: 25 | return "Character" 26 | } 27 | return "Invalid" 28 | } 29 | -------------------------------------------------------------------------------- /engine/app/args.go: -------------------------------------------------------------------------------- 1 | package app 2 | 3 | import ( 4 | "github.com/johanhenriksson/goworld/engine" 5 | "github.com/johanhenriksson/goworld/engine/graph" 6 | ) 7 | 8 | type Args struct { 9 | Title string 10 | Width int 11 | Height int 12 | Renderer engine.RendererFunc 13 | } 14 | 15 | func (a *Args) Defaults() *Args { 16 | if a.Title == "" { 17 | a.Title = "goworld" 18 | } 19 | if a.Width == 0 { 20 | a.Width = 800 21 | } 22 | if a.Height == 0 { 23 | a.Height = 600 24 | } 25 | if a.Renderer == nil { 26 | a.Renderer = graph.Default 27 | } 28 | return a 29 | } 30 | -------------------------------------------------------------------------------- /gui/style/border.go: -------------------------------------------------------------------------------- 1 | package style 2 | 3 | import ( 4 | "github.com/johanhenriksson/goworld/render/color" 5 | "github.com/kjk/flex" 6 | ) 7 | 8 | type Border struct { 9 | Width Px 10 | Color ColorProp 11 | } 12 | 13 | func (b Border) ApplyBorder(w BorderWidget) { 14 | w.Flex().StyleSetBorder(flex.EdgeAll, float32(b.Width)) 15 | c := b.Color.Vec4() 16 | w.SetBorderColor(color.RGBA(c.X, c.Y, c.Z, c.W)) 17 | } 18 | 19 | type BorderProp interface { 20 | ApplyBorder(BorderWidget) 21 | } 22 | 23 | type BorderWidget interface { 24 | FlexWidget 25 | SetBorderColor(color.T) 26 | } 27 | -------------------------------------------------------------------------------- /editor/editor.go: -------------------------------------------------------------------------------- 1 | package editor 2 | 3 | import ( 4 | "github.com/johanhenriksson/goworld/core/input/mouse" 5 | "github.com/johanhenriksson/goworld/core/object" 6 | ) 7 | 8 | type T interface { 9 | object.Object 10 | Target() object.Component 11 | 12 | Select(ev mouse.Event) 13 | Deselect(ev mouse.Event) bool 14 | 15 | Actions() []Action 16 | } 17 | 18 | // EditorUpdater is an optional interface that can be implemented by 19 | // components to receive editor updates. 20 | type EditorUpdater interface { 21 | object.Component 22 | EditorUpdate(scene object.Component, dt float32) 23 | } 24 | -------------------------------------------------------------------------------- /engine/pool.go: -------------------------------------------------------------------------------- 1 | package engine 2 | 3 | import "github.com/vkngwrapper/core/v2/core1_0" 4 | 5 | var DefaultDescriptorPools = []core1_0.DescriptorPoolSize{ 6 | { 7 | Type: core1_0.DescriptorTypeUniformBuffer, 8 | DescriptorCount: 10000, 9 | }, 10 | { 11 | Type: core1_0.DescriptorTypeStorageBuffer, 12 | DescriptorCount: 10000, 13 | }, 14 | { 15 | Type: core1_0.DescriptorTypeCombinedImageSampler, 16 | DescriptorCount: 100000, 17 | }, 18 | { 19 | Type: core1_0.DescriptorTypeInputAttachment, 20 | DescriptorCount: 1000, 21 | }, 22 | } 23 | -------------------------------------------------------------------------------- /core/input/mouse/action.go: -------------------------------------------------------------------------------- 1 | package mouse 2 | 3 | import "github.com/go-gl/glfw/v3.3/glfw" 4 | 5 | type Action int 6 | 7 | const ( 8 | Press Action = Action(glfw.Press) 9 | Release = Action(glfw.Release) 10 | Move = Action(4) 11 | Scroll = Action(5) 12 | Enter = Action(6) 13 | Leave = Action(7) 14 | ) 15 | 16 | func (a Action) String() string { 17 | switch a { 18 | case Press: 19 | return "Press" 20 | case Release: 21 | return "Release" 22 | case Move: 23 | return "Move" 24 | case Scroll: 25 | return "Scroll" 26 | } 27 | return "Invalid" 28 | } 29 | -------------------------------------------------------------------------------- /render/instance/ext_darwin.go: -------------------------------------------------------------------------------- 1 | package instance 2 | 3 | import ( 4 | "github.com/vkngwrapper/extensions/v2/ext_debug_utils" 5 | "github.com/vkngwrapper/extensions/v2/khr_get_physical_device_properties2" 6 | "github.com/vkngwrapper/extensions/v2/khr_portability_enumeration" 7 | "github.com/vkngwrapper/extensions/v2/khr_surface" 8 | ) 9 | 10 | var extensions = []string{ 11 | khr_surface.ExtensionName, 12 | ext_debug_utils.ExtensionName, 13 | khr_get_physical_device_properties2.ExtensionName, 14 | khr_portability_enumeration.ExtensionName, 15 | 16 | "VK_EXT_debug_report", 17 | "VK_EXT_metal_surface", 18 | } 19 | -------------------------------------------------------------------------------- /assets/builtin/shaders/pass/lines.vs.glsl: -------------------------------------------------------------------------------- 1 | #version 450 2 | 3 | #include "lib/common.glsl" 4 | #include "lib/objects.glsl" 5 | 6 | OUT(0, vec3, color) 7 | 8 | out gl_PerVertex 9 | { 10 | vec4 gl_Position; 11 | }; 12 | 13 | CAMERA(0, camera) 14 | OBJECT(1, object, gl_InstanceIndex) 15 | 16 | VERTEX_BUFFER(Vertex) 17 | INDEX_BUFFER(uint) 18 | 19 | void main() 20 | { 21 | // load vertex data 22 | Vertex v = get_vertex_indexed(object.vertexPtr, object.indexPtr); 23 | 24 | out_color = v.color.rgb; 25 | 26 | mat4 mvp = camera.ViewProj * object.model; 27 | gl_Position = mvp * vec4(v.position, 1); 28 | } 29 | -------------------------------------------------------------------------------- /math/random/random.go: -------------------------------------------------------------------------------- 1 | package random 2 | 3 | import ( 4 | "math/rand" 5 | "time" 6 | ) 7 | 8 | func init() { 9 | seed := time.Now().Nanosecond() 10 | Seed(seed) 11 | } 12 | 13 | func Seed(seed int) { 14 | rand.Seed(int64(seed)) 15 | } 16 | 17 | func Range(min, max float32) float32 { 18 | return min + rand.Float32()*(max-min) 19 | } 20 | 21 | func Int(min, max int) int { 22 | return rand.Intn(max-min) + min 23 | } 24 | 25 | func Chance(chance float32) bool { 26 | return Range(0, 1) <= chance 27 | } 28 | 29 | func Choice[T any](slice []T) T { 30 | idx := rand.Intn(len(slice)) 31 | return slice[idx] 32 | } 33 | -------------------------------------------------------------------------------- /geometry/sprite/material.go: -------------------------------------------------------------------------------- 1 | package sprite 2 | 3 | import ( 4 | "github.com/johanhenriksson/goworld/render/material" 5 | "github.com/johanhenriksson/goworld/render/vertex" 6 | 7 | "github.com/vkngwrapper/core/v2/core1_0" 8 | ) 9 | 10 | func Material() *material.Def { 11 | return &material.Def{ 12 | Pass: material.Forward, 13 | Shader: "forward/sprite", 14 | VertexFormat: vertex.Vertex{}, 15 | DepthTest: true, 16 | DepthWrite: true, 17 | DepthFunc: core1_0.CompareOpLessOrEqual, 18 | Primitive: vertex.Triangles, 19 | CullMode: vertex.CullNone, 20 | Transparent: true, 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /util/sync_map.go: -------------------------------------------------------------------------------- 1 | package util 2 | 3 | import ( 4 | "sync" 5 | ) 6 | 7 | // Type-safe sync.Map implementation 8 | // Read sync.Map documentation for caveats 9 | type SyncMap[K comparable, V any] struct { 10 | m sync.Map 11 | } 12 | 13 | func NewSyncMap[K comparable, V any]() *SyncMap[K, V] { 14 | return &SyncMap[K, V]{ 15 | m: sync.Map{}, 16 | } 17 | } 18 | 19 | func (m *SyncMap[K, V]) Load(key K) (value V, exists bool) { 20 | var v any 21 | v, exists = m.m.Load(key) 22 | if exists { 23 | value = v.(V) 24 | } 25 | return 26 | } 27 | 28 | func (m *SyncMap[K, V]) Store(key K, value V) { 29 | m.m.Store(key, value) 30 | } 31 | -------------------------------------------------------------------------------- /render/shader/stage.go: -------------------------------------------------------------------------------- 1 | package shader 2 | 3 | import "github.com/vkngwrapper/core/v2/core1_0" 4 | 5 | type ShaderStage core1_0.ShaderStageFlags 6 | 7 | const ( 8 | StageAll = ShaderStage(core1_0.StageAll) 9 | StageVertex = ShaderStage(core1_0.StageVertex) 10 | StageFragment = ShaderStage(core1_0.StageFragment) 11 | StageCompute = ShaderStage(core1_0.StageCompute) 12 | ) 13 | 14 | func (s ShaderStage) String() string { 15 | return s.flags().String() 16 | } 17 | 18 | // flags returns the Vulkan-native representation 19 | func (s ShaderStage) flags() core1_0.ShaderStageFlags { 20 | return core1_0.ShaderStageFlags(s) 21 | } 22 | -------------------------------------------------------------------------------- /core/object/scene.go: -------------------------------------------------------------------------------- 1 | package object 2 | 3 | func init() { 4 | Register[*scene](Type{ 5 | Name: "Scene", 6 | Create: func(pool Pool) (Component, error) { 7 | return Scene(pool), nil 8 | }, 9 | }) 10 | } 11 | 12 | type SceneFunc func(Pool, Object) 13 | 14 | type scene struct { 15 | Object 16 | } 17 | 18 | func Scene(pool Pool, funcs ...SceneFunc) Object { 19 | s := &scene{ 20 | Object: Empty(pool, "Scene"), 21 | } 22 | for _, f := range funcs { 23 | f(pool, s) 24 | } 25 | return s 26 | } 27 | 28 | func (s *scene) Active() bool { 29 | return true 30 | } 31 | 32 | func (s *scene) setActive(bool) bool { 33 | return true 34 | } 35 | -------------------------------------------------------------------------------- /core/input/mouse/statemap.go: -------------------------------------------------------------------------------- 1 | package mouse 2 | 3 | type State interface { 4 | Handler 5 | 6 | Down(Button) bool 7 | Up(Button) bool 8 | } 9 | 10 | type state map[Button]bool 11 | 12 | func NewState() State { 13 | return state{} 14 | } 15 | 16 | func (s state) MouseEvent(e Event) { 17 | if e.Action() == Press { 18 | s[e.Button()] = true 19 | } 20 | if e.Action() == Release { 21 | s[e.Button()] = false 22 | } 23 | } 24 | 25 | func (s state) Down(key Button) bool { 26 | if state, stored := s[key]; stored { 27 | return state 28 | } 29 | return false 30 | } 31 | 32 | func (s state) Up(key Button) bool { 33 | return !s.Down(key) 34 | } 35 | -------------------------------------------------------------------------------- /gui/style/color.go: -------------------------------------------------------------------------------- 1 | package style 2 | 3 | // awkward attempt at making styles compatible with the render packages color values 4 | 5 | import ( 6 | icolor "image/color" 7 | 8 | "github.com/johanhenriksson/goworld/math/vec4" 9 | "github.com/johanhenriksson/goworld/render/color" 10 | ) 11 | 12 | type Colorizable interface { 13 | SetColor(color.T) 14 | } 15 | 16 | type ColorProp interface { 17 | RGBA() icolor.RGBA 18 | Vec4() vec4.T 19 | } 20 | 21 | func RGB(r, g, b float32) color.T { 22 | // alias 23 | return color.RGB(r, g, b) 24 | } 25 | 26 | func RGBA(r, g, b, a float32) color.T { 27 | // alias 28 | return color.RGBA(r, g, b, a) 29 | } 30 | -------------------------------------------------------------------------------- /gui/style/none.go: -------------------------------------------------------------------------------- 1 | package style 2 | 3 | import "github.com/kjk/flex" 4 | 5 | // None implements all Prop interfaces but does nothing 6 | // It's a cool idea but is it worth setting it everywhere? 7 | type None struct{} 8 | 9 | func (n None) ApplyBasis(fw FlexWidget) {} 10 | func (n None) ApplyWidth(fw FlexWidget) {} 11 | func (n None) ApplyMaxWidth(fw FlexWidget) {} 12 | func (n None) ApplyHeight(fw FlexWidget) {} 13 | func (n None) ApplyMaxHeight(fw FlexWidget) {} 14 | func (n None) ApplyPadding(fw FlexWidget) {} 15 | func (n None) ApplyMargin(fw FlexWidget) {} 16 | 17 | func (n None) ApplyPosition(fw FlexWidget, edge flex.Edge) {} 18 | -------------------------------------------------------------------------------- /render/buffer/util.go: -------------------------------------------------------------------------------- 1 | package buffer 2 | 3 | import ( 4 | "github.com/johanhenriksson/goworld/render/device" 5 | 6 | "github.com/vkngwrapper/core/v2/core1_0" 7 | ) 8 | 9 | func GetBufferLimits(device *device.Device, usage core1_0.BufferUsageFlags) (align, max int) { 10 | limits := device.GetLimits() 11 | if usage&core1_0.BufferUsageUniformBuffer > 0 { 12 | return int(limits.MinUniformBufferOffsetAlignment), int(limits.MaxUniformBufferRange) 13 | } 14 | if usage&core1_0.BufferUsageStorageBuffer > 0 { 15 | return int(limits.MinStorageBufferOffsetAlignment), int(limits.MaxStorageBufferRange) 16 | } 17 | panic("unknown buffer usage type") 18 | } 19 | -------------------------------------------------------------------------------- /assets/builtin/shaders/pass/shadow.vs.glsl: -------------------------------------------------------------------------------- 1 | #version 450 2 | 3 | #include "lib/common.glsl" 4 | #include "lib/objects.glsl" 5 | 6 | OUT(0, float, depth) 7 | 8 | out gl_PerVertex 9 | { 10 | vec4 gl_Position; 11 | }; 12 | 13 | CAMERA(0, camera) 14 | OBJECT(1, object, gl_InstanceIndex) 15 | 16 | VERTEX_BUFFER(Vertex) 17 | INDEX_BUFFER(uint) 18 | 19 | void main() 20 | { 21 | // load vertex data 22 | Vertex v = get_vertex_indexed(object.vertexPtr, object.indexPtr); 23 | 24 | mat4 mvp = camera.ViewProj * object.model; 25 | gl_Position = mvp * vec4(v.position, 1); 26 | 27 | // store linear depth 28 | out_depth = gl_Position.z / gl_Position.w; 29 | } 30 | -------------------------------------------------------------------------------- /assets/builtin/shaders/pass/blur.fs.glsl: -------------------------------------------------------------------------------- 1 | #version 450 2 | 3 | #include "lib/common.glsl" 4 | 5 | IN(0, vec2, texcoord) 6 | OUT(0, vec4, color) 7 | SAMPLER(0, input) 8 | 9 | void main() 10 | { 11 | vec2 texelSize = 1.0 / vec2(textureSize(tex_input, 0)); 12 | float result = 0.0; 13 | for (int x = -2; x < 2; ++x) 14 | { 15 | for (int y = -2; y < 2; ++y) 16 | { 17 | vec2 offset = vec2(float(x), float(y)) * texelSize; 18 | result += texture(tex_input, in_texcoord + offset).r; 19 | } 20 | } 21 | result = result / (4.0 * 4.0); 22 | out_color = vec4(result, result, result, 1); 23 | } 24 | -------------------------------------------------------------------------------- /engine/window/window.go: -------------------------------------------------------------------------------- 1 | package window 2 | 3 | import ( 4 | "github.com/johanhenriksson/goworld/core/input" 5 | "github.com/johanhenriksson/goworld/engine" 6 | ) 7 | 8 | type ResizeHandler func(width, height int) 9 | 10 | type Window interface { 11 | engine.Target 12 | 13 | Title() string 14 | SetTitle(string) 15 | 16 | Poll() 17 | ShouldClose() bool 18 | Destroy() 19 | 20 | SetInputHandler(input.Handler) 21 | } 22 | 23 | type WindowArgs struct { 24 | Title string 25 | Width int 26 | Height int 27 | Frames int 28 | Vsync bool 29 | Debug bool 30 | InputHandler input.Handler 31 | ResizeHandler ResizeHandler 32 | } 33 | -------------------------------------------------------------------------------- /render/image/view.go: -------------------------------------------------------------------------------- 1 | package image 2 | 3 | import ( 4 | "github.com/johanhenriksson/goworld/render/device" 5 | 6 | "github.com/vkngwrapper/core/v2/core1_0" 7 | ) 8 | 9 | type ViewArray []*View 10 | 11 | type View struct { 12 | ptr core1_0.ImageView 13 | image *Image 14 | format core1_0.Format 15 | device *device.Device 16 | } 17 | 18 | func (v *View) Ptr() core1_0.ImageView { return v.ptr } 19 | func (v *View) Image() *Image { return v.image } 20 | func (v *View) Format() core1_0.Format { return v.format } 21 | 22 | func (v *View) Destroy() { 23 | if v.ptr != nil { 24 | v.ptr.Destroy(nil) 25 | v.ptr = nil 26 | } 27 | v.device = nil 28 | v.image = nil 29 | } 30 | -------------------------------------------------------------------------------- /assets/builtin/shaders/lib/ui.glsl: -------------------------------------------------------------------------------- 1 | struct Quad { 2 | vec2 min; // top left 3 | vec2 max; // bottom right 4 | vec2 uv_min; // top left uv 5 | vec2 uv_max; // bottom right uv 6 | vec4 color[4]; 7 | float zindex; 8 | float corner_radius; 9 | float edge_softness; 10 | float border; 11 | uint texture; 12 | uint _padding[3]; 13 | }; 14 | 15 | UNIFORM(0, config, { 16 | vec2 resolution; 17 | float zmax; 18 | }) 19 | STORAGE_BUFFER(1, Quad, quads) 20 | 21 | float RoundedRectSDF(vec2 sample_pos, vec2 rect_center, vec2 rect_half_size, float r) { 22 | vec2 d2 = (abs(rect_center - sample_pos) - rect_half_size + vec2(r, r)); 23 | return min(max(d2.x, d2.y), 0.0) + length(max(d2, 0.0)) - r; 24 | } 25 | -------------------------------------------------------------------------------- /render/vkerror/errors.go: -------------------------------------------------------------------------------- 1 | package vkerror 2 | 3 | import ( 4 | "errors" 5 | "fmt" 6 | 7 | "github.com/vkngwrapper/core/v2/common" 8 | "github.com/vkngwrapper/core/v2/core1_0" 9 | ) 10 | 11 | var ErrOutOfHostMemory = errors.New("out of host memory") 12 | var ErrOutOfDeviceMemory = errors.New("out of device memory") 13 | 14 | func FromResult(result common.VkResult) error { 15 | switch result { 16 | case core1_0.VKSuccess: 17 | return nil 18 | 19 | case core1_0.VKErrorOutOfHostMemory: 20 | return ErrOutOfHostMemory 21 | 22 | case core1_0.VKErrorOutOfDeviceMemory: 23 | return ErrOutOfDeviceMemory 24 | 25 | default: 26 | return fmt.Errorf("unmapped Vulkan error: %d", result) 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /render/command/recorder.go: -------------------------------------------------------------------------------- 1 | package command 2 | 3 | type Recorder interface { 4 | Record(CommandFn) 5 | Apply(*Buffer) 6 | } 7 | 8 | type recorder struct { 9 | parts []CommandFn 10 | } 11 | 12 | func NewRecorder() Recorder { 13 | return &recorder{ 14 | parts: make([]CommandFn, 0, 64), 15 | } 16 | } 17 | 18 | func (r recorder) Apply(cmd *Buffer) { 19 | for _, part := range r.parts { 20 | part(cmd) 21 | } 22 | } 23 | 24 | func (r *recorder) Record(cmd CommandFn) { 25 | r.parts = append(r.parts, cmd) 26 | } 27 | 28 | type empty struct{} 29 | 30 | var Empty Recorder = empty{} 31 | 32 | func (empty) Apply(*Buffer) {} 33 | func (empty) Record(CommandFn) { 34 | panic("empty command should not be recorded") 35 | } 36 | -------------------------------------------------------------------------------- /render/device/queue.go: -------------------------------------------------------------------------------- 1 | package device 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/vkngwrapper/core/v2/core1_0" 7 | ) 8 | 9 | type Queue struct { 10 | ptr core1_0.Queue 11 | flags core1_0.QueueFlags 12 | family int 13 | index int 14 | } 15 | 16 | func (q Queue) Ptr() core1_0.Queue { 17 | return q.ptr 18 | } 19 | 20 | func (q Queue) FamilyIndex() int { 21 | return q.family 22 | } 23 | 24 | func (q Queue) Index() int { 25 | return q.index 26 | } 27 | 28 | func (q Queue) Matches(flags core1_0.QueueFlags) bool { 29 | return q.flags&flags == flags 30 | } 31 | 32 | func (q Queue) String() string { 33 | return fmt.Sprintf("Queue{ptr: %v, flags: %v, family: %d, index: %d}", q.ptr, q.flags, q.family, q.index) 34 | } 35 | -------------------------------------------------------------------------------- /render/vertex/triangle.go: -------------------------------------------------------------------------------- 1 | package vertex 2 | 3 | import "github.com/johanhenriksson/goworld/math/vec3" 4 | 5 | type Triangle struct { 6 | A, B, C vec3.T 7 | } 8 | 9 | func (t *Triangle) Normal() vec3.T { 10 | // Set Vector U to (Triangle.p2 minus Triangle.p1) 11 | u := t.B.Sub(t.A) 12 | // Set Vector V to (Triangle.p3 minus Triangle.p1) 13 | v := t.C.Sub(t.A) 14 | 15 | // Set Normal.x to (multiply U.y by V.z) minus (multiply U.z by V.y) 16 | x := u.Y*v.Z - u.Z*v.Y 17 | // Set Normal.y to (multiply U.z by V.x) minus (multiply U.x by V.z) 18 | y := u.Z*v.X - u.X*v.Z 19 | // Set Normal.z to (multiply U.x by V.y) minus (multiply U.y by V.x) 20 | z := u.X*v.Y - u.Y*v.X 21 | 22 | return vec3.New(x, y, z).Normalized() 23 | } 24 | -------------------------------------------------------------------------------- /render/vertex/mesh_generated.go: -------------------------------------------------------------------------------- 1 | package vertex 2 | 3 | type Args interface{} 4 | 5 | type GeneratedMesh[A Args, V VertexFormat, I IndexFormat] interface { 6 | Mesh 7 | Update(A) 8 | } 9 | 10 | type generated[A Args, V VertexFormat, I IndexFormat] struct { 11 | Mesh 12 | key string 13 | version int 14 | hash int 15 | generator func(A) (V, I) 16 | } 17 | 18 | func NewGenerated[A Args, V VertexFormat, I IndexFormat](key string, args A, generator func(A) (V, I)) GeneratedMesh[A, V, I] { 19 | return &generated[A, V, I]{ 20 | key: key, 21 | version: 1, 22 | generator: generator, 23 | } 24 | } 25 | 26 | func (g *generated[A, V, I]) Update(args A) { 27 | // if args hash has changed, update version 28 | } 29 | -------------------------------------------------------------------------------- /core/input/keys/util.go: -------------------------------------------------------------------------------- 1 | package keys 2 | 3 | func Pressed(ev Event, code Code) bool { 4 | if ev.Action() != Press { 5 | return false 6 | } 7 | if ev.Code() != code { 8 | return false 9 | } 10 | return true 11 | } 12 | 13 | func PressedMods(ev Event, code Code, mods Modifier) bool { 14 | if !Pressed(ev, code) { 15 | return false 16 | } 17 | return ev.Modifier(mods) 18 | } 19 | 20 | func Released(ev Event, code Code) bool { 21 | if ev.Action() != Release { 22 | return false 23 | } 24 | if ev.Code() != code { 25 | return false 26 | } 27 | return true 28 | } 29 | 30 | func ReleasedMods(ev Event, code Code, mods Modifier) bool { 31 | if !Released(ev, code) { 32 | return false 33 | } 34 | return ev.Modifier(mods) 35 | } 36 | -------------------------------------------------------------------------------- /gui/hooks/state.go: -------------------------------------------------------------------------------- 1 | package hooks 2 | 3 | type State struct { 4 | data []any 5 | next int 6 | } 7 | 8 | func (s *State) Next() int { 9 | id := s.next 10 | s.next++ 11 | return id 12 | } 13 | 14 | // Write hook state. For debug/testing purposes only. 15 | func (s *State) Write(index int, data any) { 16 | if len(s.data) < index+1 { 17 | s.data = append(s.data, make([]any, index-len(s.data)+1)...) 18 | } 19 | s.data[index] = data 20 | } 21 | 22 | func Enable(new *State) { 23 | active = new 24 | active.next = 0 25 | } 26 | 27 | func Disable() { 28 | active = nil 29 | } 30 | 31 | var active *State = nil 32 | 33 | func getState() *State { 34 | if active == nil { 35 | panic("no active hook state") 36 | } 37 | return active 38 | } 39 | -------------------------------------------------------------------------------- /editor/propedit/registry.go: -------------------------------------------------------------------------------- 1 | package propedit 2 | 3 | import ( 4 | "reflect" 5 | 6 | "github.com/johanhenriksson/goworld/core/object" 7 | "github.com/johanhenriksson/goworld/gui/node" 8 | ) 9 | 10 | type PropEditor func(key, name string, prop object.GenericProp) node.T 11 | 12 | var registry map[reflect.Type]PropEditor = make(map[reflect.Type]PropEditor, 100) 13 | 14 | func For[T object.PropValue](prop object.Property[T]) PropEditor { 15 | var empty T 16 | t := reflect.TypeOf(empty) 17 | return ForType(t) 18 | } 19 | 20 | func ForType(t reflect.Type) PropEditor { 21 | return registry[t] 22 | } 23 | 24 | func Register[T object.PropValue](editor PropEditor) { 25 | var empty T 26 | t := reflect.TypeOf(empty) 27 | registry[t] = editor 28 | } 29 | -------------------------------------------------------------------------------- /render/swapchain/context.go: -------------------------------------------------------------------------------- 1 | package swapchain 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/johanhenriksson/goworld/render/device" 7 | "github.com/johanhenriksson/goworld/render/sync" 8 | ) 9 | 10 | type Context struct { 11 | Index int 12 | ImageAvailable *sync.Semaphore 13 | RenderComplete *sync.Semaphore 14 | } 15 | 16 | func NewContext(dev *device.Device, index int) *Context { 17 | return &Context{ 18 | Index: index, 19 | ImageAvailable: sync.NewSemaphore(dev, fmt.Sprintf("ImageAvailable:%d", index)), 20 | RenderComplete: sync.NewSemaphore(dev, fmt.Sprintf("RenderComplete:%d", index)), 21 | } 22 | } 23 | 24 | func (c *Context) Destroy() { 25 | c.ImageAvailable.Destroy() 26 | c.RenderComplete.Destroy() 27 | } 28 | -------------------------------------------------------------------------------- /render/framebuffer/array.go: -------------------------------------------------------------------------------- 1 | package framebuffer 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/johanhenriksson/goworld/render/device" 7 | "github.com/johanhenriksson/goworld/render/renderpass" 8 | ) 9 | 10 | type Array []*Framebuffer 11 | 12 | func NewArray(count int, device *device.Device, name string, width, height int, pass *renderpass.Renderpass) (Array, error) { 13 | var err error 14 | array := make(Array, count) 15 | for i := range array { 16 | array[i], err = New(device, fmt.Sprintf("%s[%d]", name, i), width, height, pass) 17 | if err != nil { 18 | return nil, err 19 | } 20 | } 21 | return array, nil 22 | } 23 | 24 | func (a Array) Destroy() { 25 | for i, fbuf := range a { 26 | fbuf.Destroy() 27 | a[i] = nil 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /engine/uniform/object_buffer.go: -------------------------------------------------------------------------------- 1 | package uniform 2 | 3 | import ( 4 | "github.com/johanhenriksson/goworld/render/descriptor" 5 | ) 6 | 7 | type ObjectBuffer struct { 8 | buffer []Object 9 | } 10 | 11 | func NewObjectBuffer(capacity int) *ObjectBuffer { 12 | return &ObjectBuffer{ 13 | buffer: make([]Object, 0, capacity), 14 | } 15 | } 16 | 17 | func (b *ObjectBuffer) Size() int { 18 | return cap(b.buffer) 19 | } 20 | 21 | func (b *ObjectBuffer) Flush(desc *descriptor.Storage[Object]) { 22 | desc.SetRange(0, b.buffer) 23 | } 24 | 25 | func (b *ObjectBuffer) Reset() { 26 | b.buffer = b.buffer[:0] 27 | } 28 | 29 | func (b *ObjectBuffer) Store(obj Object) int { 30 | index := len(b.buffer) 31 | b.buffer = append(b.buffer, obj) 32 | return index 33 | } 34 | -------------------------------------------------------------------------------- /gui/node/pool.go: -------------------------------------------------------------------------------- 1 | package node 2 | 3 | import ( 4 | "reflect" 5 | ) 6 | 7 | type Pool struct { 8 | free map[reflect.Type][]any 9 | } 10 | 11 | var globalPool = &Pool{ 12 | free: make(map[reflect.Type][]any), 13 | } 14 | 15 | func Alloc[P any](pool *Pool, kind reflect.Type) *node[P] { 16 | if freelist, exists := pool.free[kind]; exists && len(freelist) > 0 { 17 | idx := len(freelist) - 1 18 | n := freelist[idx] 19 | pool.free[kind] = freelist[:idx] 20 | return n.(*node[P]) 21 | } 22 | return &node[P]{ 23 | kind: kind, 24 | } 25 | } 26 | 27 | func Free[P any](pool *Pool, n *node[P]) { 28 | freelist, exists := pool.free[n.kind] 29 | if !exists { 30 | freelist = make([]any, 0, 128) 31 | } 32 | pool.free[n.kind] = append(freelist, n) 33 | } 34 | -------------------------------------------------------------------------------- /assets/builtin/shaders/deferred/deferred.fs.glsl: -------------------------------------------------------------------------------- 1 | #version 450 2 | 3 | #include "lib/common.glsl" 4 | #include "lib/objects.glsl" 5 | 6 | // Varying 7 | IN(0, flat uint, object) 8 | IN(1, vec3, position) 9 | IN(2, vec3, normal) 10 | IN(3, vec4, color) 11 | IN(4, vec2, texcoord) 12 | 13 | // Return Output 14 | OUT(0, vec4, diffuse) 15 | OUT(1, vec4, normal) 16 | OUT(2, vec4, position) 17 | 18 | OBJECT(1, object, in_object) 19 | SAMPLER_ARRAY(2, textures) 20 | 21 | void main() 22 | { 23 | uint texture0 = object.textures[TEX_SLOT_DIFFUSE]; 24 | 25 | vec3 tint = mix(vec3(1), in_color.rgb, in_color.a); 26 | out_diffuse = vec4(texture_array(textures, texture0, in_texcoord).rgb * tint, 1); 27 | out_normal = pack_normal(in_normal); 28 | out_position = vec4(in_position, 1); 29 | } 30 | -------------------------------------------------------------------------------- /core/input/mouse/utils.go: -------------------------------------------------------------------------------- 1 | package mouse 2 | 3 | import "github.com/go-gl/glfw/v3.3/glfw" 4 | 5 | var locked bool = false 6 | var lockingEnabled bool = false 7 | 8 | func Lock() { 9 | // actual cursor locking can be awkward, so leave an option to enable it 10 | // otherwise the cursor will be locked virtually - i.e. only in the sense that 11 | // mouse events have the Locked flag set to true 12 | if lockingEnabled { 13 | glfw.GetCurrentContext().SetInputMode(glfw.CursorMode, glfw.CursorDisabled) 14 | } 15 | locked = true 16 | } 17 | 18 | func Hide() { 19 | // glfw.GetCurrentContext().SetInputMode(glfw.CursorMode, glfw.CursorHidden) 20 | } 21 | 22 | func Show() { 23 | // glfw.GetCurrentContext().SetInputMode(glfw.CursorMode, glfw.CursorNormal) 24 | locked = false 25 | } 26 | -------------------------------------------------------------------------------- /assets/builtin/shaders/pass/depth.vs.glsl: -------------------------------------------------------------------------------- 1 | #version 450 2 | 3 | #include "lib/common.glsl" 4 | #include "lib/objects.glsl" 5 | 6 | OUT(0, vec3, position) 7 | 8 | out gl_PerVertex 9 | { 10 | vec4 gl_Position; 11 | }; 12 | 13 | CAMERA(0, camera) 14 | OBJECT(1, object, gl_InstanceIndex) 15 | 16 | VERTEX_BUFFER(Vertex) 17 | INDEX_BUFFER(uint) 18 | 19 | void main() 20 | { 21 | // load vertex data 22 | Vertex v = get_vertex_indexed(object.vertexPtr, object.indexPtr); 23 | 24 | mat4 mv = camera.View * object.model; 25 | 26 | // gbuffer view position 27 | // todo: can this be removed? probably just a waste of bandwidth 28 | out_position = (mv * vec4(v.position, 1.0)).xyz; 29 | 30 | // vertex clip space position 31 | gl_Position = camera.Proj * vec4(out_position, 1); 32 | } 33 | -------------------------------------------------------------------------------- /gui/style/position.go: -------------------------------------------------------------------------------- 1 | package style 2 | 3 | import "github.com/kjk/flex" 4 | 5 | type Absolute struct { 6 | Left, Right PositionValueProp 7 | Top, Bottom PositionValueProp 8 | } 9 | 10 | func (a Absolute) ApplyPosition(fw FlexWidget) { 11 | fw.Flex().StyleSetPositionType(flex.PositionTypeAbsolute) 12 | 13 | if a.Left != nil { 14 | a.Left.ApplyPosition(fw, flex.EdgeLeft) 15 | } 16 | if a.Right != nil { 17 | a.Right.ApplyPosition(fw, flex.EdgeRight) 18 | } 19 | if a.Top != nil { 20 | a.Top.ApplyPosition(fw, flex.EdgeTop) 21 | } 22 | if a.Bottom != nil { 23 | a.Bottom.ApplyPosition(fw, flex.EdgeBottom) 24 | } 25 | } 26 | 27 | type Relative struct{} 28 | 29 | func (r Relative) ApplyPosition(fw FlexWidget) { 30 | fw.Flex().StyleSetPositionType(flex.PositionTypeAbsolute) 31 | } 32 | -------------------------------------------------------------------------------- /gui/widget/widget.go: -------------------------------------------------------------------------------- 1 | package widget 2 | 3 | import ( 4 | "github.com/johanhenriksson/goworld/math/vec2" 5 | 6 | "github.com/kjk/flex" 7 | ) 8 | 9 | type T interface { 10 | Key() string 11 | 12 | // Properties returns a pointer to the components property struct. 13 | // The pointer is used to compare the states when deciding if the component needs to be updated. 14 | Props() any 15 | 16 | // Update replaces the components property struct. 17 | Update(any) 18 | 19 | // Size returns the actual size of the element in pixels 20 | Size() vec2.T 21 | 22 | // Position returns the current position of the element relative to its parent 23 | Position() vec2.T 24 | 25 | Children() []T 26 | SetChildren([]T) 27 | Destroy() 28 | 29 | Flex() *flex.Node 30 | 31 | Draw(DrawArgs, *QuadBuffer) 32 | } 33 | -------------------------------------------------------------------------------- /editor/builtin/physics_world.go: -------------------------------------------------------------------------------- 1 | package builtin 2 | 3 | import ( 4 | "github.com/johanhenriksson/goworld/core/object" 5 | "github.com/johanhenriksson/goworld/editor" 6 | "github.com/johanhenriksson/goworld/physics" 7 | ) 8 | 9 | func init() { 10 | editor.RegisterEditor(&physics.World{}, NewWorldEditor) 11 | } 12 | 13 | type WorldEditor struct { 14 | *editor.ComponentEditor 15 | target *physics.World 16 | } 17 | 18 | func NewWorldEditor(ctx *editor.Context, world *physics.World) *WorldEditor { 19 | world.Debug(false) 20 | return object.NewComponent(ctx.Objects, &WorldEditor{ 21 | ComponentEditor: editor.NewComponentEditor(ctx.Objects, world), 22 | target: world, 23 | }) 24 | } 25 | 26 | func (e *WorldEditor) Update(scene object.Component, dt float32) { 27 | e.target.DebugDraw() 28 | } 29 | -------------------------------------------------------------------------------- /math/quat/quat_suite_test.go: -------------------------------------------------------------------------------- 1 | package quat_test 2 | 3 | import ( 4 | . "github.com/johanhenriksson/goworld/test/util" 5 | . "github.com/onsi/ginkgo/v2" 6 | . "github.com/onsi/gomega" 7 | 8 | "testing" 9 | 10 | "github.com/johanhenriksson/goworld/math/quat" 11 | "github.com/johanhenriksson/goworld/math/vec3" 12 | ) 13 | 14 | func TestQuat(t *testing.T) { 15 | RegisterFailHandler(Fail) 16 | RunSpecs(t, "math/quat") 17 | } 18 | 19 | var _ = Describe("quaternion", func() { 20 | Context("euler angles", func() { 21 | It("converts back and forth", func() { 22 | x, y, z := float32(10), float32(20), float32(30) 23 | q := quat.Euler(x, y, z) 24 | r := q.Euler() 25 | GinkgoWriter.Println(x, y, z, r) 26 | Expect(r).To(ApproxVec3(vec3.New(x, y, z)), "wrong rotation") 27 | }) 28 | }) 29 | }) 30 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | goworld - Experimental Golang OpenGL Engine 2 | Copyright (C) 2016 Johan Henriksson 3 | 4 | This program is free software; you can redistribute it and/or 5 | modify it under the terms of the GNU General Public License 6 | as published by the Free Software Foundation; either version 2 7 | of the License, or (at your option) any later version. 8 | 9 | This program is distributed in the hope that it will be useful, 10 | but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | GNU General Public License for more details. 13 | 14 | You should have received a copy of the GNU General Public License 15 | along with this program; if not, write to the Free Software 16 | Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. 17 | -------------------------------------------------------------------------------- /assets/builtin_fs.go: -------------------------------------------------------------------------------- 1 | package assets 2 | 3 | import ( 4 | "embed" 5 | "errors" 6 | "fmt" 7 | "io" 8 | "os" 9 | 10 | "github.com/johanhenriksson/goworld/assets/fs" 11 | ) 12 | 13 | //go:embed builtin/* 14 | var builtinFs embed.FS 15 | 16 | var BuiltinFilesystem fs.Filesystem = &builtinFilesystem{} 17 | 18 | type builtinFilesystem struct{} 19 | 20 | func (bfs *builtinFilesystem) Read(key string) ([]byte, error) { 21 | file, err := builtinFs.Open("builtin/" + key) 22 | if err != nil { 23 | if errors.Is(err, os.ErrNotExist) { 24 | return nil, fmt.Errorf("builtin asset %s %w", key, fs.ErrNotFound) 25 | } 26 | } 27 | return io.ReadAll(file) 28 | } 29 | 30 | func (_ *builtinFilesystem) Write(key string, data []byte) error { 31 | return fmt.Errorf("%w: cant write to builtin file system", fs.ErrImmutable) 32 | } 33 | -------------------------------------------------------------------------------- /math/spline/linear.go: -------------------------------------------------------------------------------- 1 | package spline 2 | 3 | import ( 4 | "encoding/gob" 5 | 6 | "github.com/johanhenriksson/goworld/math/vec2" 7 | ) 8 | 9 | type Linear struct { 10 | Points []vec2.T 11 | } 12 | 13 | func init() { 14 | gob.Register(Linear{}) 15 | } 16 | 17 | func NewLinear(points ...vec2.T) Linear { 18 | return Linear{ 19 | Points: points, 20 | } 21 | } 22 | 23 | func (l Linear) Eval(t float32) float32 { 24 | if len(l.Points) == 0 { 25 | return 0 26 | } 27 | if len(l.Points) == 1 { 28 | return l.Points[0].Y 29 | } 30 | if t <= l.Points[0].X { 31 | return l.Points[0].Y 32 | } 33 | for i := 1; i < len(l.Points); i++ { 34 | if t < l.Points[i].X { 35 | a := l.Points[i-1] 36 | b := l.Points[i] 37 | return a.Y + (t-a.X)*(b.Y-a.Y)/(b.X-a.X) 38 | } 39 | } 40 | return l.Points[len(l.Points)-1].Y 41 | } 42 | -------------------------------------------------------------------------------- /render/renderpass/subpass.go: -------------------------------------------------------------------------------- 1 | package renderpass 2 | 3 | import ( 4 | "github.com/johanhenriksson/goworld/render/renderpass/attachment" 5 | 6 | "github.com/vkngwrapper/core/v2/core1_0" 7 | ) 8 | 9 | type Name string 10 | 11 | const ExternalSubpass Name = "external" 12 | 13 | type Subpass struct { 14 | index int 15 | 16 | Name Name 17 | Depth bool 18 | ColorAttachments []attachment.Name 19 | InputAttachments []attachment.Name 20 | } 21 | 22 | func (s *Subpass) Index() int { 23 | return s.index 24 | } 25 | 26 | type SubpassDependency struct { 27 | Src Name 28 | Dst Name 29 | 30 | Flags core1_0.DependencyFlags 31 | SrcStageMask core1_0.PipelineStageFlags 32 | SrcAccessMask core1_0.AccessFlags 33 | DstStageMask core1_0.PipelineStageFlags 34 | DstAccessMask core1_0.AccessFlags 35 | } 36 | -------------------------------------------------------------------------------- /gui/node/renderer.go: -------------------------------------------------------------------------------- 1 | package node 2 | 3 | import ( 4 | "github.com/johanhenriksson/goworld/gui/widget" 5 | "github.com/johanhenriksson/goworld/math/vec2" 6 | 7 | "github.com/kjk/flex" 8 | ) 9 | 10 | type RenderFunc func() T 11 | 12 | type Renderer interface { 13 | Render(viewport vec2.T) widget.T 14 | } 15 | 16 | type renderer struct { 17 | key string 18 | root RenderFunc 19 | tree T 20 | display widget.T 21 | } 22 | 23 | func NewRenderer(key string, app RenderFunc) Renderer { 24 | return &renderer{ 25 | root: app, 26 | } 27 | } 28 | 29 | func (r *renderer) Render(viewport vec2.T) widget.T { 30 | r.tree = Reconcile(r.tree, r.root()) 31 | r.display = r.tree.Hydrate(r.key) 32 | 33 | root := r.display.Flex() 34 | flex.CalculateLayout(root, viewport.X, viewport.Y, flex.DirectionLTR) 35 | 36 | return r.display 37 | } 38 | -------------------------------------------------------------------------------- /editor/propedit/transform_editor.go: -------------------------------------------------------------------------------- 1 | package propedit 2 | 3 | import ( 4 | "github.com/johanhenriksson/goworld/core/transform" 5 | "github.com/johanhenriksson/goworld/gui/node" 6 | "github.com/johanhenriksson/goworld/math/quat" 7 | "github.com/johanhenriksson/goworld/math/vec3" 8 | ) 9 | 10 | func Transform(key string, tf transform.T) node.T { 11 | return Container(key, []node.T{ 12 | Vec3Field("position", "Position", Vec3Props{ 13 | Value: tf.Position(), 14 | OnChange: tf.SetPosition, 15 | }), 16 | Vec3Field("rotation", "Rotation", Vec3Props{ 17 | Value: tf.Rotation().Euler(), 18 | OnChange: func(euler vec3.T) { 19 | tf.SetRotation(quat.Euler(euler.X, euler.Y, euler.Z)) 20 | }, 21 | }), 22 | Vec3Field("scale", "Scale", Vec3Props{ 23 | Value: tf.Scale(), 24 | OnChange: tf.SetScale, 25 | }), 26 | }) 27 | } 28 | -------------------------------------------------------------------------------- /math/vec2/operations.go: -------------------------------------------------------------------------------- 1 | package vec2 2 | 3 | import "github.com/johanhenriksson/goworld/math" 4 | 5 | // New returns a vec2 from its components 6 | func New(x, y float32) T { 7 | return T{X: x, Y: y} 8 | } 9 | 10 | // NewI returns a vec2 from integer components 11 | func NewI(x, y int) T { 12 | return T{X: float32(x), Y: float32(y)} 13 | } 14 | 15 | // Dot returns the dot product of two vectors. 16 | func Dot(a, b T) float32 { 17 | return a.X*b.X + a.Y*b.Y 18 | } 19 | 20 | // Distance returns the euclidian distance between two points. 21 | func Distance(a, b T) float32 { 22 | return a.Sub(b).Length() 23 | } 24 | 25 | func Min(a, b T) T { 26 | return T{ 27 | X: math.Min(a.X, b.X), 28 | Y: math.Min(a.Y, b.Y), 29 | } 30 | } 31 | 32 | func Max(a, b T) T { 33 | return T{ 34 | X: math.Max(a.X, b.X), 35 | Y: math.Max(a.Y, b.Y), 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /engine/window/glfw/util.go: -------------------------------------------------------------------------------- 1 | package glfw 2 | 3 | import ( 4 | "github.com/go-gl/glfw/v3.3/glfw" 5 | "github.com/johanhenriksson/goworld/math" 6 | ) 7 | 8 | func GetCurrentMonitor(window *glfw.Window) *glfw.Monitor { 9 | // translated to Go from https://stackoverflow.com/a/31526753 10 | wx, wy := window.GetPos() 11 | ww, wh := window.GetSize() 12 | 13 | bestoverlap := 0 14 | var bestmonitor *glfw.Monitor 15 | for _, monitor := range glfw.GetMonitors() { 16 | mode := monitor.GetVideoMode() 17 | mx, my := monitor.GetPos() 18 | mw, mh := mode.Width, mode.Height 19 | 20 | overlap := math.Max(0, math.Min(wx+ww, mx+mw)-math.Max(wx, mx)) * 21 | math.Max(0, math.Min(wy+wh, my+mh)-math.Max(wy, my)) 22 | 23 | if bestoverlap < overlap { 24 | bestoverlap = overlap 25 | bestmonitor = monitor 26 | } 27 | } 28 | 29 | return bestmonitor 30 | } 31 | -------------------------------------------------------------------------------- /gui/widget/button/button_suite_test.go: -------------------------------------------------------------------------------- 1 | package button_test 2 | 3 | import ( 4 | . "github.com/onsi/ginkgo/v2" 5 | . "github.com/onsi/gomega" 6 | 7 | "testing" 8 | 9 | "github.com/johanhenriksson/goworld/gui/node" 10 | "github.com/johanhenriksson/goworld/gui/widget/button" 11 | "github.com/johanhenriksson/goworld/math/vec2" 12 | ) 13 | 14 | func TestButton(t *testing.T) { 15 | RegisterFailHandler(Fail) 16 | RunSpecs(t, "gui/widget/button") 17 | } 18 | 19 | var _ = Describe("button", func() { 20 | It("renders", func() { 21 | renderer := node.NewRenderer("test", func() node.T { 22 | return button.New("button", button.Props{}) 23 | }) 24 | tree := renderer.Render(vec2.One) 25 | 26 | children := tree.Children() 27 | for _, child := range children { 28 | child.Children() 29 | } 30 | 31 | // todo: incomplete test 32 | }) 33 | }) 34 | -------------------------------------------------------------------------------- /math/mat4/translation.go: -------------------------------------------------------------------------------- 1 | // Based on code from github.com/go-gl/mathgl: 2 | // Copyright 2014 The go-gl Authors. All rights reserved. 3 | // Use of this source code is governed by a BSD-style 4 | // license that can be found in the LICENSE file. 5 | package mat4 6 | 7 | import ( 8 | "github.com/johanhenriksson/goworld/math/vec3" 9 | ) 10 | 11 | // Translate returns a homogeneous (4x4 for 3D-space) Translation matrix that moves a point by Tx units in the x-direction, Ty units in the y-direction, 12 | // and Tz units in the z-direction 13 | func Translate(translation vec3.T) T { 14 | return T{1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, translation.X, translation.Y, translation.Z, 1} 15 | } 16 | 17 | // Scale creates a homogeneous 3D scaling matrix 18 | func Scale(scale vec3.T) T { 19 | return T{scale.X, 0, 0, 0, 0, scale.Y, 0, 0, 0, 0, scale.Z, 0, 0, 0, 0, 1} 20 | } 21 | -------------------------------------------------------------------------------- /core/input/debug.go: -------------------------------------------------------------------------------- 1 | package input 2 | 3 | import ( 4 | "log" 5 | 6 | "github.com/johanhenriksson/goworld/core/input/keys" 7 | "github.com/johanhenriksson/goworld/core/input/mouse" 8 | ) 9 | 10 | type nopHandler struct{} 11 | 12 | func (h *nopHandler) KeyEvent(e keys.Event) {} 13 | func (h *nopHandler) MouseEvent(e mouse.Event) {} 14 | 15 | func NopHandler() Handler { 16 | return &nopHandler{} 17 | } 18 | 19 | type debugger struct { 20 | Handler 21 | } 22 | 23 | func DebugMiddleware(next Handler) Handler { 24 | return &debugger{next} 25 | } 26 | 27 | func (d debugger) KeyEvent(e keys.Event) { 28 | log.Printf("%+v\n", e) 29 | if d.Handler != nil { 30 | d.Handler.KeyEvent(e) 31 | } 32 | } 33 | 34 | func (d debugger) MouseEvent(e mouse.Event) { 35 | log.Printf("%+v\n", e) 36 | if d.Handler != nil { 37 | d.Handler.MouseEvent(e) 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /editor/propedit/color_editor.go: -------------------------------------------------------------------------------- 1 | package propedit 2 | 3 | import ( 4 | "github.com/johanhenriksson/goworld/core/object" 5 | "github.com/johanhenriksson/goworld/gui/node" 6 | "github.com/johanhenriksson/goworld/math/vec3" 7 | "github.com/johanhenriksson/goworld/render/color" 8 | ) 9 | 10 | func init() { 11 | Register[color.T](func(key, name string, prop object.GenericProp) node.T { 12 | return ColorField(key, name, ColorProps{ 13 | Value: prop.GetAny().(color.T), 14 | OnChange: func(c color.T) { prop.SetAny(c) }, 15 | }) 16 | }) 17 | } 18 | 19 | type ColorProps struct { 20 | Value color.T 21 | OnChange func(color.T) 22 | } 23 | 24 | func ColorField(key string, title string, props ColorProps) node.T { 25 | return Vec3Field(key, title, Vec3Props{ 26 | Value: props.Value.Vec3(), 27 | OnChange: func(v vec3.T) { props.OnChange(color.FromVec3(v)) }, 28 | }) 29 | } 30 | -------------------------------------------------------------------------------- /engine/app/interrupter.go: -------------------------------------------------------------------------------- 1 | package app 2 | 3 | import ( 4 | "log" 5 | "os" 6 | "os/signal" 7 | "runtime" 8 | ) 9 | 10 | type Interrupter interface { 11 | Running() bool 12 | } 13 | 14 | type interrupter struct { 15 | running bool 16 | } 17 | 18 | func (r *interrupter) Running() bool { 19 | return r.running 20 | } 21 | 22 | func NewInterrupter() Interrupter { 23 | sigint := make(chan os.Signal, 1) 24 | signal.Notify(sigint, os.Interrupt) 25 | r := &interrupter{running: true} 26 | 27 | go func() { 28 | for range sigint { 29 | if !r.running { 30 | stackdump := make([]byte, 100*1024) 31 | n := runtime.Stack(stackdump, true) 32 | log.Println("Goroutine dump:\n" + string(stackdump[:n])) 33 | 34 | log.Println("Killed") 35 | os.Exit(1) 36 | } else { 37 | log.Println("Interrupt") 38 | r.running = false 39 | } 40 | } 41 | }() 42 | 43 | return r 44 | } 45 | -------------------------------------------------------------------------------- /core/object/dict.go: -------------------------------------------------------------------------------- 1 | package object 2 | 3 | type Dict[K comparable, V any] struct { 4 | items map[K]V 5 | } 6 | 7 | func NewDict[K comparable, V any]() Dict[K, V] { 8 | return Dict[K, V]{ 9 | items: map[K]V{}, 10 | } 11 | } 12 | 13 | func (d *Dict[K, V]) Get(key K) (V, bool) { 14 | v, ok := d.items[key] 15 | return v, ok 16 | } 17 | 18 | func (d *Dict[K, V]) GetAny(key K) (any, bool) { 19 | v, ok := d.items[key] 20 | return v, ok 21 | } 22 | 23 | func (d *Dict[K, V]) Set(key K, value V) { 24 | d.items[key] = value 25 | } 26 | 27 | func (d *Dict[K, V]) SetAny(key K, value any) { 28 | d.items[key] = value.(V) 29 | } 30 | 31 | func (d *Dict[K, V]) Delete(key K) { 32 | delete(d.items, key) 33 | } 34 | 35 | func (d *Dict[K, V]) Serialize(enc Encoder) error { 36 | return enc.Encode(d.items) 37 | } 38 | 39 | func (d *Dict[K, V]) Deserialize(dec Decoder) error { 40 | return dec.Decode(&d.items) 41 | } 42 | -------------------------------------------------------------------------------- /assets/texture.go: -------------------------------------------------------------------------------- 1 | package assets 2 | 3 | import ( 4 | "github.com/johanhenriksson/goworld/assets/fs" 5 | "github.com/johanhenriksson/goworld/render/color" 6 | "github.com/johanhenriksson/goworld/render/font" 7 | "github.com/johanhenriksson/goworld/render/texture" 8 | ) 9 | 10 | type Texture interface { 11 | Asset 12 | 13 | // LoadTexture is called by texture caches and loaders, and should return the texture data. 14 | // Its unfortunate that this method cant return the texture itself directly, since it 15 | // requires access to a graphics queue. The texture upload logic must be centralized somewhere. 16 | // Techincally not different for meshes? hmm 17 | // Should there be a concept on a cpu-side texture? Similar to vertex.Mesh? 18 | LoadTexture(assets fs.Filesystem) *texture.Data 19 | } 20 | 21 | var _ Texture = texture.PathRef("") 22 | var _ Texture = (*font.Glyph)(nil) 23 | var _ Texture = color.White 24 | -------------------------------------------------------------------------------- /gui/hooks/hooks_test.go: -------------------------------------------------------------------------------- 1 | package hooks_test 2 | 3 | import ( 4 | . "github.com/onsi/ginkgo/v2" 5 | . "github.com/onsi/gomega" 6 | 7 | "testing" 8 | 9 | "github.com/johanhenriksson/goworld/gui/hooks" 10 | ) 11 | 12 | func TestHooks(t *testing.T) { 13 | RegisterFailHandler(Fail) 14 | RunSpecs(t, "gui/hooks") 15 | } 16 | 17 | func SomeComponent() (string, func()) { 18 | title, setTitle := hooks.UseState("hello!") 19 | click := func() { 20 | setTitle("clicked") 21 | } 22 | return title, click 23 | } 24 | 25 | var _ = Describe("hooks", func() { 26 | It("updates and maintains state", func() { 27 | state := hooks.State{} 28 | hooks.Enable(&state) 29 | output, click := SomeComponent() 30 | hooks.Disable() 31 | Expect(output).To(Equal("hello!")) 32 | 33 | click() 34 | 35 | hooks.Enable(&state) 36 | output, _ = SomeComponent() 37 | hooks.Disable() 38 | Expect(output).To(Equal("clicked")) 39 | }) 40 | }) 41 | -------------------------------------------------------------------------------- /render/font/glyph.go: -------------------------------------------------------------------------------- 1 | package font 2 | 3 | import ( 4 | "github.com/johanhenriksson/goworld/assets/fs" 5 | "github.com/johanhenriksson/goworld/math/vec2" 6 | "github.com/johanhenriksson/goworld/render/image" 7 | "github.com/johanhenriksson/goworld/render/texture" 8 | 9 | "github.com/vkngwrapper/core/v2/core1_0" 10 | ) 11 | 12 | type Glyph struct { 13 | key string 14 | Size vec2.T 15 | Bearing vec2.T 16 | Advance float32 17 | Mask *image.Data 18 | } 19 | 20 | // 21 | // assets.Texture implementation 22 | // 23 | 24 | func (r *Glyph) Key() string { return r.key } 25 | func (r *Glyph) Version() int { return 1 } 26 | 27 | func (r *Glyph) LoadTexture(fs.Filesystem) *texture.Data { 28 | return &texture.Data{ 29 | Image: r.Mask, 30 | Args: texture.Args{ 31 | Filter: texture.FilterLinear, 32 | Wrap: texture.WrapBorder, 33 | Border: core1_0.BorderColorFloatTransparentBlack, 34 | Mipmaps: false, 35 | }, 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /engine/cache/shader_cache.go: -------------------------------------------------------------------------------- 1 | package cache 2 | 3 | import ( 4 | "github.com/johanhenriksson/goworld/assets" 5 | "github.com/johanhenriksson/goworld/render/device" 6 | "github.com/johanhenriksson/goworld/render/shader" 7 | ) 8 | 9 | type ShaderCache T[assets.Shader, *shader.Shader] 10 | 11 | func NewShaderCache(dev *device.Device) ShaderCache { 12 | return New[assets.Shader, *shader.Shader](&shaders{ 13 | device: dev, 14 | }) 15 | } 16 | 17 | type shaders struct { 18 | device *device.Device 19 | } 20 | 21 | func (s *shaders) Name() string { 22 | return "Shaders" 23 | } 24 | 25 | func (s *shaders) Instantiate(key assets.Shader, callback func(*shader.Shader)) { 26 | // load shader in a background goroutine 27 | go func() { 28 | shader := key.LoadShader(assets.FS, s.device) 29 | callback(shader) 30 | }() 31 | } 32 | 33 | func (s *shaders) Delete(shader *shader.Shader) { 34 | shader.Destroy() 35 | } 36 | 37 | func (s *shaders) Destroy() { 38 | } 39 | -------------------------------------------------------------------------------- /render/image/loader.go: -------------------------------------------------------------------------------- 1 | package image 2 | 3 | import ( 4 | "bytes" 5 | imglib "image" 6 | "image/draw" 7 | 8 | // image codecs 9 | _ "image/png" 10 | 11 | "github.com/johanhenriksson/goworld/assets/fs" 12 | "github.com/vkngwrapper/core/v2/core1_0" 13 | ) 14 | 15 | type Data struct { 16 | Width int 17 | Height int 18 | Format core1_0.Format 19 | Buffer []byte 20 | } 21 | 22 | func LoadFile(assets fs.Filesystem, file string) (*Data, error) { 23 | imgdata, err := assets.Read(file) 24 | if err != nil { 25 | return nil, err 26 | } 27 | buf := bytes.NewBuffer(imgdata) 28 | img, _, err := imglib.Decode(buf) 29 | if err != nil { 30 | return nil, err 31 | } 32 | 33 | rgba := imglib.NewRGBA(img.Bounds()) 34 | draw.Draw(rgba, rgba.Bounds(), img, imglib.Point{0, 0}, draw.Src) 35 | 36 | return &Data{ 37 | Width: rgba.Rect.Size().X, 38 | Height: rgba.Rect.Size().Y, 39 | Format: FormatRGBA8Unorm, 40 | Buffer: rgba.Pix, 41 | }, nil 42 | } 43 | -------------------------------------------------------------------------------- /physics/collision_mesh_test.go: -------------------------------------------------------------------------------- 1 | package physics_test 2 | 3 | import ( 4 | . "github.com/onsi/ginkgo/v2" 5 | . "github.com/onsi/gomega" 6 | 7 | "github.com/johanhenriksson/goworld/math/vec3" 8 | "github.com/johanhenriksson/goworld/physics" 9 | "github.com/johanhenriksson/goworld/render/vertex" 10 | ) 11 | 12 | var _ = Describe("Optimize", func() { 13 | It("correctly reduces the mesh", func() { 14 | vertices := []physics.Vertex{ 15 | {vec3.Zero}, 16 | {vec3.Zero}, 17 | {vec3.New(1, 1, 1)}, 18 | {vec3.Zero}, 19 | {vec3.One}, 20 | } 21 | indices := []uint32{ 22 | 4, 1, 2, 3, 0, 23 | } 24 | 25 | A := vertex.NewTriangles("test", vertices, indices) 26 | C := physics.CollisionMesh(A) 27 | 28 | m := C.(vertex.MutableMesh[physics.Vertex, uint32]) 29 | Expect(m.Vertices()).To(HaveLen(2)) 30 | Expect(m.Vertices()).To(Equal([]physics.Vertex{{vec3.One}, {vec3.Zero}})) 31 | Expect(m.Indices()).To(Equal([]uint32{0, 1, 0, 1, 1})) 32 | }) 33 | }) 34 | -------------------------------------------------------------------------------- /util/align.go: -------------------------------------------------------------------------------- 1 | package util 2 | 3 | import ( 4 | "fmt" 5 | "reflect" 6 | ) 7 | 8 | // ValidateAlignment checks if a given struct shares the memory layout of an equivalent C struct 9 | func ValidateAlignment(value any) error { 10 | t := reflect.TypeOf(value) 11 | if t.Kind() != reflect.Struct { 12 | return fmt.Errorf("value must be a struct, was %s", t.Kind()) 13 | } 14 | 15 | expectedOffset := 0 16 | for i := 0; i < t.NumField(); i++ { 17 | field := t.Field(i) 18 | if field.Offset != uintptr(expectedOffset) { 19 | return fmt.Errorf("layout causes alignment issues. expected field %s to have offset %d, was %d", 20 | field.Name, expectedOffset, field.Offset) 21 | } 22 | expectedOffset = int(field.Offset + field.Type.Size()) 23 | } 24 | 25 | return nil 26 | } 27 | 28 | func Align(offset, alignment int) int { 29 | count := offset / alignment 30 | diff := offset % alignment 31 | if diff > 0 { 32 | count++ 33 | } 34 | return count * alignment 35 | } 36 | -------------------------------------------------------------------------------- /core/object/ghost.go: -------------------------------------------------------------------------------- 1 | package object 2 | 3 | import "github.com/johanhenriksson/goworld/core/transform" 4 | 5 | type ghost struct { 6 | object 7 | target transform.T 8 | } 9 | 10 | func Ghost(pool Pool, name string, target transform.T) Object { 11 | ghost := &ghost{ 12 | object: *emptyObject(pool, "Ghost:"+name), 13 | target: target, 14 | } 15 | ghost.transform = target 16 | return ghost 17 | } 18 | 19 | func (g *ghost) setParent(parent Object) { 20 | g.component.setParent(parent) 21 | // do not modify transform hierarchy 22 | } 23 | 24 | type float struct { 25 | object 26 | } 27 | 28 | // Floating objects are not part of the transform heirarchy 29 | func Floating(pool Pool, name string) Object { 30 | float := &float{ 31 | object: *emptyObject(pool, name), 32 | } 33 | float.transform.SetParent(nil) 34 | return float 35 | } 36 | 37 | func (f *float) setParent(parent Object) { 38 | f.component.setParent(parent) 39 | // do not modify transform hierarchy 40 | } 41 | -------------------------------------------------------------------------------- /render/buffer/item.go: -------------------------------------------------------------------------------- 1 | package buffer 2 | 3 | import ( 4 | "fmt" 5 | "reflect" 6 | 7 | "github.com/johanhenriksson/goworld/render/device" 8 | "github.com/johanhenriksson/goworld/util" 9 | ) 10 | 11 | type Item[K any] struct { 12 | T 13 | } 14 | 15 | // NewItem creates a new typed single-item buffer. 16 | // When allocating items, the Size argument is ignored 17 | func NewItem[K any](device *device.Device, args Args) *Item[K] { 18 | align, maxSize := GetBufferLimits(device, args.Usage) 19 | 20 | var empty K 21 | kind := reflect.TypeOf(empty) 22 | 23 | element := util.Align(int(kind.Size()), align) 24 | if element > maxSize { 25 | panic(fmt.Sprintf("buffer is too large for the specified usage. size: %d, max: %d", element, maxSize)) 26 | } 27 | 28 | args.Size = element 29 | buffer := New(device, args) 30 | 31 | return &Item[K]{ 32 | T: buffer, 33 | } 34 | } 35 | 36 | func (i *Item[K]) Set(data K) { 37 | ptr := &data 38 | i.Write(0, ptr) 39 | i.Flush() 40 | } 41 | -------------------------------------------------------------------------------- /gui/widget/icon/icon.go: -------------------------------------------------------------------------------- 1 | package icon 2 | 3 | import ( 4 | "github.com/johanhenriksson/goworld/core/input/mouse" 5 | "github.com/johanhenriksson/goworld/gui/node" 6 | "github.com/johanhenriksson/goworld/gui/style" 7 | "github.com/johanhenriksson/goworld/gui/widget/label" 8 | ) 9 | 10 | type Icon rune 11 | 12 | type Props struct { 13 | Icon Icon 14 | Style Style 15 | OnMouseUp func(mouse.Event) 16 | OnMouseDown func(mouse.Event) 17 | } 18 | 19 | func New(key string, props Props) node.T { 20 | return label.New(key, label.Props{ 21 | Text: string(props.Icon), 22 | OnMouseUp: props.OnMouseUp, 23 | OnMouseDown: props.OnMouseDown, 24 | Style: label.Style{ 25 | Color: props.Style.Color, 26 | Hidden: props.Icon == IconNone, 27 | Font: style.Font{ 28 | Name: "fonts/MaterialIcons-Regular.ttf", 29 | Size: props.Style.Size, 30 | }, 31 | Hover: label.Hover{ 32 | Color: props.Style.Hover.Color, 33 | }, 34 | }, 35 | }) 36 | } 37 | -------------------------------------------------------------------------------- /editor/propedit/bool_editor.go: -------------------------------------------------------------------------------- 1 | package propedit 2 | 3 | import ( 4 | "github.com/johanhenriksson/goworld/gui/node" 5 | "github.com/johanhenriksson/goworld/gui/style" 6 | "github.com/johanhenriksson/goworld/gui/widget/checkbox" 7 | "github.com/johanhenriksson/goworld/gui/widget/label" 8 | "github.com/johanhenriksson/goworld/gui/widget/rect" 9 | ) 10 | 11 | type BoolProps struct { 12 | Value bool 13 | OnChange func(bool) 14 | } 15 | 16 | func BoolField(key string, title string, props BoolProps) node.T { 17 | return rect.New(key, rect.Props{ 18 | Style: rect.Style{ 19 | Layout: style.Row{}, 20 | Width: style.Pct(100), 21 | Padding: style.RectY(4), 22 | }, 23 | Children: []node.T{ 24 | label.New("label", label.Props{ 25 | Text: title, 26 | Style: label.Style{ 27 | Grow: style.Grow(1), 28 | }, 29 | }), 30 | checkbox.New("value", checkbox.Props{ 31 | Checked: props.Value, 32 | OnChange: props.OnChange, 33 | }), 34 | }, 35 | }) 36 | } 37 | -------------------------------------------------------------------------------- /render/font/font_suite_test.go: -------------------------------------------------------------------------------- 1 | package font_test 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/johanhenriksson/goworld/assets" 7 | "github.com/johanhenriksson/goworld/render/font" 8 | . "github.com/onsi/ginkgo/v2" 9 | . "github.com/onsi/gomega" 10 | "golang.org/x/image/math/fixed" 11 | ) 12 | 13 | func TestFont(t *testing.T) { 14 | RegisterFailHandler(Fail) 15 | RunSpecs(t, "render/font") 16 | } 17 | 18 | var _ = Describe("font utils", func() { 19 | It("converts fixed to float32", func() { 20 | v := fixed.I(2) 21 | Expect(font.FixToFloat(v)).To(BeNumerically("~", float32(2.0))) 22 | 23 | v2 := fixed.Int26_6(1<<6 + 1<<4) 24 | Expect(font.FixToFloat(v2)).To(BeNumerically("~", float32(1.25))) 25 | }) 26 | 27 | It("extracts glyphs", func() { 28 | f := font.Load(assets.FS, "fonts/SourceSansPro-Regular.ttf", 12, 1) 29 | Expect(f).ToNot(BeNil()) 30 | a, err := f.Glyph('g') 31 | Expect(err).ToNot(HaveOccurred()) 32 | Expect(a.Advance).To(BeNumerically(">", 0)) 33 | }) 34 | }) 35 | -------------------------------------------------------------------------------- /core/light/light.go: -------------------------------------------------------------------------------- 1 | package light 2 | 3 | import ( 4 | "github.com/johanhenriksson/goworld/core/object" 5 | "github.com/johanhenriksson/goworld/engine/uniform" 6 | "github.com/johanhenriksson/goworld/math/mat4" 7 | "github.com/johanhenriksson/goworld/math/vec4" 8 | "github.com/johanhenriksson/goworld/render/color" 9 | ) 10 | 11 | type ShadowmapStore interface { 12 | Lookup(T, int) (int, bool) 13 | } 14 | 15 | type T interface { 16 | object.Component 17 | 18 | Type() Type 19 | CastShadows() bool 20 | Shadowmaps() int 21 | LightData(ShadowmapStore) uniform.Light 22 | ShadowProjection(mapIndex int) uniform.Camera 23 | } 24 | 25 | // Descriptor holds rendering information for lights 26 | type Descriptor struct { 27 | Projection mat4.T // Light projection matrix 28 | View mat4.T // Light view matrix 29 | ViewProj mat4.T 30 | Color color.T 31 | Position vec4.T 32 | Type Type 33 | Range float32 34 | Intensity float32 35 | Shadows uint32 36 | Index int 37 | } 38 | -------------------------------------------------------------------------------- /engine/graph/post_node.go: -------------------------------------------------------------------------------- 1 | package graph 2 | 3 | import ( 4 | "github.com/johanhenriksson/goworld/engine" 5 | "github.com/johanhenriksson/goworld/render/command" 6 | "github.com/johanhenriksson/goworld/render/swapchain" 7 | "github.com/johanhenriksson/goworld/render/sync" 8 | ) 9 | 10 | type postNode struct { 11 | *node 12 | target engine.Target 13 | } 14 | 15 | func newPostNode(app engine.App, target engine.Target) *postNode { 16 | return &postNode{ 17 | node: newNode(app, "Post", nil), 18 | target: target, 19 | } 20 | } 21 | 22 | func (n *postNode) Present(worker command.Worker, context *swapchain.Context) { 23 | // submit a dummy pass that waits for all previous passes to complete, then signals the render complete semaphore 24 | worker.Submit(command.SubmitInfo{ 25 | Marker: n.Name(), 26 | Commands: command.Empty, 27 | Wait: n.waits(context.Index), 28 | Signal: []*sync.Semaphore{context.RenderComplete}, 29 | }) 30 | 31 | // present 32 | n.target.Present(worker, context) 33 | } 34 | -------------------------------------------------------------------------------- /core/events/event.go: -------------------------------------------------------------------------------- 1 | package events 2 | 3 | type Data any 4 | 5 | type Event[T Data] struct { 6 | callbacks []func(T) 7 | } 8 | 9 | func New[T Data]() Event[T] { 10 | return Event[T]{} 11 | } 12 | 13 | func (e Event[T]) Emit(event T) { 14 | for _, callback := range e.callbacks { 15 | if callback != nil { 16 | callback(event) 17 | } 18 | } 19 | } 20 | 21 | func (e *Event[T]) Subscribe(handler func(T)) func() { 22 | unsubscriber := func(id int) func() { 23 | called := false 24 | return func() { 25 | if called { 26 | // its not safe to call the unsubscriber multiple times since the id might be reused 27 | panic("unsubscriber called multiple times") 28 | } 29 | e.callbacks[id] = nil 30 | called = true 31 | } 32 | } 33 | 34 | for id, callback := range e.callbacks { 35 | if callback == nil { 36 | e.callbacks[id] = handler 37 | return unsubscriber(id) 38 | } 39 | } 40 | 41 | e.callbacks = append(e.callbacks, handler) 42 | return unsubscriber(len(e.callbacks) - 1) 43 | } 44 | -------------------------------------------------------------------------------- /physics/callback.go: -------------------------------------------------------------------------------- 1 | package physics 2 | 3 | /* 4 | #cgo CXXFLAGS: -std=c++11 -I/usr/local/include/bullet 5 | #cgo CFLAGS: -I/usr/local/include/bullet 6 | #cgo LDFLAGS: -lstdc++ -L/usr/local/lib -lBulletDynamics -lBulletCollision -lLinearMath -lBullet3Common 7 | #include "bullet.h" 8 | */ 9 | import "C" 10 | 11 | import ( 12 | "github.com/johanhenriksson/goworld/geometry/lines" 13 | "github.com/johanhenriksson/goworld/math/vec3" 14 | "github.com/johanhenriksson/goworld/render/color" 15 | ) 16 | 17 | //export GoDrawLineCallback 18 | func GoDrawLineCallback(handle C.goDynamicsWorldHandle, start_x, start_y, start_z, end_x, end_y, end_z, color_r, color_g, color_b C.float) { 19 | start := vec3.New(float32(start_x), float32(start_y), float32(start_z)) 20 | end := vec3.New(float32(end_x), float32(end_y), float32(end_z)) 21 | color := color.RGB(float32(color_r), float32(color_g), float32(color_b)) 22 | 23 | // retrive world object 24 | // world := debugWorlds[handle] 25 | 26 | lines.Debug.Add(start, end, color) 27 | } 28 | -------------------------------------------------------------------------------- /engine/pass/shadow_cache.go: -------------------------------------------------------------------------------- 1 | package pass 2 | 3 | import ( 4 | "github.com/johanhenriksson/goworld/core/light" 5 | "github.com/johanhenriksson/goworld/engine/cache" 6 | "github.com/johanhenriksson/goworld/render/descriptor" 7 | ) 8 | 9 | type ShadowCache struct { 10 | samplers cache.SamplerCache 11 | lookup ShadowmapLookupFn 12 | } 13 | 14 | var _ light.ShadowmapStore = &ShadowCache{} 15 | 16 | func NewShadowCache(samplers cache.SamplerCache, lookup ShadowmapLookupFn) *ShadowCache { 17 | return &ShadowCache{ 18 | samplers: samplers, 19 | lookup: lookup, 20 | } 21 | } 22 | 23 | func (s *ShadowCache) Lookup(lit light.T, cascade int) (int, bool) { 24 | if shadowtex := s.lookup(lit, cascade); shadowtex != nil { 25 | handle := s.samplers.Assign(shadowtex) 26 | return handle.ID, true 27 | } 28 | // no shadowmap available 29 | return 0, false 30 | } 31 | 32 | // Flush the underlying sampler cache 33 | func (s *ShadowCache) Flush(samplers *descriptor.SamplerArray) { 34 | s.samplers.Flush(samplers) 35 | } 36 | -------------------------------------------------------------------------------- /gui/widget/test_widget.go: -------------------------------------------------------------------------------- 1 | package widget 2 | 3 | import ( 4 | "github.com/johanhenriksson/goworld/math/vec2" 5 | "github.com/kjk/flex" 6 | ) 7 | 8 | func Dummy(key string) T { 9 | node := flex.NewNodeWithConfig(flex.NewConfig()) 10 | node.Context = key 11 | return &dummy{ 12 | key: key, 13 | flex: node, 14 | } 15 | } 16 | 17 | type dummy struct { 18 | key string 19 | flex *flex.Node 20 | children []T 21 | props any 22 | } 23 | 24 | func (w *dummy) Key() string { return w.key } 25 | func (w *dummy) Destroy() {} 26 | 27 | func (w *dummy) Update(p any) { w.props = p } 28 | func (w *dummy) Props() any { return w.props } 29 | 30 | func (w *dummy) Flex() *flex.Node { return w.flex } 31 | 32 | func (w *dummy) Position() vec2.T { return vec2.Zero } 33 | func (w *dummy) Size() vec2.T { return vec2.Zero } 34 | 35 | func (w *dummy) Draw(args DrawArgs, quads *QuadBuffer) {} 36 | 37 | func (w *dummy) Children() []T { return w.children } 38 | func (w *dummy) SetChildren(c []T) { w.children = c } 39 | -------------------------------------------------------------------------------- /gui/widget/textbox/style.go: -------------------------------------------------------------------------------- 1 | package textbox 2 | 3 | import ( 4 | . "github.com/johanhenriksson/goworld/gui/style" 5 | "github.com/johanhenriksson/goworld/gui/widget/label" 6 | "github.com/johanhenriksson/goworld/gui/widget/rect" 7 | "github.com/johanhenriksson/goworld/render/color" 8 | ) 9 | 10 | var DefaultStyle = Style{ 11 | Text: label.Style{ 12 | Color: color.Black, 13 | }, 14 | Bg: rect.Style{ 15 | Color: color.White, 16 | Padding: RectXY(4, 2), 17 | Basis: Pct(100), 18 | Shrink: Shrink(1), 19 | Grow: Grow(1), 20 | Radius: Px(5), 21 | Border: Border{ 22 | Width: Px(1), 23 | Color: color.Black, 24 | }, 25 | }, 26 | } 27 | 28 | var InputErrorStyle = Style{ 29 | Text: label.Style{ 30 | Color: color.Black, 31 | }, 32 | Bg: rect.Style{ 33 | Color: color.White, 34 | Padding: RectXY(4, 2), 35 | Basis: Pct(100), 36 | Shrink: Shrink(1), 37 | Grow: Grow(1), 38 | Radius: Px(5), 39 | Border: Border{ 40 | Width: Px(1), 41 | Color: color.Red, 42 | }, 43 | }, 44 | } 45 | -------------------------------------------------------------------------------- /render/descriptor/set.go: -------------------------------------------------------------------------------- 1 | package descriptor 2 | 3 | import ( 4 | "github.com/johanhenriksson/goworld/render/device" 5 | 6 | "github.com/vkngwrapper/core/v2/core1_0" 7 | ) 8 | 9 | type Set interface { 10 | Ptr() core1_0.DescriptorSet 11 | Write(write core1_0.WriteDescriptorSet) 12 | Destroy() 13 | 14 | adopt(Descriptor) 15 | } 16 | 17 | type set struct { 18 | device *device.Device 19 | layout SetLayout 20 | ptr core1_0.DescriptorSet 21 | items []Descriptor 22 | } 23 | 24 | func (s *set) Ptr() core1_0.DescriptorSet { 25 | return s.ptr 26 | } 27 | 28 | func (s *set) Write(write core1_0.WriteDescriptorSet) { 29 | write.DstSet = s.ptr 30 | if err := s.device.Ptr().UpdateDescriptorSets([]core1_0.WriteDescriptorSet{write}, nil); err != nil { 31 | panic(err) 32 | } 33 | } 34 | 35 | func (s *set) Destroy() { 36 | for _, d := range s.items { 37 | d.Destroy() 38 | } 39 | } 40 | 41 | // adopt a descriptor, freeing it when the set is destroyed 42 | func (s *set) adopt(d Descriptor) { 43 | s.items = append(s.items, d) 44 | } 45 | -------------------------------------------------------------------------------- /render/descriptor/descriptor.go: -------------------------------------------------------------------------------- 1 | package descriptor 2 | 3 | import ( 4 | "github.com/johanhenriksson/goworld/render/device" 5 | 6 | "github.com/vkngwrapper/core/v2/core1_0" 7 | "github.com/vkngwrapper/extensions/v2/ext_descriptor_indexing" 8 | ) 9 | 10 | type Descriptor interface { 11 | // Initialize the descriptor with the provided device, set, and binding index. 12 | // This is called automatically during Set instantiation. 13 | Initialize(dev *device.Device, set Set, binding int) 14 | 15 | BindingFlags() ext_descriptor_indexing.DescriptorBindingFlags 16 | 17 | // Destroy the descriptor. 18 | // Releasing any resources it owns, such as uniform buffers. 19 | Destroy() 20 | 21 | // LayoutBinding returns a descriptor set layout binding for this descriptor 22 | // using the provided binding index. This is useful for creating descriptor 23 | // set layouts including this descriptor. 24 | LayoutBinding(int) core1_0.DescriptorSetLayoutBinding 25 | } 26 | 27 | type VariableDescriptor interface { 28 | Descriptor 29 | MaxCount() int 30 | } 31 | -------------------------------------------------------------------------------- /assets/builtin/shaders/deferred/deferred.vs.glsl: -------------------------------------------------------------------------------- 1 | #version 450 2 | 3 | #include "lib/common.glsl" 4 | #include "lib/objects.glsl" 5 | 6 | // Varyings 7 | OUT(0, flat uint, object) 8 | OUT(1, vec3, position) 9 | OUT(2, vec3, normal) 10 | OUT(3, vec4, color) 11 | OUT(4, vec2, texcoord) 12 | 13 | out gl_PerVertex { 14 | vec4 gl_Position; 15 | }; 16 | 17 | CAMERA(0, camera) 18 | OBJECT(1, object, gl_InstanceIndex) 19 | 20 | VERTEX_BUFFER(Vertex) 21 | INDEX_BUFFER(uint) 22 | 23 | void main() 24 | { 25 | out_object = get_object_index(); 26 | 27 | // load vertex data 28 | Vertex vtx = get_vertex_indexed(object.vertexPtr, object.indexPtr); 29 | 30 | mat4 mv = camera.View * object.model; 31 | 32 | // textures 33 | out_texcoord = vtx.tex; 34 | out_color = vtx.color; 35 | 36 | // gbuffer position 37 | out_position = (mv * vec4(vtx.position, 1)).xyz; 38 | 39 | // gbuffer normal 40 | out_normal = normalize((mv * vec4(vtx.normal, 0.0)).xyz); 41 | 42 | // vertex clip space position 43 | gl_Position = camera.Proj * vec4(out_position, 1); 44 | } 45 | -------------------------------------------------------------------------------- /engine/app/frame.go: -------------------------------------------------------------------------------- 1 | package app 2 | 3 | import ( 4 | osimage "image" 5 | "runtime" 6 | 7 | "github.com/johanhenriksson/goworld/core/object" 8 | "github.com/johanhenriksson/goworld/engine" 9 | "github.com/johanhenriksson/goworld/render/image" 10 | ) 11 | 12 | // Render a single frame and return it as *image.RGBA 13 | func Frame(args Args, scenefuncs ...object.SceneFunc) *osimage.RGBA { 14 | runtime.LockOSThread() 15 | args.Defaults() 16 | 17 | app := engine.New("goworld", 0) 18 | defer app.Destroy() 19 | 20 | buffer := engine.NewColorTarget(app.Device(), "output", image.FormatRGBA8Unorm, engine.TargetSize{ 21 | Width: args.Width, 22 | Height: args.Height, 23 | Frames: 1, 24 | Scale: 1, 25 | }) 26 | defer buffer.Destroy() 27 | 28 | // create renderer 29 | renderer := args.Renderer(app, buffer) 30 | defer renderer.Destroy() 31 | 32 | // create scene 33 | pool := object.NewPool() 34 | scene := object.Scene(pool, scenefuncs...) 35 | 36 | scene.Update(scene, 0) 37 | renderer.Draw(scene, 0, 0) 38 | 39 | return renderer.Screengrab() 40 | } 41 | -------------------------------------------------------------------------------- /core/object/registry.go: -------------------------------------------------------------------------------- 1 | package object 2 | 3 | import ( 4 | "reflect" 5 | ) 6 | 7 | type CreateFn func(Pool) (Component, error) 8 | 9 | type Type struct { 10 | Name string 11 | Path []string 12 | Create CreateFn 13 | 14 | rtype reflect.Type 15 | } 16 | 17 | type Registry map[string]*Type 18 | 19 | var types = Registry{} 20 | 21 | func typeName(obj any) string { 22 | t := reflect.TypeOf(obj).Elem() 23 | return t.PkgPath() + "/" + t.Name() 24 | } 25 | 26 | func init() { 27 | Register[*object](Type{ 28 | Name: "Object", 29 | Create: func(pool Pool) (Component, error) { 30 | return Empty(pool, "Object"), nil 31 | }, 32 | rtype: baseObjectType, 33 | }) 34 | Register[*component](Type{ 35 | Name: "Component", 36 | rtype: baseComponentType, 37 | }) 38 | } 39 | 40 | func Register[T any](info Type) { 41 | var empty T 42 | kind := typeName(empty) 43 | info.rtype = reflect.TypeOf(empty).Elem() 44 | if info.Name == "" { 45 | info.Name = kind 46 | } 47 | types[kind] = &info 48 | } 49 | 50 | func Types() Registry { 51 | return types 52 | } 53 | -------------------------------------------------------------------------------- /gui/style/fixed.go: -------------------------------------------------------------------------------- 1 | package style 2 | 3 | import ( 4 | "github.com/kjk/flex" 5 | ) 6 | 7 | // Px is a pixel value 8 | type Px float32 9 | 10 | func (p Px) ApplyBasis(fw FlexWidget) { fw.Flex().StyleSetFlexBasis(float32(p)) } 11 | func (p Px) ApplyWidth(fw FlexWidget) { fw.Flex().StyleSetWidth(float32(p)) } 12 | func (p Px) ApplyMinWidth(fw FlexWidget) { fw.Flex().StyleSetMinWidth(float32(p)) } 13 | func (p Px) ApplyMaxWidth(fw FlexWidget) { fw.Flex().StyleSetMaxWidth(float32(p)) } 14 | func (p Px) ApplyHeight(fw FlexWidget) { fw.Flex().StyleSetHeight(float32(p)) } 15 | func (p Px) ApplyMinHeight(fw FlexWidget) { fw.Flex().StyleSetMinHeight(float32(p)) } 16 | func (p Px) ApplyMaxHeight(fw FlexWidget) { fw.Flex().StyleSetMaxHeight(float32(p)) } 17 | func (p Px) ApplyPadding(fw FlexWidget) { fw.Flex().StyleSetPadding(flex.EdgeAll, float32(p)) } 18 | func (p Px) ApplyMargin(fw FlexWidget) { fw.Flex().StyleSetMargin(flex.EdgeAll, float32(p)) } 19 | 20 | func (p Px) ApplyPosition(fw FlexWidget, edge flex.Edge) { 21 | fw.Flex().StyleSetPosition(edge, float32(p)) 22 | } 23 | -------------------------------------------------------------------------------- /render/buffer/buffer_suite_test.go: -------------------------------------------------------------------------------- 1 | package buffer_test 2 | 3 | import ( 4 | . "github.com/onsi/ginkgo/v2" 5 | . "github.com/onsi/gomega" 6 | 7 | "testing" 8 | 9 | "github.com/johanhenriksson/goworld/render/buffer" 10 | "github.com/johanhenriksson/goworld/render/device" 11 | 12 | "github.com/vkngwrapper/core/v2/core1_0" 13 | ) 14 | 15 | func TestBuffer(t *testing.T) { 16 | RegisterFailHandler(Fail) 17 | RunSpecs(t, "render/buffer") 18 | } 19 | 20 | type mockBuffer struct { 21 | size int 22 | } 23 | 24 | var _ buffer.T = (*mockBuffer)(nil) 25 | 26 | func (d *mockBuffer) Size() int { return d.size } 27 | func (d *mockBuffer) Ptr() core1_0.Buffer { return nil } 28 | func (d *mockBuffer) Memory() *device.Memory { return nil } 29 | func (d *mockBuffer) Address() device.Address { return device.InvalidAddress } 30 | func (d *mockBuffer) Read(offset int, data any) int { return 0 } 31 | func (d *mockBuffer) Write(offset int, data any) int { return 0 } 32 | 33 | func (d *mockBuffer) Flush() {} 34 | func (d *mockBuffer) Destroy() {} 35 | -------------------------------------------------------------------------------- /math/noise.go: -------------------------------------------------------------------------------- 1 | package math 2 | 3 | import ( 4 | opensimplex "github.com/ojrac/opensimplex-go" 5 | ) 6 | 7 | // Noise utility to sample simplex noise 8 | type Noise struct { 9 | Seed int 10 | Scale float32 11 | Freq float32 12 | 13 | simplex opensimplex.Noise 14 | } 15 | 16 | // NewNoise creates a new noise struct from a seed value and a frequency factor. 17 | func NewNoise(seed int, scale, freq float32) *Noise { 18 | return &Noise{ 19 | Seed: seed, 20 | Scale: scale, 21 | Freq: freq, 22 | 23 | simplex: opensimplex.New(int64(seed)), 24 | } 25 | } 26 | 27 | // Sample the 2D noise at a certain position 28 | func (n *Noise) Sample2(x, z int) float32 { 29 | fx, fz := float64(float32(x)*n.Freq), float64(float32(z)*n.Freq) 30 | return n.Scale * float32(n.simplex.Eval2(fx, fz)) 31 | } 32 | 33 | // Sample the 3D noise at a certain position 34 | func (n *Noise) Sample3(x, y, z int) float32 { 35 | // jeez 36 | fx, fy, fz := float64(float32(x)*n.Freq), float64(float32(y)*n.Freq), float64(float32(z)*n.Freq) 37 | return n.Scale * float32(n.simplex.Eval3(fx, fy, fz)) 38 | } 39 | -------------------------------------------------------------------------------- /render/buffer/allocator.go: -------------------------------------------------------------------------------- 1 | package buffer 2 | 3 | import ( 4 | "errors" 5 | 6 | "github.com/johanhenriksson/goworld/render/device" 7 | ) 8 | 9 | var ErrOutOfMemory = errors.New("out of memory") 10 | var ErrInvalidFree = errors.New("illegal free() call") 11 | 12 | type Allocator interface { 13 | Alloc(size int) (Block, error) 14 | Free(b Block) error 15 | } 16 | 17 | type Block struct { 18 | buffer T 19 | size int 20 | offset int 21 | } 22 | 23 | func EntireBuffer(buffer T) Block { 24 | return Block{buffer, buffer.Size(), 0} 25 | } 26 | 27 | func (b *Block) Buffer() T { return b.buffer } 28 | func (b *Block) Size() int { return b.size } 29 | func (b *Block) Offset() int { return b.offset } 30 | 31 | func (b *Block) Address() device.Address { 32 | return b.buffer.Address() + device.Address(b.offset) 33 | } 34 | 35 | func isPowerOfTwo(x uint) bool { 36 | return (x & (x - 1)) == 0 37 | } 38 | 39 | func Align(offset, align int) int { 40 | remainder := offset % align 41 | if remainder == 0 { 42 | return offset 43 | } 44 | return offset + (align - remainder) 45 | } 46 | -------------------------------------------------------------------------------- /render/color/color_suite_test.go: -------------------------------------------------------------------------------- 1 | package color_test 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/johanhenriksson/goworld/render/color" 7 | . "github.com/onsi/ginkgo/v2" 8 | . "github.com/onsi/gomega" 9 | ) 10 | 11 | func TestColor(t *testing.T) { 12 | RegisterFailHandler(Fail) 13 | RunSpecs(t, "render/color") 14 | } 15 | 16 | var _ = Describe("colors", func() { 17 | It("converts from hex codes", func() { 18 | c := color.Hex("#123456") 19 | Expect(c.R).To(BeNumerically("~", float32(0x12)/255.0)) 20 | Expect(c.G).To(BeNumerically("~", float32(0x34)/255.0)) 21 | Expect(c.B).To(BeNumerically("~", float32(0x56)/255.0)) 22 | 23 | a := color.Hex("#000000f0") 24 | Expect(a.A).To(BeNumerically("~", float32(0xF0)/255.0)) 25 | }) 26 | 27 | It("converts to hex codes", func() { 28 | c := color.RGB( 29 | float32(0x12)/255.0, 30 | float32(0x34)/255.0, 31 | float32(0x56)/255.0, 32 | ) 33 | Expect(c.Hex()).To(Equal("#123456")) 34 | 35 | a := color.RGBA( 36 | 0, 0, 0, 37 | float32(0xF0)/255.0, 38 | ) 39 | Expect(a.Hex()).To(Equal("#000000f0")) 40 | }) 41 | }) 42 | -------------------------------------------------------------------------------- /core/input/keys/statemap.go: -------------------------------------------------------------------------------- 1 | package keys 2 | 3 | type State interface { 4 | Handler 5 | 6 | Down(Code) bool 7 | Up(Code) bool 8 | 9 | Shift() bool 10 | Ctrl() bool 11 | Alt() bool 12 | Super() bool 13 | } 14 | 15 | type state map[Code]bool 16 | 17 | func NewState() State { 18 | return state{} 19 | } 20 | 21 | func (s state) KeyEvent(e Event) { 22 | if e.Action() == Press { 23 | s[e.Code()] = true 24 | } 25 | if e.Action() == Release { 26 | s[e.Code()] = false 27 | } 28 | } 29 | 30 | func (s state) Down(key Code) bool { 31 | if state, stored := s[key]; stored { 32 | return state 33 | } 34 | return false 35 | } 36 | 37 | func (s state) Up(key Code) bool { 38 | return !s.Down(key) 39 | } 40 | 41 | func (s state) Shift() bool { 42 | return s.Down(LeftShift) || s.Down(RightShift) 43 | } 44 | 45 | func (s state) Alt() bool { 46 | return s.Down(LeftAlt) || s.Down(RightAlt) 47 | } 48 | 49 | func (s state) Ctrl() bool { 50 | return s.Down(LeftControl) || s.Down(RightControl) 51 | } 52 | 53 | func (s state) Super() bool { 54 | return s.Down(LeftSuper) || s.Down(RightSuper) 55 | } 56 | -------------------------------------------------------------------------------- /util/align_test.go: -------------------------------------------------------------------------------- 1 | package util_test 2 | 3 | import ( 4 | . "github.com/onsi/ginkgo/v2" 5 | . "github.com/onsi/gomega" 6 | 7 | . "github.com/johanhenriksson/goworld/util" 8 | ) 9 | 10 | type AlignCase struct { 11 | Offset int 12 | Alignment int 13 | Expected int 14 | } 15 | 16 | var _ = Describe("align utils", func() { 17 | It("returns the expected alignment", func() { 18 | cases := []AlignCase{ 19 | {23, 64, 64}, 20 | {64, 64, 64}, 21 | {72, 64, 128}, 22 | } 23 | for _, testcase := range cases { 24 | actual := Align(testcase.Offset, testcase.Alignment) 25 | Expect(actual).To(Equal(testcase.Expected)) 26 | } 27 | }) 28 | 29 | It("returns errors for misaligned structs", func() { 30 | type FailingStruct struct { 31 | A bool 32 | B int 33 | } 34 | err := ValidateAlignment(FailingStruct{}) 35 | Expect(err).To(HaveOccurred()) 36 | }) 37 | 38 | It("validates aligned structs", func() { 39 | type PassingStruct struct { 40 | A int 41 | B float32 42 | } 43 | err := ValidateAlignment(PassingStruct{}) 44 | Expect(err).ToNot(HaveOccurred()) 45 | }) 46 | }) 47 | -------------------------------------------------------------------------------- /core/object/array.go: -------------------------------------------------------------------------------- 1 | package object 2 | 3 | type Array[T PropValue] struct { 4 | items []T 5 | } 6 | 7 | func NewArray[T PropValue]() Array[T] { 8 | return Array[T]{} 9 | } 10 | 11 | func (a *Array[T]) Length() int { 12 | return len(a.items) 13 | } 14 | 15 | func (a *Array[T]) Get(index int) T { 16 | return a.items[index] 17 | } 18 | 19 | func (a *Array[T]) GetAny(index int) any { 20 | return a.items[index] 21 | } 22 | 23 | func (a *Array[T]) Set(index int, value T) { 24 | a.items[index] = value 25 | } 26 | 27 | func (a *Array[T]) SetAny(index int, value any) { 28 | a.items[index] = value.(T) 29 | } 30 | 31 | func (a *Array[T]) Append(value T) { 32 | a.items = append(a.items, value) 33 | } 34 | 35 | func (a *Array[T]) AppendAny(value any) { 36 | a.items = append(a.items, value.(T)) 37 | } 38 | 39 | func (a *Array[T]) Delete(index int) { 40 | a.items = append(a.items[:index], a.items[index+1:]...) 41 | } 42 | 43 | func (a *Array[T]) Serialize(enc Encoder) error { 44 | return enc.Encode(a.items) 45 | } 46 | 47 | func (a *Array[T]) Deserialize(dec Decoder) error { 48 | return dec.Decode(&a.items) 49 | } 50 | -------------------------------------------------------------------------------- /engine/pass/pass.go: -------------------------------------------------------------------------------- 1 | package pass 2 | 3 | import ( 4 | "github.com/johanhenriksson/goworld/core/mesh" 5 | "github.com/johanhenriksson/goworld/engine/cache" 6 | "github.com/johanhenriksson/goworld/engine/uniform" 7 | "github.com/johanhenriksson/goworld/render/descriptor" 8 | "github.com/johanhenriksson/goworld/render/renderpass" 9 | "github.com/johanhenriksson/goworld/render/texture" 10 | ) 11 | 12 | const MainSubpass = renderpass.Name("main") 13 | 14 | type BasicDescriptors struct { 15 | descriptor.Set 16 | Camera *descriptor.Uniform[uniform.Camera] 17 | Objects *descriptor.Storage[uniform.Object] 18 | } 19 | 20 | func AssignMeshTextures(samplers cache.SamplerCache, msh mesh.Mesh, slots []texture.Slot) uniform.TextureIds { 21 | if len(slots) > uniform.MaxTextures { 22 | panic("too many textures") 23 | } 24 | textureIds := uniform.TextureIds{} 25 | for id, slot := range slots { 26 | ref := msh.Texture(slot) 27 | if ref != nil { 28 | handle, exists := samplers.TryFetch(ref) 29 | if exists { 30 | textureIds[id] = uniform.TextureId(handle.ID) 31 | } 32 | } 33 | } 34 | return textureIds 35 | } 36 | -------------------------------------------------------------------------------- /render/renderpass/attachment/attachment.go: -------------------------------------------------------------------------------- 1 | package attachment 2 | 3 | import ( 4 | "github.com/vkngwrapper/core/v2/core1_0" 5 | ) 6 | 7 | type Name string 8 | 9 | type T interface { 10 | Name() Name 11 | Image() Image 12 | Clear() core1_0.ClearValue 13 | Description() core1_0.AttachmentDescription 14 | Blend() Blend 15 | } 16 | 17 | type BlendOp struct { 18 | Operation core1_0.BlendOp 19 | SrcFactor core1_0.BlendFactor 20 | DstFactor core1_0.BlendFactor 21 | } 22 | 23 | type Blend struct { 24 | Enabled bool 25 | Color BlendOp 26 | Alpha BlendOp 27 | } 28 | 29 | type attachment struct { 30 | name Name 31 | image Image 32 | clear core1_0.ClearValue 33 | desc core1_0.AttachmentDescription 34 | blend Blend 35 | } 36 | 37 | func (a *attachment) Description() core1_0.AttachmentDescription { 38 | return a.desc 39 | } 40 | 41 | func (a *attachment) Name() Name { return a.name } 42 | func (a *attachment) Image() Image { return a.image } 43 | func (a *attachment) Clear() core1_0.ClearValue { return a.clear } 44 | func (a *attachment) Blend() Blend { return a.blend } 45 | -------------------------------------------------------------------------------- /assets/builtin/shaders/forward/forward.vs.glsl: -------------------------------------------------------------------------------- 1 | #version 450 2 | 3 | #include "lib/common.glsl" 4 | #include "lib/objects.glsl" 5 | 6 | OUT(0, flat uint, object) 7 | OUT(1, vec4, color) 8 | OUT(2, vec2, texcoord) 9 | OUT(3, vec3, view_position) 10 | OUT(4, vec3, world_normal) 11 | OUT(5, vec3, world_position) 12 | 13 | out gl_PerVertex { 14 | vec4 gl_Position; 15 | }; 16 | 17 | CAMERA(0, camera) 18 | OBJECT(1, object, gl_InstanceIndex) 19 | 20 | VERTEX_BUFFER(Vertex) 21 | INDEX_BUFFER(uint) 22 | 23 | void main() 24 | { 25 | out_object = get_object_index(); 26 | 27 | // load vertex data 28 | Vertex v = get_vertex_indexed(object.vertexPtr, object.indexPtr); 29 | 30 | // texture coords 31 | out_texcoord = v.tex; 32 | out_color = v.color; 33 | 34 | // gbuffer view position 35 | out_view_position = (camera.View * object.model * vec4(v.position, 1)).xyz; 36 | out_world_position = (object.model * vec4(v.position, 1)).xyz; 37 | 38 | // world normal 39 | out_world_normal = normalize((object.model * vec4(v.normal, 0)).xyz); 40 | 41 | // vertex clip space position 42 | gl_Position = camera.Proj * vec4(out_view_position, 1); 43 | } 44 | -------------------------------------------------------------------------------- /gui/widget/quad.go: -------------------------------------------------------------------------------- 1 | package widget 2 | 3 | import ( 4 | "fmt" 5 | "unsafe" 6 | 7 | "github.com/johanhenriksson/goworld/math/vec2" 8 | "github.com/johanhenriksson/goworld/render/color" 9 | ) 10 | 11 | type Quad struct { 12 | Min vec2.T 13 | Max vec2.T 14 | MinUV vec2.T 15 | MaxUV vec2.T 16 | Color [4]color.T 17 | ZIndex float32 18 | Radius float32 19 | Softness float32 20 | Border float32 21 | Texture uint32 22 | _padding [3]uint32 23 | } 24 | 25 | func init() { 26 | size := unsafe.Sizeof(Quad{}) 27 | if size != 128 { 28 | panic(fmt.Sprintf("Quad size is not 64 bytes, was %d", size)) 29 | } 30 | align := unsafe.Alignof(Quad{}) 31 | if align != 4 { 32 | panic(fmt.Sprintf("Quad alignment is not 4 bytes, was %d", align)) 33 | } 34 | } 35 | 36 | type QuadBuffer struct { 37 | Data []Quad 38 | } 39 | 40 | func NewQuadBuffer(capacity int) *QuadBuffer { 41 | return &QuadBuffer{ 42 | Data: make([]Quad, 0, capacity), 43 | } 44 | } 45 | 46 | func (qb *QuadBuffer) Push(quad Quad) { 47 | qb.Data = append(qb.Data, quad) 48 | } 49 | 50 | func (qb *QuadBuffer) Reset() { 51 | qb.Data = qb.Data[:0] 52 | } 53 | -------------------------------------------------------------------------------- /render/shader/details.go: -------------------------------------------------------------------------------- 1 | package shader 2 | 3 | import ( 4 | "encoding/json" 5 | 6 | "github.com/johanhenriksson/goworld/assets/fs" 7 | "github.com/johanhenriksson/goworld/render/texture" 8 | "github.com/johanhenriksson/goworld/render/types" 9 | ) 10 | 11 | type InputDetails struct { 12 | Index int 13 | Type string 14 | } 15 | 16 | type Details struct { 17 | Inputs map[string]InputDetails 18 | Bindings map[string]int 19 | Textures []texture.Slot 20 | } 21 | 22 | func (d *Details) ParseInputs() (Inputs, error) { 23 | inputs := Inputs{} 24 | for name, input := range d.Inputs { 25 | kind, err := types.TypeFromString(input.Type) 26 | if err != nil { 27 | return nil, err 28 | } 29 | inputs[name] = Input{ 30 | Index: input.Index, 31 | Type: kind, 32 | } 33 | } 34 | return inputs, nil 35 | } 36 | 37 | func ReadDetails(assets fs.Filesystem, path string) (*Details, error) { 38 | data, err := assets.Read(path) 39 | if err != nil { 40 | return nil, err 41 | } 42 | 43 | details := &Details{} 44 | err = json.Unmarshal(data, details) 45 | if err != nil { 46 | return nil, err 47 | } 48 | 49 | return details, nil 50 | } 51 | -------------------------------------------------------------------------------- /assets/builtin/shaders/lib/objects.glsl: -------------------------------------------------------------------------------- 1 | #define TEX_SLOT_DIFFUSE 0 2 | #define TEX_SLOT_NORMAL 1 3 | 4 | // Standard engine vertex format 5 | // Size: 48 bytes 6 | struct Vertex { 7 | vec3 position; 8 | vec3 normal; 9 | vec2 tex; 10 | vec4 color; 11 | }; 12 | 13 | struct Object { 14 | mat4 model; 15 | uint textures[MAX_TEXTURES]; 16 | 17 | uint64_t vertexPtr; 18 | uint64_t indexPtr; 19 | }; 20 | 21 | #define get_object_index() (gl_InstanceIndex) 22 | 23 | #define get_vertex_indexed(vertexPtr, indexPtr) (VertexBuffer(vertexPtr)[IndexBuffer(indexPtr)[gl_VertexIndex].index].vertex) 24 | #define get_vertex(vertexPtr) (VertexBuffer(vertexPtr)[gl_VertexIndex].vertex) 25 | 26 | #define VERTEX_BUFFER(VertexType) layout(buffer_reference, scalar, buffer_reference_align=4) readonly buffer VertexBuffer { VertexType vertex; }; 27 | #define INDEX_BUFFER(IndexType) layout(buffer_reference, scalar, buffer_reference_align=4) readonly buffer IndexBuffer { IndexType index; }; 28 | 29 | #define OBJECT(bind,name,handle) \ 30 | layout (scalar, binding = bind) readonly buffer uniform_ ## name { Object item[]; } _sb_ ## name; \ 31 | Object name = _sb_ ## name.item[handle]; 32 | -------------------------------------------------------------------------------- /render/descriptor/descriptor_struct_test.go: -------------------------------------------------------------------------------- 1 | package descriptor_test 2 | 3 | import ( 4 | . "github.com/johanhenriksson/goworld/render/descriptor" 5 | . "github.com/onsi/ginkgo/v2" 6 | . "github.com/onsi/gomega" 7 | 8 | "github.com/vkngwrapper/core/v2/core1_0" 9 | ) 10 | 11 | var _ = Describe("descriptor struct parsing", func() { 12 | type TestSet struct { 13 | Set 14 | Diffuse *Sampler 15 | } 16 | 17 | It("correctly parses valid structs", func() { 18 | set := TestSet{ 19 | Diffuse: &Sampler{ 20 | Stages: core1_0.StageAll, 21 | }, 22 | } 23 | desc, err := ParseDescriptorStruct(&set) 24 | Expect(err).ToNot(HaveOccurred()) 25 | Expect(desc).To(HaveLen(1), "expected to find diffuse descriptor") 26 | }) 27 | 28 | It("rejects unset descriptor fields", func() { 29 | set := TestSet{ 30 | Diffuse: nil, 31 | } 32 | _, err := ParseDescriptorStruct(&set) 33 | Expect(err).To(HaveOccurred()) 34 | }) 35 | 36 | It("rejects non-pointer fields", func() { 37 | type FailSet struct { 38 | Set 39 | Diffuse Sampler 40 | } 41 | set := FailSet{} 42 | _, err := ParseDescriptorStruct(&set) 43 | Expect(err).To(HaveOccurred()) 44 | }) 45 | }) 46 | -------------------------------------------------------------------------------- /render/pipeline/args.go: -------------------------------------------------------------------------------- 1 | package pipeline 2 | 3 | import ( 4 | "reflect" 5 | 6 | "github.com/johanhenriksson/goworld/render/renderpass" 7 | "github.com/johanhenriksson/goworld/render/shader" 8 | "github.com/johanhenriksson/goworld/render/vertex" 9 | 10 | "github.com/vkngwrapper/core/v2/core1_0" 11 | ) 12 | 13 | type Args struct { 14 | Pass *renderpass.Renderpass 15 | Subpass renderpass.Name 16 | Shader *shader.Shader 17 | Pointers vertex.Pointers 18 | 19 | Layout *Layout 20 | 21 | Primitive vertex.Primitive 22 | PolygonFillMode core1_0.PolygonMode 23 | CullMode vertex.CullMode 24 | 25 | DepthTest bool 26 | DepthWrite bool 27 | DepthClamp bool 28 | DepthFunc core1_0.CompareOp 29 | 30 | StencilTest bool 31 | } 32 | 33 | func (args *Args) defaults() { 34 | if args.DepthFunc == 0 { 35 | args.DepthFunc = core1_0.CompareOpLessOrEqual 36 | } 37 | if args.Primitive == 0 { 38 | args.Primitive = vertex.Triangles 39 | } 40 | } 41 | 42 | type PushConstant struct { 43 | Stages core1_0.ShaderStageFlags 44 | Type any 45 | } 46 | 47 | func (p *PushConstant) Size() int { 48 | t := reflect.TypeOf(p.Type) 49 | return int(t.Size()) 50 | } 51 | -------------------------------------------------------------------------------- /render/color/palette.go: -------------------------------------------------------------------------------- 1 | package color 2 | 3 | import ( 4 | "github.com/johanhenriksson/goworld/math/random" 5 | ) 6 | 7 | // A Palette is a list of colors 8 | type Palette []T 9 | 10 | // RawPalette creates a palette from a list of hex integers 11 | func RawPalette(colors ...int) Palette { 12 | palette := make(Palette, len(colors)) 13 | for i, clr := range colors { 14 | palette[i] = RGBA( 15 | float32((clr>>16)&0xFF)/255.0, 16 | float32((clr>>8)&0xFF)/255.0, 17 | float32((clr>>0)&0xFF)/255.0, 18 | 1.0) 19 | } 20 | return palette 21 | } 22 | 23 | // DefaultPalette https://lospec.com/palette-list/broken-facility 24 | var DefaultPalette = RawPalette( 25 | 0x24211e, 0x898377, 0xada99e, 0xcccac4, 0xf9f8f7, 26 | 0x563735, 0x835748, 0xa37254, 0xb59669, 0xcab880, 27 | 0x4d1c2d, 0x98191e, 0xd12424, 0xdd4b63, 0xf379e2, 28 | 0xc86826, 0xd8993f, 0xe8c04f, 0xf2db89, 0xf8f1c6, 29 | 0x17601f, 0x488c36, 0x7abd40, 0xa4cf41, 0xcdde5e, 30 | 0x5044ba, 0x5e9ccc, 0x7fc6ce, 0x9de2df, 0xcaf1ea, 31 | 0x202c56, 0x3f2d6d, 0x772673, 0xb9284f, 0xcb5135, 32 | 0xeda7d8, 0xf3bedd, 0xdbebeb, 0xe9dde8, 0xd5c4df) 33 | 34 | func Random() T { 35 | return random.Choice(DefaultPalette) 36 | } 37 | -------------------------------------------------------------------------------- /physics/collision_mesh.go: -------------------------------------------------------------------------------- 1 | package physics 2 | 3 | import ( 4 | "github.com/johanhenriksson/goworld/math/vec3" 5 | "github.com/johanhenriksson/goworld/render/vertex" 6 | ) 7 | 8 | // Vertex format used by physics meshes 9 | type Vertex struct { 10 | vec3.T `vtx:"position,float,3"` 11 | } 12 | 13 | func (v Vertex) Position() vec3.T { 14 | return v.T 15 | } 16 | 17 | func CollisionMesh(mesh vertex.Mesh) vertex.Mesh { 18 | // generate collision mesh 19 | // todo: use greedy face optimization 20 | 21 | indexMap := make(map[vec3.T]uint32, mesh.IndexCount()) 22 | vertexdata := make([]Vertex, 0, mesh.VertexCount()/4) 23 | indexdata := make([]uint32, 0, mesh.IndexCount()) 24 | for p := range mesh.Positions() { 25 | // check if the vertex position already has an index 26 | // todo: tolerance 27 | index, exists := indexMap[p] 28 | if !exists { 29 | // create a new index from the vertex 30 | index = uint32(len(vertexdata)) 31 | vertexdata = append(vertexdata, Vertex{p}) 32 | indexMap[p] = index 33 | } 34 | // store vertex index 35 | indexdata = append(indexdata, index) 36 | } 37 | 38 | return vertex.NewTriangles[Vertex, uint32](mesh.Key(), vertexdata, indexdata) 39 | } 40 | -------------------------------------------------------------------------------- /geometry/lines/immediate.go: -------------------------------------------------------------------------------- 1 | package lines 2 | 3 | import ( 4 | "github.com/johanhenriksson/goworld/core/mesh" 5 | "github.com/johanhenriksson/goworld/core/object" 6 | "github.com/johanhenriksson/goworld/math/vec3" 7 | "github.com/johanhenriksson/goworld/render/color" 8 | ) 9 | 10 | var Debug = &DebugLines{} 11 | 12 | type DebugLines struct { 13 | enabled bool 14 | frame int 15 | meshes []*Lines 16 | } 17 | 18 | func (li *DebugLines) Setup(frames int) { 19 | li.meshes = make([]*Lines, frames) 20 | for i := range li.meshes { 21 | li.meshes[i] = New(object.GlobalPool, Args{}) 22 | } 23 | li.enabled = true 24 | } 25 | 26 | func (li *DebugLines) Add(from, to vec3.T, clr color.T) { 27 | if !li.enabled { 28 | return 29 | } 30 | mesh := li.meshes[li.frame] 31 | mesh.Lines = append(mesh.Lines, Line{ 32 | Start: from, 33 | End: to, 34 | Color: clr, 35 | }) 36 | } 37 | 38 | func (li *DebugLines) Fetch() mesh.Mesh { 39 | // build mesh for current frame 40 | mesh := li.meshes[li.frame] 41 | mesh.Refresh() 42 | 43 | // set next frame 44 | li.frame = (li.frame + 1) % len(li.meshes) 45 | 46 | // prepare next mesh 47 | nextMesh := li.meshes[li.frame] 48 | nextMesh.Clear() 49 | 50 | return mesh 51 | } 52 | -------------------------------------------------------------------------------- /gui/fragment.go: -------------------------------------------------------------------------------- 1 | package gui 2 | 3 | import ( 4 | "github.com/johanhenriksson/goworld/core/object" 5 | "github.com/johanhenriksson/goworld/gui/node" 6 | ) 7 | 8 | type FragmentPosition int 9 | 10 | const FragmentLast FragmentPosition = 0 11 | const FragmentFirst FragmentPosition = 1 12 | 13 | // A Fragment contains a UI subtree to be rendered as a child of a given element 14 | type Fragment interface { 15 | object.Component 16 | Render() node.T 17 | 18 | Slot() string 19 | Position() FragmentPosition 20 | } 21 | 22 | type FragmentArgs struct { 23 | Slot string 24 | Position FragmentPosition 25 | Render node.RenderFunc 26 | } 27 | 28 | type fragment struct { 29 | object.Component 30 | FragmentArgs 31 | } 32 | 33 | func NewFragment(pool object.Pool, args FragmentArgs) Fragment { 34 | return object.NewComponent(pool, &fragment{ 35 | FragmentArgs: args, 36 | }) 37 | } 38 | 39 | func (f *fragment) Name() string { return "UIFragment" } 40 | 41 | func (f *fragment) Slot() string { 42 | return f.FragmentArgs.Slot 43 | } 44 | 45 | func (f *fragment) Position() FragmentPosition { 46 | return f.FragmentArgs.Position 47 | } 48 | 49 | func (f *fragment) Render() node.T { 50 | return f.FragmentArgs.Render() 51 | } 52 | -------------------------------------------------------------------------------- /gui/style/rect.go: -------------------------------------------------------------------------------- 1 | package style 2 | 3 | import "github.com/kjk/flex" 4 | 5 | type Rect struct { 6 | Left float32 7 | Right float32 8 | Top float32 9 | Bottom float32 10 | } 11 | 12 | func RectAll(v float32) Rect { 13 | return Rect{ 14 | Left: v, 15 | Right: v, 16 | Top: v, 17 | Bottom: v, 18 | } 19 | } 20 | 21 | func RectXY(x, y float32) Rect { 22 | return Rect{ 23 | Left: x, 24 | Right: x, 25 | Top: y, 26 | Bottom: y, 27 | } 28 | } 29 | 30 | func RectX(v float32) Rect { 31 | return Rect{ 32 | Left: v, 33 | Right: v, 34 | } 35 | } 36 | 37 | func RectY(v float32) Rect { 38 | return Rect{ 39 | Top: v, 40 | Bottom: v, 41 | } 42 | } 43 | 44 | func (p Rect) ApplyPadding(fw FlexWidget) { 45 | fw.Flex().StyleSetPadding(flex.EdgeLeft, p.Left) 46 | fw.Flex().StyleSetPadding(flex.EdgeRight, p.Right) 47 | fw.Flex().StyleSetPadding(flex.EdgeTop, p.Top) 48 | fw.Flex().StyleSetPadding(flex.EdgeBottom, p.Bottom) 49 | } 50 | 51 | func (p Rect) ApplyMargin(fw FlexWidget) { 52 | fw.Flex().StyleSetMargin(flex.EdgeLeft, p.Left) 53 | fw.Flex().StyleSetMargin(flex.EdgeRight, p.Right) 54 | fw.Flex().StyleSetMargin(flex.EdgeTop, p.Top) 55 | fw.Flex().StyleSetMargin(flex.EdgeBottom, p.Bottom) 56 | } 57 | -------------------------------------------------------------------------------- /core/object/reference_test.go: -------------------------------------------------------------------------------- 1 | package object 2 | 3 | import ( 4 | . "github.com/onsi/ginkgo/v2" 5 | . "github.com/onsi/gomega" 6 | ) 7 | 8 | type ObjectWithReference struct { 9 | Object 10 | Reference Ref[Object] 11 | } 12 | 13 | var _ = Describe("", func() { 14 | var pool Pool 15 | var a *ObjectWithReference 16 | var b Object 17 | 18 | BeforeEach(func() { 19 | Register[*ObjectWithReference](Type{}) 20 | 21 | pool = NewPool() 22 | a = NewObject(pool, "a", &ObjectWithReference{}) 23 | b = Empty(pool, "b") 24 | Attach(a, b) 25 | a.Reference.Set(b) 26 | }) 27 | 28 | It("serializes empty references", func() { 29 | obj := NewObject(pool, "obj", &ObjectWithReference{}) 30 | out := Copy(pool, obj) 31 | Expect(out).ToNot(BeNil()) 32 | Expect(out.Len()).To(Equal(0)) 33 | ref, ok := out.Reference.Get() 34 | Expect(ok).To(BeFalse()) 35 | Expect(ref).To(BeNil()) 36 | }) 37 | 38 | It("serializes correctly", func() { 39 | sa := Copy(pool, a) 40 | 41 | Expect(sa.ID()).ToNot(Equal(a.ID())) 42 | Expect(sa.Len()).To(Equal(1)) 43 | 44 | sbref, ok := sa.Reference.Get() 45 | Expect(ok).To(BeTrue(), "handle should be valid") 46 | 47 | sb := sa.Child(0).(Object) 48 | Expect(sb.ID()).To(Equal(sbref.ID())) 49 | }) 50 | }) 51 | -------------------------------------------------------------------------------- /render/noise/white_noise.go: -------------------------------------------------------------------------------- 1 | package noise 2 | 3 | import ( 4 | "fmt" 5 | "math/rand" 6 | 7 | "github.com/johanhenriksson/goworld/render/image" 8 | "github.com/johanhenriksson/goworld/render/texture" 9 | ) 10 | 11 | type WhiteNoise struct { 12 | Width int 13 | Height int 14 | 15 | key string 16 | } 17 | 18 | func NewWhiteNoise(width, height int) *WhiteNoise { 19 | return &WhiteNoise{ 20 | key: fmt.Sprintf("noise-white-%dx%d", width, height), 21 | Width: width, 22 | Height: height, 23 | } 24 | } 25 | 26 | func (n *WhiteNoise) Key() string { return n.key } 27 | func (n *WhiteNoise) Version() int { return 1 } 28 | 29 | func (n *WhiteNoise) ImageData() *image.Data { 30 | size := n.Width * n.Height 31 | buffer := make([]byte, 4*size) 32 | for i := 0; i < len(buffer); i += 4 { 33 | v := uint8(rand.Intn(255)) 34 | buffer[i+0] = v 35 | buffer[i+1] = v 36 | buffer[i+2] = v 37 | buffer[i+3] = 255 38 | } 39 | return &image.Data{ 40 | Width: n.Width, 41 | Height: n.Height, 42 | Format: image.FormatRGBA8Unorm, 43 | Buffer: buffer, 44 | } 45 | } 46 | 47 | func (n *WhiteNoise) TextureArgs() texture.Args { 48 | return texture.Args{ 49 | Filter: texture.FilterNearest, 50 | Wrap: texture.WrapRepeat, 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /engine/uniform/light.go: -------------------------------------------------------------------------------- 1 | package uniform 2 | 3 | import ( 4 | "fmt" 5 | "structs" 6 | "unsafe" 7 | 8 | "github.com/johanhenriksson/goworld/math/mat4" 9 | "github.com/johanhenriksson/goworld/math/vec4" 10 | "github.com/johanhenriksson/goworld/render/color" 11 | ) 12 | 13 | const ShadowCascades = 4 14 | const LightPadding = 74 15 | 16 | type Light struct { 17 | _ structs.HostLayout 18 | 19 | ViewProj [ShadowCascades]mat4.T 20 | Shadowmap [ShadowCascades]uint32 21 | Distance [ShadowCascades]float32 22 | Color color.T 23 | Position vec4.T 24 | Type uint32 25 | Intensity float32 26 | Range float32 27 | Falloff float32 28 | } 29 | 30 | type LightSettings struct { 31 | _ structs.HostLayout 32 | 33 | AmbientColor color.T 34 | AmbientIntensity float32 35 | Count int32 36 | ShadowSamples int32 37 | ShadowSampleRadius float32 38 | ShadowBias float32 39 | NormalOffset float32 40 | _padding [LightPadding]uint32 41 | } 42 | 43 | func init() { 44 | lightSz := unsafe.Sizeof(Light{}) 45 | settingSz := unsafe.Sizeof(LightSettings{}) 46 | if lightSz != settingSz { 47 | panic(fmt.Sprintf("Light (%d) and LightSetting (%d) must have equal size", lightSz, settingSz)) 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /gui/style/percent.go: -------------------------------------------------------------------------------- 1 | package style 2 | 3 | import "github.com/kjk/flex" 4 | 5 | // Pct is a percentage value 6 | type Pct float32 7 | 8 | func (p Pct) ApplyBasis(fw FlexWidget) { fw.Flex().StyleSetFlexBasisPercent(float32(p)) } 9 | func (p Pct) ApplyWidth(fw FlexWidget) { fw.Flex().StyleSetWidthPercent(float32(p)) } 10 | func (p Pct) ApplyMinWidth(fw FlexWidget) { fw.Flex().StyleSetMinWidthPercent(float32(p)) } 11 | func (p Pct) ApplyMaxWidth(fw FlexWidget) { fw.Flex().StyleSetMaxWidthPercent(float32(p)) } 12 | func (p Pct) ApplyHeight(fw FlexWidget) { fw.Flex().StyleSetHeightPercent(float32(p)) } 13 | func (p Pct) ApplyMaxHeight(fw FlexWidget) { fw.Flex().StyleSetMaxHeightPercent(float32(p)) } 14 | func (p Pct) ApplyMinHeight(fw FlexWidget) { fw.Flex().StyleSetMinHeightPercent(float32(p)) } 15 | func (p Pct) ApplyPadding(fw FlexWidget) { fw.Flex().StyleSetPaddingPercent(flex.EdgeAll, float32(p)) } 16 | func (p Pct) ApplyMargin(fw FlexWidget) { fw.Flex().StyleSetMarginPercent(flex.EdgeAll, float32(p)) } 17 | 18 | func (p Pct) ApplyPosition(fw FlexWidget, edge flex.Edge) { 19 | fw.Flex().StyleSetPositionPercent(edge, float32(p)) 20 | } 21 | 22 | func (p Pct) ApplyLineHeight(fw FontWidget) { 23 | fw.SetLineHeight(float32(p / 100)) 24 | } 25 | -------------------------------------------------------------------------------- /physics/sphere.go: -------------------------------------------------------------------------------- 1 | package physics 2 | 3 | import ( 4 | "unsafe" 5 | 6 | "github.com/johanhenriksson/goworld/core/object" 7 | ) 8 | 9 | func init() { 10 | object.Register[*Sphere](object.Type{ 11 | Name: "Sphere Collider", 12 | Path: []string{"Physics"}, 13 | Create: func(ctx object.Pool) (object.Component, error) { 14 | return NewSphere(ctx, 1), nil 15 | }, 16 | }) 17 | } 18 | 19 | type Sphere struct { 20 | kind ShapeType 21 | *Collider 22 | 23 | Radius object.Property[float32] 24 | } 25 | 26 | var _ = checkShape(NewSphere(object.GlobalPool, 1)) 27 | 28 | func NewSphere(pool object.Pool, radius float32) *Sphere { 29 | sphere := &Sphere{ 30 | kind: SphereShape, 31 | Radius: object.NewProperty[float32](radius), 32 | } 33 | sphere.Collider = newCollider(pool, sphere, true) 34 | 35 | // resize shape when radius is modified 36 | sphere.Radius.OnChange.Subscribe(func(t float32) { 37 | sphere.refresh() 38 | }) 39 | 40 | return sphere 41 | } 42 | 43 | func (s *Sphere) colliderCreate() shapeHandle { 44 | return shape_new_sphere(unsafe.Pointer(s), s.Radius.Get()) 45 | } 46 | 47 | func (s *Sphere) colliderIsCompound() bool { return false } 48 | 49 | func (s *Sphere) colliderRefresh() {} 50 | func (s *Sphere) colliderDestroy() {} 51 | -------------------------------------------------------------------------------- /render/renderpass/attachment/blend.go: -------------------------------------------------------------------------------- 1 | package attachment 2 | 3 | import ( 4 | "github.com/vkngwrapper/core/v2/core1_0" 5 | ) 6 | 7 | var BlendMix = Blend{ 8 | Enabled: true, 9 | Color: BlendOp{ 10 | Operation: core1_0.BlendOpAdd, 11 | SrcFactor: core1_0.BlendFactorSrcAlpha, 12 | DstFactor: core1_0.BlendFactorOneMinusSrcAlpha, 13 | }, 14 | Alpha: BlendOp{ 15 | Operation: core1_0.BlendOpAdd, 16 | SrcFactor: core1_0.BlendFactorOne, 17 | DstFactor: core1_0.BlendFactorZero, 18 | }, 19 | } 20 | 21 | var BlendAdditive = Blend{ 22 | Enabled: true, 23 | Color: BlendOp{ 24 | Operation: core1_0.BlendOpAdd, 25 | SrcFactor: core1_0.BlendFactorOne, 26 | DstFactor: core1_0.BlendFactorOne, 27 | }, 28 | Alpha: BlendOp{ 29 | Operation: core1_0.BlendOpAdd, 30 | SrcFactor: core1_0.BlendFactorOne, 31 | DstFactor: core1_0.BlendFactorZero, 32 | }, 33 | } 34 | 35 | var BlendMultiply = Blend{ 36 | Enabled: true, 37 | Color: BlendOp{ 38 | Operation: core1_0.BlendOpAdd, 39 | SrcFactor: core1_0.BlendFactorSrcAlpha, 40 | DstFactor: core1_0.BlendFactorOneMinusSrcAlpha, 41 | }, 42 | Alpha: BlendOp{ 43 | Operation: core1_0.BlendOpAdd, 44 | SrcFactor: core1_0.BlendFactorSrcAlpha, 45 | DstFactor: core1_0.BlendFactorOneMinusSrcAlpha, 46 | }, 47 | } 48 | -------------------------------------------------------------------------------- /render/sync/semaphore.go: -------------------------------------------------------------------------------- 1 | package sync 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/johanhenriksson/goworld/render/device" 7 | 8 | "github.com/vkngwrapper/core/v2/core1_0" 9 | "github.com/vkngwrapper/core/v2/driver" 10 | ) 11 | 12 | type Semaphore struct { 13 | device *device.Device 14 | ptr core1_0.Semaphore 15 | name string 16 | } 17 | 18 | func NewSemaphore(dev *device.Device, name string) *Semaphore { 19 | ptr, _, err := dev.Ptr().CreateSemaphore(nil, core1_0.SemaphoreCreateInfo{}) 20 | if err != nil { 21 | panic(err) 22 | } 23 | dev.SetDebugObjectName(driver.VulkanHandle(ptr.Handle()), core1_0.ObjectTypeSemaphore, name) 24 | 25 | return &Semaphore{ 26 | device: dev, 27 | ptr: ptr, 28 | name: name, 29 | } 30 | } 31 | 32 | func (s Semaphore) Ptr() core1_0.Semaphore { 33 | return s.ptr 34 | } 35 | 36 | func (s *Semaphore) Name() string { 37 | return s.name 38 | } 39 | 40 | func (s *Semaphore) Destroy() { 41 | if s.ptr != nil { 42 | s.ptr.Destroy(nil) 43 | s.ptr = nil 44 | } 45 | } 46 | 47 | func NewSemaphoreArray(dev *device.Device, name string, count int) []*Semaphore { 48 | arr := make([]*Semaphore, count) 49 | for i := range arr { 50 | arr[i] = NewSemaphore(dev, fmt.Sprintf("%s:%d", name, i)) 51 | } 52 | return arr 53 | } 54 | -------------------------------------------------------------------------------- /gui/style/column_test.go: -------------------------------------------------------------------------------- 1 | package style_test 2 | 3 | import ( 4 | . "github.com/johanhenriksson/goworld/gui/style" 5 | . "github.com/onsi/ginkgo/v2" 6 | . "github.com/onsi/gomega" 7 | 8 | "github.com/johanhenriksson/goworld/gui/widget" 9 | "github.com/johanhenriksson/goworld/gui/widget/rect" 10 | "github.com/johanhenriksson/goworld/math/vec2" 11 | 12 | "github.com/kjk/flex" 13 | ) 14 | 15 | var _ = Describe("column layout", func() { 16 | It("correctly sizes elements in a column layout", func() { 17 | 18 | a := rect.Create("a", rect.Props{ 19 | Style: rect.Style{ 20 | Height: Pct(50), 21 | }, 22 | }) 23 | b := rect.Create("b", rect.Props{ 24 | Style: rect.Style{ 25 | Height: Pct(50), 26 | }, 27 | }) 28 | parent := rect.Create("parent", rect.Props{ 29 | Style: rect.Style{ 30 | Layout: Column{}, 31 | }, 32 | }) 33 | parent.SetChildren([]widget.T{a, b}) 34 | root := parent.Flex() 35 | flex.CalculateLayout(root, 100, 100, flex.DirectionLTR) 36 | 37 | Expect(parent.Size()).To(Equal(vec2.New(100, 100))) 38 | Expect(a.Size()).To(Equal(vec2.New(100, 50))) 39 | Expect(a.Position()).To(Equal(vec2.New(0, 0))) 40 | Expect(b.Size()).To(Equal(vec2.New(100, 50))) 41 | Expect(b.Position()).To(Equal(vec2.New(0, 50))) 42 | }) 43 | }) 44 | -------------------------------------------------------------------------------- /engine/uniform/camera.go: -------------------------------------------------------------------------------- 1 | package uniform 2 | 3 | import ( 4 | "structs" 5 | 6 | "github.com/johanhenriksson/goworld/core/draw" 7 | "github.com/johanhenriksson/goworld/math/mat4" 8 | "github.com/johanhenriksson/goworld/math/vec2" 9 | "github.com/johanhenriksson/goworld/math/vec4" 10 | ) 11 | 12 | type Camera struct { 13 | _ structs.HostLayout 14 | 15 | Proj mat4.T 16 | View mat4.T 17 | ViewProj mat4.T 18 | ProjInv mat4.T 19 | ViewInv mat4.T 20 | ViewProjInv mat4.T 21 | Eye vec4.T 22 | Forward vec4.T 23 | Viewport vec2.T 24 | Delta float32 25 | Time float32 26 | } 27 | 28 | func CameraFromArgs(args draw.Args) Camera { 29 | return Camera{ 30 | Proj: args.Camera.Proj, 31 | View: args.Camera.View, 32 | ViewProj: args.Camera.ViewProj, 33 | ProjInv: args.Camera.Proj.Invert(), 34 | ViewInv: args.Camera.ViewInv, 35 | ViewProjInv: args.Camera.ViewProjInv, 36 | Eye: vec4.Extend(args.Camera.Position, 0), 37 | Forward: vec4.Extend(args.Camera.Forward, 0), 38 | Viewport: vec2.NewI(args.Camera.Viewport.Width, args.Camera.Viewport.Height), 39 | 40 | // todo: timing values should not be part of the camera 41 | 42 | Delta: args.Delta, 43 | Time: args.Time, 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /editor/inspector.go: -------------------------------------------------------------------------------- 1 | package editor 2 | 3 | import ( 4 | "github.com/johanhenriksson/goworld/core/object" 5 | "github.com/johanhenriksson/goworld/editor/propedit" 6 | "github.com/johanhenriksson/goworld/gui/node" 7 | "github.com/johanhenriksson/goworld/gui/style" 8 | "github.com/johanhenriksson/goworld/gui/widget/label" 9 | "github.com/johanhenriksson/goworld/gui/widget/rect" 10 | "github.com/johanhenriksson/goworld/render/color" 11 | ) 12 | 13 | func Inspector(target object.Component, propEditors ...node.T) node.T { 14 | title := "Component: " 15 | if _, isObject := target.(object.Object); isObject { 16 | title = "Object: " 17 | } 18 | 19 | key := object.Key("inspector", target) 20 | children := make([]node.T, 0, 4+len(propEditors)) 21 | children = append(children, []node.T{ 22 | label.New("title", label.Props{ 23 | Text: title + target.Name(), 24 | Style: label.Style{ 25 | Font: style.Font{ 26 | Size: 16, 27 | }, 28 | }, 29 | }), 30 | rect.New("underline", rect.Props{ 31 | Style: rect.Style{ 32 | Width: style.Pct(100), 33 | Border: style.Border{ 34 | Width: style.Px(0.5), 35 | Color: color.White, 36 | }, 37 | }, 38 | }), 39 | }...) 40 | children = append(children, propEditors...) 41 | return propedit.Container(key, children) 42 | } 43 | -------------------------------------------------------------------------------- /render/command/work_channel.go: -------------------------------------------------------------------------------- 1 | package command 2 | 3 | type WorkFunc func() 4 | 5 | type Channel struct { 6 | buffer int 7 | work chan WorkFunc 8 | } 9 | 10 | func NewChannel(buffer int) *Channel { 11 | return &Channel{ 12 | buffer: buffer, 13 | work: make(chan WorkFunc, buffer), 14 | } 15 | } 16 | 17 | func (ch *Channel) Recv() <-chan WorkFunc { 18 | return ch.work 19 | } 20 | 21 | // Invoke schedules a callback to be called from the worker thread 22 | func (ch *Channel) Invoke(callback WorkFunc) { 23 | ch.work <- callback 24 | } 25 | 26 | // InvokeSync schedules a callback to be called on the worker thread, 27 | // and blocks until the callback is finished. 28 | func (ch *Channel) InvokeSync(callback WorkFunc) { 29 | done := make(chan struct{}) 30 | ch.Invoke(func() { 31 | callback() 32 | close(done) 33 | }) 34 | <-done 35 | } 36 | 37 | // Aborts the worker, cancelling any pending work. 38 | func (ch *Channel) Close() { 39 | close(ch.work) 40 | } 41 | 42 | // Stop the worker and release any resources. Blocks until all work in completed. 43 | func (ch *Channel) Stop() { 44 | ch.InvokeSync(func() { 45 | close(ch.work) 46 | }) 47 | } 48 | 49 | // Flush blocks the caller until all pending work is completed 50 | func (ch *Channel) Flush() { 51 | ch.InvokeSync(func() {}) 52 | } 53 | -------------------------------------------------------------------------------- /assets/builtin/shaders/forward/sprite.fs.glsl: -------------------------------------------------------------------------------- 1 | #version 450 2 | 3 | #include "lib/common.glsl" 4 | #include "lib/objects.glsl" 5 | #include "lib/lighting.glsl" 6 | 7 | IN(0, flat uint, object) 8 | IN(1, vec4, color) 9 | IN(2, vec2, texcoord) 10 | IN(3, vec3, view_position) 11 | IN(4, vec3, world_normal) 12 | IN(5, vec3, world_position) 13 | 14 | // Return Output 15 | OUT(0, vec4, diffuse) 16 | 17 | CAMERA(0, camera) 18 | OBJECT(1, object, in_object) 19 | LIGHTS(2, lights) 20 | SAMPLER_ARRAY(3, textures) 21 | 22 | void main() 23 | { 24 | uint texture0 = object.textures[TEX_SLOT_DIFFUSE]; 25 | vec4 albedo = texture_array(textures, texture0, in_texcoord); 26 | if (albedo.a < 0.01) { 27 | discard; 28 | } 29 | 30 | // apply tint 31 | vec3 tint = mix(vec3(1), in_color.rgb, in_color.a); 32 | albedo = vec4(albedo.rgb * tint, albedo.a); 33 | 34 | // calculate lighting 35 | int lightCount = lights.settings.Count; 36 | vec3 lightColor = ambientLight(lights.settings, 1); 37 | for(int i = 0; i < lightCount; i++) { 38 | lightColor += calculateLightColor(lights.item[i], in_world_position, in_world_normal, in_view_position.z, lights.settings); 39 | } 40 | 41 | // gamma correct & write fragment 42 | vec3 linearColor = pow(albedo.rgb, vec3(gamma)); 43 | out_diffuse = vec4(linearColor * lightColor, albedo.a); 44 | } 45 | -------------------------------------------------------------------------------- /gui/style/row_test.go: -------------------------------------------------------------------------------- 1 | package style_test 2 | 3 | import ( 4 | . "github.com/johanhenriksson/goworld/gui/style" 5 | . "github.com/onsi/ginkgo/v2" 6 | . "github.com/onsi/gomega" 7 | 8 | "github.com/johanhenriksson/goworld/gui/widget" 9 | "github.com/johanhenriksson/goworld/gui/widget/rect" 10 | "github.com/johanhenriksson/goworld/math/vec2" 11 | 12 | "github.com/kjk/flex" 13 | ) 14 | 15 | var _ = Describe("row layout", func() { 16 | It("correctly sizes elements in a row layout", func() { 17 | a := rect.Create("a", rect.Props{ 18 | Style: rect.Style{ 19 | Width: Px(20), 20 | Height: Px(20), 21 | }, 22 | }) 23 | b := rect.Create("b", rect.Props{ 24 | Style: rect.Style{ 25 | Width: Px(20), 26 | Height: Px(20), 27 | }, 28 | }) 29 | parent := rect.Create("parent", rect.Props{ 30 | Style: rect.Style{ 31 | Layout: Row{}, 32 | }, 33 | }) 34 | parent.SetChildren([]widget.T{a, b}) 35 | root := parent.Flex() 36 | flex.CalculateLayout(root, flex.Undefined, flex.Undefined, flex.DirectionLTR) 37 | 38 | Expect(parent.Size()).To(Equal(vec2.New(40, 20))) 39 | Expect(a.Size()).To(Equal(vec2.New(20, 20))) 40 | Expect(b.Size()).To(Equal(vec2.New(20, 20))) 41 | Expect(a.Position()).To(Equal(vec2.New(0, 0))) 42 | Expect(b.Position()).To(Equal(vec2.New(20, 0))) 43 | }) 44 | }) 45 | -------------------------------------------------------------------------------- /gui/widget/menu/menu_test.go: -------------------------------------------------------------------------------- 1 | package menu_test 2 | 3 | import ( 4 | . "github.com/onsi/ginkgo/v2" 5 | . "github.com/onsi/gomega" 6 | 7 | "testing" 8 | 9 | "github.com/johanhenriksson/goworld/gui/node" 10 | "github.com/johanhenriksson/goworld/gui/widget/menu" 11 | "github.com/johanhenriksson/goworld/gui/widget/rect" 12 | "github.com/johanhenriksson/goworld/render/color" 13 | ) 14 | 15 | func TestMenu(t *testing.T) { 16 | RegisterFailHandler(Fail) 17 | RunSpecs(t, "gui/widget/menu") 18 | } 19 | 20 | var _ = Describe("Menu widget", func() { 21 | It("renders", func() { 22 | render := func() node.T { 23 | return rect.New("gui", rect.Props{ 24 | Children: []node.T{ 25 | makeMenu(), 26 | }, 27 | }) 28 | } 29 | 30 | tree := render() 31 | Expect(tree.Children()).To(HaveLen(1)) 32 | }) 33 | }) 34 | 35 | func makeMenu() node.T { 36 | return menu.Menu("gui-menu", menu.Props{ 37 | Style: menu.Style{ 38 | Color: color.RGB(0.76, 0.76, 0.76), 39 | HoverColor: color.RGB(0.85, 0.85, 0.85), 40 | TextColor: color.Black, 41 | }, 42 | 43 | Items: []menu.ItemProps{ 44 | { 45 | Key: "menu-file", 46 | Title: "File", 47 | Items: []menu.ItemProps{ 48 | { 49 | Key: "file-exit", 50 | Title: "Exit", 51 | }, 52 | }, 53 | }, 54 | }, 55 | }) 56 | } 57 | -------------------------------------------------------------------------------- /core/object/property_test.go: -------------------------------------------------------------------------------- 1 | package object_test 2 | 3 | import ( 4 | . "github.com/onsi/ginkgo/v2" 5 | . "github.com/onsi/gomega" 6 | 7 | "github.com/johanhenriksson/goworld/core/object" 8 | ) 9 | 10 | type WithProp struct { 11 | object.Component 12 | Prop object.Property[int] 13 | } 14 | 15 | var _ = Describe("Properties", func() { 16 | var obj *WithProp 17 | pool := object.NewPool() 18 | 19 | BeforeEach(func() { 20 | obj = object.NewComponent(pool, &WithProp{ 21 | Prop: object.NewProperty(1337), 22 | }) 23 | }) 24 | 25 | Context("basics", func() { 26 | It("stores data", func() { 27 | Expect(obj.Prop.Get()).To(Equal(1337), "wrong default value") 28 | 29 | obj.Prop.Set(42) 30 | Expect(obj.Prop.Get()).To(Equal(42), "setter should update stored value") 31 | }) 32 | 33 | It("raises an OnChanged event", func() { 34 | var event int 35 | obj.Prop.OnChange.Subscribe(func(i int) { 36 | event = i 37 | }) 38 | 39 | obj.Prop.Set(42) 40 | Expect(event).To(Equal(42)) 41 | }) 42 | }) 43 | 44 | Context("generic property functions", func() { 45 | It("collects and modifies generic properties", func() { 46 | props := object.Properties(obj) 47 | Expect(props).To(HaveLen(1)) 48 | 49 | props[0].SetAny(12) 50 | 51 | Expect(obj.Prop.Get()).To(Equal(12)) 52 | }) 53 | }) 54 | }) 55 | -------------------------------------------------------------------------------- /engine/uniform/light_buffer.go: -------------------------------------------------------------------------------- 1 | package uniform 2 | 3 | import ( 4 | "unsafe" 5 | 6 | "github.com/johanhenriksson/goworld/render/color" 7 | "github.com/johanhenriksson/goworld/render/descriptor" 8 | ) 9 | 10 | type LightBuffer struct { 11 | buffer []Light 12 | settings LightSettings 13 | } 14 | 15 | func NewLightBuffer(capacity int) *LightBuffer { 16 | return &LightBuffer{ 17 | buffer: make([]Light, 1, capacity+1), 18 | 19 | // default lighting settings 20 | settings: LightSettings{ 21 | AmbientColor: color.White, 22 | AmbientIntensity: 0.4, 23 | 24 | ShadowBias: 0.005, 25 | ShadowSampleRadius: 1, 26 | ShadowSamples: 1, 27 | NormalOffset: 0.1, 28 | }, 29 | } 30 | } 31 | 32 | func (b *LightBuffer) Size() int { 33 | return cap(b.buffer) - 1 34 | } 35 | 36 | func (b *LightBuffer) Flush(desc *descriptor.Storage[Light]) { 37 | // settings is stored in the first element of the buffer 38 | // it excludes the first element containing the light settings 39 | b.settings.Count = int32(len(b.buffer) - 1) 40 | b.buffer[0] = *(*Light)(unsafe.Pointer(&b.settings)) 41 | desc.SetRange(0, b.buffer) 42 | } 43 | 44 | func (b *LightBuffer) Reset() { 45 | b.buffer = b.buffer[:1] 46 | } 47 | 48 | func (b *LightBuffer) Store(light Light) { 49 | b.buffer = append(b.buffer, light) 50 | } 51 | -------------------------------------------------------------------------------- /gui/widget/util.go: -------------------------------------------------------------------------------- 1 | package widget 2 | 3 | import ( 4 | "github.com/johanhenriksson/goworld/core/input/mouse" 5 | "github.com/johanhenriksson/goworld/math/vec2" 6 | "github.com/kjk/flex" 7 | ) 8 | 9 | func AbsolutePosition(node *flex.Node) vec2.T { 10 | pos := vec2.New(node.LayoutGetLeft(), node.LayoutGetTop()) 11 | if node.Parent != nil { 12 | pos = pos.Add(AbsolutePosition(node.Parent)) 13 | } 14 | return pos 15 | } 16 | 17 | func Find(widget T, key string) T { 18 | if widget.Key() == key { 19 | return widget 20 | } 21 | for _, child := range widget.Children() { 22 | if hit := Find(child, key); hit != nil { 23 | return hit 24 | } 25 | } 26 | return nil 27 | } 28 | 29 | func SimulateClick(widget T, button mouse.Button) { 30 | handler, ok := widget.(mouse.Handler) 31 | if !ok { 32 | panic("widget does not implement mouse.Handler") 33 | } 34 | 35 | press := mouse.NewButtonEvent(button, mouse.Press, widget.Position(), 0, false) 36 | handler.MouseEvent(press) 37 | release := mouse.NewButtonEvent(button, mouse.Release, widget.Position(), 0, false) 38 | handler.MouseEvent(release) 39 | } 40 | 41 | type MouseHandler interface { 42 | MouseEvent(mouse.Event) (MouseHandler, float32) 43 | 44 | MouseEnter(e mouse.Event) 45 | MouseExit(e mouse.Event) 46 | MouseMove(e mouse.Event) 47 | MouseClick(e mouse.Event) 48 | } 49 | -------------------------------------------------------------------------------- /assets/builtin/shaders/pass/ui_quad.vs.glsl: -------------------------------------------------------------------------------- 1 | #version 450 2 | 3 | #include "lib/common.glsl" 4 | #include "lib/ui.glsl" 5 | 6 | OUT(0, vec4, color) 7 | OUT(1, vec2, texcoord) 8 | OUT(2, vec2, position) 9 | OUT(3, flat vec2, center) 10 | OUT(4, flat vec2, half_size) 11 | OUT(5, flat uint, quad_index) 12 | 13 | out gl_PerVertex 14 | { 15 | vec4 gl_Position; 16 | }; 17 | 18 | const ivec3 vertices[] = 19 | { 20 | {-1, -1, 0}, // 0 21 | {-1, +1, 1}, // 1 22 | {+1, -1, 2}, // 2 23 | 24 | {-1, +1, 1}, // 1 25 | {+1, +1, 3}, // 3 26 | {+1, -1, 2}, // 2 27 | }; 28 | 29 | void main() 30 | { 31 | out_quad_index = gl_InstanceIndex; 32 | Quad quad = quads.item[out_quad_index]; 33 | vec2 vertex = vertices[gl_VertexIndex].xy; 34 | int corner = vertices[gl_VertexIndex].z; 35 | 36 | out_half_size = (quad.max - quad.min) / 2; 37 | out_center = (quad.max + quad.min) / 2; 38 | out_position = vertex * out_half_size + out_center; 39 | 40 | vec2 tex_half_size = (quad.uv_max - quad.uv_min) / 2; 41 | vec2 tex_center = (quad.uv_max + quad.uv_min) / 2; 42 | out_texcoord = vertex * tex_half_size + tex_center; 43 | 44 | gl_Position = vec4( 45 | 2 * out_position.x / config.resolution.x - 1, 46 | 2 * out_position.y / config.resolution.y - 1, 47 | 1 - quad.zindex / (config.zmax + 1), 48 | 1); 49 | 50 | out_color = quad.color[corner]; 51 | } 52 | -------------------------------------------------------------------------------- /assets/hash.go: -------------------------------------------------------------------------------- 1 | package assets 2 | 3 | import ( 4 | "crypto/sha256" 5 | "encoding/hex" 6 | "reflect" 7 | "unsafe" 8 | ) 9 | 10 | func Hash(v any) string { 11 | rv := reflect.ValueOf(v) 12 | 13 | // Handle nil values 14 | if !rv.IsValid() { 15 | panic("cannot hash nil value") 16 | } 17 | 18 | // Dereference pointers and interfaces 19 | for rv.Kind() == reflect.Ptr || rv.Kind() == reflect.Interface { 20 | rv = rv.Elem() 21 | } 22 | 23 | var ptr unsafe.Pointer 24 | var size uintptr 25 | 26 | switch rv.Kind() { 27 | case reflect.UnsafePointer: 28 | panic("cannot hash unsafe.Pointer") 29 | case reflect.Slice, reflect.Array: 30 | ptr = unsafe.Pointer(rv.Pointer()) 31 | if rv.Type().Kind() == reflect.Ptr { 32 | panic("cannot hash pointer slice") 33 | } 34 | size = uintptr(rv.Len()) * rv.Type().Size() 35 | case reflect.String: 36 | str := rv.String() 37 | ptr = unsafe.Pointer(unsafe.StringData(str)) 38 | size = uintptr(len(str)) 39 | default: 40 | // for other types, get a pointer to the value 41 | ptr = unsafe.Pointer(rv.UnsafeAddr()) 42 | size = rv.Type().Size() 43 | } 44 | 45 | // create a byte slice that represents the memory without copying 46 | buf := unsafe.Slice((*byte)(ptr), size) 47 | 48 | // calculate SHA-256 hash 49 | hash := sha256.Sum256(buf) 50 | return hex.EncodeToString(hash[:]) 51 | } 52 | -------------------------------------------------------------------------------- /gui/widget/window/style.go: -------------------------------------------------------------------------------- 1 | package window 2 | 3 | import ( 4 | . "github.com/johanhenriksson/goworld/gui/style" 5 | "github.com/johanhenriksson/goworld/gui/widget/button" 6 | "github.com/johanhenriksson/goworld/gui/widget/label" 7 | "github.com/johanhenriksson/goworld/gui/widget/rect" 8 | "github.com/johanhenriksson/goworld/render/color" 9 | ) 10 | 11 | type Style struct { 12 | MinWidth MinWidthProp 13 | MaxWidth MaxWidthProp 14 | } 15 | 16 | var TitleStyle = label.Style{ 17 | Grow: Grow(1), 18 | 19 | Color: RGB(1, 1, 1), 20 | Font: Font{ 21 | Size: 16, 22 | }, 23 | } 24 | 25 | var TitlebarStyle = rect.Style{ 26 | Color: RGBA(0.05, 0.05, 0.05, 0.8), 27 | Padding: Px(4), 28 | Layout: Row{}, 29 | AlignItems: AlignCenter, 30 | Pressed: rect.Pressed{ 31 | Color: RGBA(0.2, 0.2, 0.2, 0.8), 32 | }, 33 | } 34 | 35 | var FrameStyle = rect.Style{ 36 | Color: RGBA(0.1, 0.1, 0.1, 0.8), 37 | Padding: RectXY(10, 10), 38 | Layout: Column{}, 39 | AlignItems: AlignStart, 40 | AlignContent: AlignStretch, 41 | } 42 | 43 | var CloseButtonStyle = button.Style{ 44 | TextColor: color.White, 45 | BgColor: RGB(0.597, 0.098, 0.117), 46 | Padding: Rect{ 47 | Left: 5, 48 | Right: 5, 49 | Top: 2, 50 | Bottom: 2, 51 | }, 52 | 53 | Hover: button.Hover{ 54 | BgColor: RGB(0.3, 0.3, 0.3), 55 | }, 56 | } 57 | -------------------------------------------------------------------------------- /assets/fs/local_fs.go: -------------------------------------------------------------------------------- 1 | package fs 2 | 3 | import ( 4 | "errors" 5 | "fmt" 6 | "io" 7 | "os" 8 | "path/filepath" 9 | ) 10 | 11 | type Local struct { 12 | root string 13 | } 14 | 15 | var _ Filesystem = (*Local)(nil) 16 | 17 | func NewLocal(root string) *Local { 18 | return &Local{root: root} 19 | } 20 | 21 | func (fs *Local) path(key string) string { 22 | return filepath.Join(fs.root, key) 23 | } 24 | 25 | func (fs *Local) Read(key string) ([]byte, error) { 26 | path := fs.path(key) 27 | fp, err := os.Open(path) 28 | if err != nil { 29 | if errors.Is(err, os.ErrNotExist) { 30 | return nil, fmt.Errorf("asset %s %w", key, ErrNotFound) 31 | } 32 | return nil, fmt.Errorf("error opening asset %s: %w", key, err) 33 | } 34 | defer fp.Close() 35 | 36 | data, err := io.ReadAll(fp) 37 | if err != nil { 38 | return nil, fmt.Errorf("error reading asset %s: %w", key, err) 39 | } 40 | 41 | return data, nil 42 | } 43 | 44 | func (fs *Local) Write(key string, data []byte) error { 45 | path := fs.path(key) 46 | file, err := os.Create(path) 47 | if err != nil { 48 | if errors.Is(err, os.ErrExist) { 49 | return fmt.Errorf("asset %s exists: %w", key, ErrNotFound) 50 | } 51 | return fmt.Errorf("error opening asset %s: %w", key, err) 52 | } 53 | defer file.Close() 54 | _, err = file.Write(data) 55 | // todo: wrap errors 56 | return err 57 | } 58 | -------------------------------------------------------------------------------- /assets/fs/layered_fs.go: -------------------------------------------------------------------------------- 1 | package fs 2 | 3 | import ( 4 | "errors" 5 | "fmt" 6 | "slices" 7 | ) 8 | 9 | type Layered struct { 10 | layers []Filesystem 11 | } 12 | 13 | var _ Filesystem = (*Layered)(nil) 14 | 15 | func NewLayered(layers ...Filesystem) *Layered { 16 | return &Layered{layers: layers} 17 | } 18 | 19 | // Push adds a layer to the top of the filesystem stack 20 | func (fs *Layered) Push(layer Filesystem) { 21 | fs.layers = slices.Insert(fs.layers, 0, layer) 22 | } 23 | 24 | // Pop removes the top layer from the filesystem stack 25 | func (fs *Layered) Pop() { 26 | fs.layers = slices.Delete(fs.layers, 0, 1) 27 | } 28 | 29 | func (fs *Layered) Read(key string) ([]byte, error) { 30 | for _, layer := range fs.layers { 31 | data, err := layer.Read(key) 32 | if err == nil { 33 | return data, nil 34 | } 35 | } 36 | return nil, fmt.Errorf("asset %s %w", key, ErrNotFound) 37 | } 38 | 39 | func (fs *Layered) Write(key string, data []byte) error { 40 | if len(fs.layers) == 0 { 41 | return fmt.Errorf("no layers in filesystem") 42 | } 43 | for _, layer := range fs.layers { 44 | err := layer.Write(key, data) 45 | if errors.Is(err, ErrImmutable) { 46 | // skip immutable layers 47 | continue 48 | } else if err != nil { 49 | return err 50 | } 51 | } 52 | return fmt.Errorf("%w: no mutable layers in filesystem", ErrImmutable) 53 | } 54 | -------------------------------------------------------------------------------- /core/input/keys/handler.go: -------------------------------------------------------------------------------- 1 | package keys 2 | 3 | import ( 4 | "github.com/go-gl/glfw/v3.3/glfw" 5 | ) 6 | 7 | type Callback func(Event) 8 | 9 | type Handler interface { 10 | KeyEvent(Event) 11 | } 12 | 13 | type FocusHandler interface { 14 | Handler 15 | FocusEvent() 16 | BlurEvent() 17 | } 18 | 19 | var focused FocusHandler 20 | 21 | func KeyCallbackWrapper(handler Handler) glfw.KeyCallback { 22 | return func( 23 | w *glfw.Window, 24 | key glfw.Key, 25 | scancode int, 26 | action glfw.Action, 27 | mods glfw.ModifierKey, 28 | ) { 29 | ev := &event{ 30 | code: Code(key), 31 | action: Action(action), 32 | mods: Modifier(mods), 33 | } 34 | if focused != nil { 35 | focused.KeyEvent(ev) 36 | } else { 37 | handler.KeyEvent(ev) 38 | } 39 | } 40 | } 41 | 42 | func CharCallbackWrapper(handler Handler) glfw.CharCallback { 43 | return func( 44 | w *glfw.Window, 45 | char rune, 46 | ) { 47 | ev := &event{ 48 | char: char, 49 | action: Char, 50 | } 51 | if focused != nil { 52 | focused.KeyEvent(ev) 53 | } else { 54 | handler.KeyEvent(ev) 55 | } 56 | } 57 | } 58 | 59 | func Focus(handler FocusHandler) { 60 | if focused == handler { 61 | return 62 | } 63 | if focused != nil { 64 | focused.BlurEvent() 65 | } 66 | focused = handler 67 | if focused != nil { 68 | focused.FocusEvent() 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /editor/propedit/container.go: -------------------------------------------------------------------------------- 1 | package propedit 2 | 3 | import ( 4 | "github.com/johanhenriksson/goworld/gui/node" 5 | "github.com/johanhenriksson/goworld/gui/style" 6 | "github.com/johanhenriksson/goworld/gui/widget/label" 7 | "github.com/johanhenriksson/goworld/gui/widget/rect" 8 | "github.com/johanhenriksson/goworld/render/color" 9 | ) 10 | 11 | func Container(key string, children []node.T) node.T { 12 | return rect.New(key, rect.Props{ 13 | Style: rect.Style{ 14 | Layout: style.Column{}, 15 | AlignItems: style.AlignStart, 16 | Width: style.Pct(100), 17 | }, 18 | Children: children, 19 | }) 20 | } 21 | 22 | func Field(key, title string, children []node.T) node.T { 23 | return rect.New(key, rect.Props{ 24 | Style: rect.Style{ 25 | Layout: style.Column{}, 26 | AlignItems: style.AlignStart, 27 | Width: style.Pct(100), 28 | Padding: style.RectY(4), 29 | }, 30 | Children: []node.T{ 31 | label.New("label", label.Props{ 32 | Text: title, 33 | Style: label.Style{ 34 | Color: color.White, 35 | }, 36 | }), 37 | rect.New("editor", rect.Props{ 38 | Style: rect.Style{ 39 | Layout: style.Row{}, 40 | AlignItems: style.AlignStart, 41 | Width: style.Pct(100), 42 | Padding: style.RectY(2), 43 | }, 44 | Children: children, 45 | }), 46 | }, 47 | }) 48 | } 49 | -------------------------------------------------------------------------------- /render/sync/fence.go: -------------------------------------------------------------------------------- 1 | package sync 2 | 3 | import ( 4 | "time" 5 | 6 | "github.com/johanhenriksson/goworld/render/device" 7 | 8 | "github.com/vkngwrapper/core/v2/core1_0" 9 | "github.com/vkngwrapper/core/v2/driver" 10 | ) 11 | 12 | type Fence struct { 13 | device *device.Device 14 | ptr core1_0.Fence 15 | } 16 | 17 | func NewFence(device *device.Device, name string, signaled bool) *Fence { 18 | var flags core1_0.FenceCreateFlags 19 | if signaled { 20 | flags = core1_0.FenceCreateSignaled 21 | } 22 | 23 | ptr, _, err := device.Ptr().CreateFence(nil, core1_0.FenceCreateInfo{ 24 | Flags: flags, 25 | }) 26 | if err != nil { 27 | panic(err) 28 | } 29 | device.SetDebugObjectName(driver.VulkanHandle(ptr.Handle()), core1_0.ObjectTypeFence, name) 30 | 31 | return &Fence{ 32 | device: device, 33 | ptr: ptr, 34 | } 35 | } 36 | 37 | func (f *Fence) Ptr() core1_0.Fence { 38 | return f.ptr 39 | } 40 | 41 | func (f *Fence) Reset() { 42 | f.device.Ptr().ResetFences([]core1_0.Fence{f.ptr}) 43 | } 44 | 45 | func (f *Fence) Destroy() { 46 | f.ptr.Destroy(nil) 47 | f.ptr = nil 48 | } 49 | 50 | func (f *Fence) Wait() { 51 | f.device.Ptr().WaitForFences(true, time.Hour, []core1_0.Fence{f.ptr}) 52 | } 53 | 54 | func (f *Fence) Done() bool { 55 | r, err := f.ptr.Status() 56 | if err != nil { 57 | panic(err) 58 | } 59 | return r == core1_0.VKSuccess 60 | } 61 | -------------------------------------------------------------------------------- /gui/widget/image/style.go: -------------------------------------------------------------------------------- 1 | package image 2 | 3 | import ( 4 | . "github.com/johanhenriksson/goworld/gui/style" 5 | "github.com/kjk/flex" 6 | ) 7 | 8 | var DefaultStyle = &Style{} 9 | 10 | type Style struct { 11 | Extends *Style 12 | 13 | // Display properties 14 | 15 | Hidden bool 16 | 17 | // Sizing properties 18 | 19 | Width WidthProp 20 | MaxWidth MaxWidthProp 21 | Height HeightProp 22 | MaxHeight MaxHeightProp 23 | 24 | // Flex properties 25 | 26 | Basis BasisProp 27 | Grow FlexGrowProp 28 | Shrink FlexShrinkProp 29 | } 30 | 31 | func (style *Style) Apply(w T, state State) { 32 | if style.Extends == nil { 33 | if style != DefaultStyle { 34 | DefaultStyle.Apply(w, state) 35 | } 36 | } else { 37 | style.Extends.Apply(w, state) 38 | } 39 | 40 | // always set display: flex 41 | w.Flex().StyleSetDisplay(flex.DisplayFlex) 42 | 43 | if style.Basis != nil { 44 | style.Basis.ApplyBasis(w) 45 | } 46 | if style.Width != nil { 47 | style.Width.ApplyWidth(w) 48 | } 49 | if style.MaxWidth != nil { 50 | style.MaxWidth.ApplyMaxWidth(w) 51 | } 52 | if style.Height != nil { 53 | style.Height.ApplyHeight(w) 54 | } 55 | if style.MaxHeight != nil { 56 | style.MaxHeight.ApplyMaxHeight(w) 57 | } 58 | if style.Grow != nil { 59 | style.Grow.ApplyFlexGrow(w) 60 | } 61 | if style.Shrink != nil { 62 | style.Shrink.ApplyFlexShrink(w) 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /gui/widget/checkbox/checkbox.go: -------------------------------------------------------------------------------- 1 | package checkbox 2 | 3 | import ( 4 | "github.com/johanhenriksson/goworld/core/input/mouse" 5 | "github.com/johanhenriksson/goworld/gui/node" 6 | "github.com/johanhenriksson/goworld/gui/style" 7 | "github.com/johanhenriksson/goworld/gui/widget/icon" 8 | "github.com/johanhenriksson/goworld/gui/widget/rect" 9 | "github.com/johanhenriksson/goworld/render/color" 10 | ) 11 | 12 | type Props struct { 13 | Style Style 14 | Checked bool 15 | OnChange func(bool) 16 | } 17 | 18 | type Style struct { 19 | } 20 | 21 | func New(key string, props Props) node.T { 22 | return node.Component(key, props, render) 23 | } 24 | 25 | func render(props Props) node.T { 26 | checker := icon.IconCheckBoxOutlineBlank 27 | if props.Checked { 28 | checker = icon.IconCheckBox 29 | } 30 | 31 | click := func(e mouse.Event) { 32 | props.OnChange(!props.Checked) 33 | } 34 | 35 | return rect.New("background", rect.Props{ 36 | OnMouseUp: func(e mouse.Event) { 37 | // grab input focus 38 | // ... but how? 39 | e.Consume() 40 | }, 41 | OnMouseDown: click, 42 | 43 | Style: rect.Style{ 44 | Margin: style.Rect{ 45 | Right: 4, 46 | }, 47 | }, 48 | 49 | Children: []node.T{ 50 | icon.New("checked", icon.Props{ 51 | Icon: checker, 52 | Style: icon.Style{ 53 | Size: 18, 54 | Color: color.White, 55 | }, 56 | }), 57 | }, 58 | }) 59 | } 60 | -------------------------------------------------------------------------------- /gui/widget/window/modal/modal.go: -------------------------------------------------------------------------------- 1 | package modal 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/johanhenriksson/goworld/gui/node" 7 | "github.com/johanhenriksson/goworld/gui/style" 8 | "github.com/johanhenriksson/goworld/gui/widget/rect" 9 | "github.com/johanhenriksson/goworld/gui/widget/window" 10 | 11 | "github.com/samber/lo" 12 | ) 13 | 14 | type Props struct { 15 | Children []node.T 16 | Title string 17 | OnClose func() 18 | } 19 | 20 | func New(key string, props Props) node.T { 21 | return rect.New(key, rect.Props{ 22 | Children: []node.T{ 23 | window.New(key, window.Props{ 24 | Title: props.Title, 25 | OnClose: props.OnClose, 26 | Floating: false, 27 | Children: lo.Map(props.Children, modalRow), 28 | }), 29 | }, 30 | Style: rect.Style{ 31 | Position: style.Absolute{}, 32 | Width: style.Pct(100), 33 | Height: style.Pct(100), 34 | 35 | Layout: style.Row{}, 36 | JustifyContent: style.JustifyCenter, 37 | AlignContent: style.AlignCenter, 38 | AlignItems: style.AlignCenter, 39 | }, 40 | }) 41 | } 42 | 43 | func modalRow(child node.T, index int) node.T { 44 | return rect.New(fmt.Sprintf("row-%d", index), rect.Props{ 45 | Style: rect.Style{ 46 | Layout: style.Row{}, 47 | Width: style.Pct(100), 48 | Basis: style.Pct(100), 49 | Padding: style.RectY(2), 50 | }, 51 | Children: []node.T{child}, 52 | }) 53 | } 54 | -------------------------------------------------------------------------------- /render/texture/ref.go: -------------------------------------------------------------------------------- 1 | package texture 2 | 3 | import ( 4 | "encoding/gob" 5 | 6 | "github.com/johanhenriksson/goworld/assets/fs" 7 | "github.com/johanhenriksson/goworld/render/image" 8 | ) 9 | 10 | var Checker = PathRef("textures/uv_checker.png") 11 | 12 | func init() { 13 | gob.Register(&pathRef{}) 14 | gob.Register(Args{}) 15 | } 16 | 17 | type Data struct { 18 | Args 19 | Image *image.Data 20 | } 21 | 22 | type pathRef struct { 23 | Path string 24 | Args Args 25 | 26 | data *Data 27 | } 28 | 29 | func PathRef(path string) *pathRef { 30 | return &pathRef{ 31 | Path: path, 32 | Args: Args{ 33 | Filter: FilterLinear, 34 | Wrap: WrapRepeat, 35 | }, 36 | } 37 | } 38 | 39 | func PathArgsRef(path string, args Args) *pathRef { 40 | return &pathRef{ 41 | Path: path, 42 | Args: args, 43 | } 44 | } 45 | 46 | func (r *pathRef) Key() string { return r.Path } // todo: this must include arguments somehow 47 | func (r *pathRef) Version() int { return 1 } 48 | 49 | func (r *pathRef) LoadTexture(assets fs.Filesystem) *Data { 50 | // caching 51 | // todo: move somewhere else where its easier to keep track of cached data 52 | if r.data != nil { 53 | return r.data 54 | } 55 | 56 | // load image 57 | img, err := image.LoadFile(assets, r.Path) 58 | if err != nil { 59 | panic(err) 60 | } 61 | 62 | r.data = &Data{ 63 | Image: img, 64 | Args: r.Args, 65 | } 66 | return r.data 67 | } 68 | -------------------------------------------------------------------------------- /gui/hooks/hooks.go: -------------------------------------------------------------------------------- 1 | package hooks 2 | 3 | type Effect func() 4 | 5 | func UseState[T any](initial T) (T, func(T)) { 6 | // keep a reference to the current state 7 | state := getState() 8 | index := state.Next() 9 | 10 | // store state 11 | value := initial 12 | if len(state.data) > index { 13 | value = state.data[index].(T) 14 | } else { 15 | state.data = append(state.data, value) 16 | } 17 | 18 | setter := func(new T) { 19 | state.data[index] = new 20 | } 21 | return value, setter 22 | } 23 | 24 | func UseEffect(callback Effect, deps ...any) { 25 | // keep a reference to the current state 26 | state := getState() 27 | index := state.Next() 28 | 29 | // if we have no dependencies, run the callback every frame 30 | noDeps := len(deps) == 0 31 | if noDeps { 32 | callback() 33 | return 34 | } 35 | 36 | // check if any dependencies have changed 37 | changed := false 38 | if len(state.data) > index { 39 | // previous state exists - compare it 40 | prev := state.data[index].([]any) 41 | for i, dep := range deps { 42 | if prev[i] != dep { 43 | changed = true 44 | // update previous value so that the callback wont run again next time 45 | prev[i] = dep 46 | } 47 | } 48 | } else { 49 | // no previous state exists yet 50 | state.data = append(state.data, deps) 51 | } 52 | 53 | // finally, run the callback if anything changed 54 | if changed { 55 | callback() 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /math/vec4/operations.go: -------------------------------------------------------------------------------- 1 | package vec4 2 | 3 | import ( 4 | "github.com/johanhenriksson/goworld/math" 5 | "github.com/johanhenriksson/goworld/math/random" 6 | "github.com/johanhenriksson/goworld/math/vec2" 7 | "github.com/johanhenriksson/goworld/math/vec3" 8 | ) 9 | 10 | // New returns a new vec4 from its components 11 | func New(x, y, z, w float32) T { 12 | return T{x, y, z, w} 13 | } 14 | 15 | // Extend a vec3 to a vec4 by adding a W component 16 | func Extend(v vec3.T, w float32) T { 17 | return T{v.X, v.Y, v.Z, w} 18 | } 19 | 20 | // Extend2 a vec2 to a vec4 by adding the Z and W components 21 | func Extend2(v vec2.T, z, w float32) T { 22 | return T{v.X, v.Y, z, w} 23 | } 24 | 25 | // Dot returns the dot product of two vectors. 26 | func Dot(a, b T) float32 { 27 | return a.X*b.X + a.Y*b.Y + a.Z*b.Z + a.W*b.W 28 | } 29 | 30 | // Random vector, not normalized. 31 | func Random(min, max T) T { 32 | return T{ 33 | random.Range(min.X, max.X), 34 | random.Range(min.Y, max.Y), 35 | random.Range(min.Z, max.Z), 36 | random.Range(min.W, max.W), 37 | } 38 | } 39 | 40 | func Min(a, b T) T { 41 | return T{ 42 | X: math.Min(a.X, b.X), 43 | Y: math.Min(a.Y, b.Y), 44 | Z: math.Min(a.Z, b.Z), 45 | W: math.Min(a.W, b.W), 46 | } 47 | } 48 | 49 | func Max(a, b T) T { 50 | return T{ 51 | X: math.Max(a.X, b.X), 52 | Y: math.Max(a.Y, b.Y), 53 | Z: math.Max(a.Z, b.Z), 54 | W: math.Max(a.W, b.W), 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /render/buffer/array.go: -------------------------------------------------------------------------------- 1 | package buffer 2 | 3 | import ( 4 | "fmt" 5 | "reflect" 6 | 7 | "github.com/johanhenriksson/goworld/render/device" 8 | "github.com/johanhenriksson/goworld/util" 9 | ) 10 | 11 | type Array[K any] struct { 12 | T 13 | stride int 14 | count int 15 | } 16 | 17 | // NewArray creates a new typed array buffer. 18 | // When allocating arrays, the Size argument is the number of elements 19 | func NewArray[K any](device *device.Device, args Args) *Array[K] { 20 | align, maxSize := GetBufferLimits(device, args.Usage) 21 | 22 | var empty K 23 | kind := reflect.TypeOf(empty) 24 | sizeof := int(kind.Size()) 25 | 26 | stride := util.Align(sizeof, align) 27 | count := args.Size 28 | size := count * stride 29 | if size > maxSize { 30 | panic(fmt.Sprintf("buffer is too large for the specified usage. size: %d, max: %d", size, maxSize)) 31 | } 32 | 33 | args.Size = size 34 | buffer := New(device, args) 35 | 36 | return &Array[K]{ 37 | T: buffer, 38 | stride: stride, 39 | count: count, 40 | } 41 | } 42 | 43 | func (a *Array[K]) Set(index int, data K) { 44 | a.Write(index*a.stride, &data) 45 | a.Flush() 46 | } 47 | 48 | func (a *Array[K]) SetRange(offset int, data []K) { 49 | for i, el := range data { 50 | a.Write((i+offset)*a.stride, &el) 51 | } 52 | a.Flush() 53 | } 54 | 55 | func (a *Array[K]) Count() int { return a.count } 56 | func (a *Array[K]) Stride() int { return a.stride } 57 | -------------------------------------------------------------------------------- /core/object/reference.go: -------------------------------------------------------------------------------- 1 | package object 2 | 3 | import ( 4 | "encoding/gob" 5 | ) 6 | 7 | type Handle uint 8 | 9 | func init() { 10 | gob.Register(Handle(0)) 11 | } 12 | 13 | type Ref[T Component] struct { 14 | Property[Handle] 15 | pool Pool 16 | } 17 | 18 | var _ GenericProp = &Ref[Component]{} 19 | 20 | func NewRef[T Component](cmp T) Ref[T] { 21 | return Ref[T]{ 22 | Property: NewProperty(cmp.ID()), 23 | pool: cmp.Pool().unwrap(), 24 | } 25 | } 26 | 27 | func EmptyRef[T Component]() Ref[T] { 28 | return Ref[T]{ 29 | Property: NewProperty[Handle](0), 30 | pool: nil, 31 | } 32 | } 33 | 34 | func (r *Ref[T]) Get() (T, bool) { 35 | var empty T 36 | if r.pool == nil { 37 | return empty, false 38 | } 39 | 40 | cmp, ok := r.pool.Resolve(r.Property.value) 41 | if !ok { 42 | return empty, false 43 | } 44 | 45 | cast, ok := cmp.(T) 46 | if !ok { 47 | return empty, false 48 | } 49 | 50 | return cast, true 51 | } 52 | 53 | func (r *Ref[T]) Set(cmp T) { 54 | r.Property.Set(cmp.ID()) 55 | r.pool = cmp.Pool().unwrap() 56 | } 57 | 58 | // 59 | // serialization 60 | // 61 | 62 | func (r *Ref[T]) Serialize(enc Encoder) error { 63 | return enc.Encode(r.Property.value) 64 | } 65 | 66 | func (r *Ref[T]) Deserialize(pool Pool, dec Decoder) error { 67 | if err := r.Property.Deserialize(pool, dec); err != nil { 68 | return err 69 | } 70 | r.value = pool.remap(r.value) 71 | r.pool = pool.unwrap() 72 | return nil 73 | } 74 | -------------------------------------------------------------------------------- /assets/builtin/shaders/forward/forward.fs.glsl: -------------------------------------------------------------------------------- 1 | #version 450 2 | 3 | #include "lib/common.glsl" 4 | #include "lib/objects.glsl" 5 | #include "lib/lighting.glsl" 6 | 7 | IN(0, flat uint, object) 8 | IN(1, vec4, color) 9 | IN(2, vec2, texcoord) 10 | IN(3, vec3, view_position) 11 | IN(4, vec3, world_normal) 12 | IN(5, vec3, world_position) 13 | 14 | // Return Output 15 | OUT(0, vec4, diffuse) 16 | 17 | CAMERA(0, camera) 18 | OBJECT(1, object, in_object) 19 | LIGHTS(2, lights) 20 | SAMPLER_ARRAY(3, textures) 21 | 22 | void main() 23 | { 24 | uint texture0 = object.textures[TEX_SLOT_DIFFUSE]; 25 | vec4 albedo = texture_array(textures, texture0, in_texcoord); 26 | 27 | // todo: transparency should be a property of the material. if its opaque, alpha should be ignored. 28 | // discard low alpha fragments 29 | if (albedo.a < 0.01) { 30 | discard; 31 | } 32 | 33 | // apply tint 34 | vec3 tint = mix(vec3(1), in_color.rgb, in_color.a); 35 | albedo = vec4(albedo.rgb * tint, albedo.a); 36 | 37 | // calculate lighting 38 | int lightCount = lights.settings.Count; 39 | vec3 lightColor = ambientLight(lights.settings, 1); 40 | for(int i = 0; i < lightCount; i++) { 41 | lightColor += calculateLightColor(lights.item[i], in_world_position, in_world_normal, in_view_position.z, lights.settings); 42 | } 43 | 44 | // gamma correct & write fragment 45 | vec3 linearColor = pow(albedo.rgb, vec3(gamma)); 46 | out_diffuse = vec4(linearColor * lightColor, 1); 47 | } 48 | -------------------------------------------------------------------------------- /physics/box.go: -------------------------------------------------------------------------------- 1 | package physics 2 | 3 | import ( 4 | "fmt" 5 | "unsafe" 6 | 7 | "github.com/johanhenriksson/goworld/core/object" 8 | "github.com/johanhenriksson/goworld/math/vec3" 9 | ) 10 | 11 | func init() { 12 | object.Register[*Box](object.Type{ 13 | Name: "Box Collider", 14 | Path: []string{"Physics"}, 15 | Create: func(ctx object.Pool) (object.Component, error) { 16 | return NewBox(ctx, vec3.One), nil 17 | }, 18 | }) 19 | } 20 | 21 | type Box struct { 22 | kind ShapeType 23 | *Collider 24 | 25 | Extents object.Property[vec3.T] 26 | } 27 | 28 | var _ = checkShape(NewBox(object.GlobalPool, vec3.Zero)) 29 | 30 | func NewBox(pool object.Pool, size vec3.T) *Box { 31 | box := &Box{ 32 | kind: BoxShape, 33 | Extents: object.NewProperty(size), 34 | } 35 | box.Collider = newCollider(pool, box, true) 36 | 37 | // resize shape when extents are modified 38 | box.Extents.OnChange.Subscribe(func(t vec3.T) { 39 | box.refresh() 40 | }) 41 | 42 | return object.NewComponent(pool, box) 43 | } 44 | 45 | func (b *Box) Name() string { 46 | return "BoxShape" 47 | } 48 | 49 | func (b *Box) String() string { 50 | return fmt.Sprintf("Box[Size=%s]", b.Extents.Get()) 51 | } 52 | 53 | func (b *Box) colliderCreate() shapeHandle { 54 | return shape_new_box(unsafe.Pointer(b), b.Extents.Get().Scaled(0.5)) 55 | } 56 | 57 | func (b *Box) colliderIsCompound() bool { return false } 58 | 59 | func (b *Box) colliderRefresh() {} 60 | func (b *Box) colliderDestroy() {} 61 | -------------------------------------------------------------------------------- /render/command/pool.go: -------------------------------------------------------------------------------- 1 | package command 2 | 3 | import ( 4 | "github.com/johanhenriksson/goworld/render/device" 5 | 6 | "github.com/samber/lo" 7 | "github.com/vkngwrapper/core/v2/core1_0" 8 | ) 9 | 10 | type Pool struct { 11 | ptr core1_0.CommandPool 12 | device *device.Device 13 | } 14 | 15 | func NewPool(device *device.Device, flags core1_0.CommandPoolCreateFlags, queueFamilyIdx int) *Pool { 16 | ptr, _, err := device.Ptr().CreateCommandPool(nil, core1_0.CommandPoolCreateInfo{ 17 | Flags: flags, 18 | QueueFamilyIndex: queueFamilyIdx, 19 | }) 20 | if err != nil { 21 | panic(err) 22 | } 23 | return &Pool{ 24 | ptr: ptr, 25 | device: device, 26 | } 27 | } 28 | 29 | func (p *Pool) Ptr() core1_0.CommandPool { 30 | return p.ptr 31 | } 32 | 33 | func (p *Pool) Destroy() { 34 | p.ptr.Destroy(nil) 35 | p.ptr = nil 36 | } 37 | 38 | func (p *Pool) Allocate(level core1_0.CommandBufferLevel) *Buffer { 39 | buffers := p.AllocateBuffers(level, 1) 40 | return buffers[0] 41 | } 42 | 43 | func (p *Pool) AllocateBuffers(level core1_0.CommandBufferLevel, count int) []*Buffer { 44 | ptrs, _, err := p.device.Ptr().AllocateCommandBuffers(core1_0.CommandBufferAllocateInfo{ 45 | CommandPool: p.ptr, 46 | Level: level, 47 | CommandBufferCount: count, 48 | }) 49 | if err != nil { 50 | panic(err) 51 | } 52 | 53 | return lo.Map(ptrs, func(ptr core1_0.CommandBuffer, _ int) *Buffer { 54 | return newBuffer(p.device, p.ptr, ptr) 55 | }) 56 | } 57 | -------------------------------------------------------------------------------- /assets/builtin/shaders/forward/sprite.vs.glsl: -------------------------------------------------------------------------------- 1 | #version 450 2 | 3 | #include "lib/common.glsl" 4 | #include "lib/objects.glsl" 5 | 6 | OUT(0, flat uint, object) 7 | OUT(1, vec4, color) 8 | OUT(2, vec2, texcoord) 9 | OUT(3, vec3, view_position) 10 | OUT(4, vec3, world_normal) 11 | OUT(5, vec3, world_position) 12 | 13 | out gl_PerVertex { 14 | vec4 gl_Position; 15 | }; 16 | 17 | CAMERA(0, camera) 18 | OBJECT(1, object, gl_InstanceIndex) 19 | 20 | VERTEX_BUFFER(Vertex) 21 | INDEX_BUFFER(uint) 22 | 23 | void main() 24 | { 25 | out_object = get_object_index(); 26 | 27 | // load vertex data 28 | Vertex v = get_vertex_indexed(object.vertexPtr, object.indexPtr); 29 | 30 | vec3 center = (object.model * vec4(0, 0, 0, 1.0)).xyz; 31 | vec3 lookDirection = normalize(center - camera.Eye.xyz); 32 | vec3 up = vec3(0, 1, 0); 33 | 34 | vec3 right = normalize(cross(up, lookDirection)); 35 | 36 | // if Y is not locked, calculate up vector 37 | // up = normalize(cross(lookDirection, right)); 38 | 39 | out_world_position = center + 40 | right * v.position.x + 41 | up * v.position.y; 42 | 43 | // texture & color 44 | out_texcoord = v.tex; 45 | out_color = v.color; 46 | 47 | // gbuffer view position 48 | out_view_position = (camera.View * vec4(out_world_position.xyz, 1.0)).xyz; 49 | 50 | // world normal is always facing the camera 51 | out_world_normal = normalize(camera.Eye.xyz - center); 52 | 53 | // vertex clip space position 54 | gl_Position = camera.Proj * vec4(out_view_position, 1); 55 | } 56 | -------------------------------------------------------------------------------- /assets/assets.go: -------------------------------------------------------------------------------- 1 | package assets 2 | 3 | import ( 4 | "log" 5 | "os" 6 | "path/filepath" 7 | 8 | "github.com/johanhenriksson/goworld/assets/fs" 9 | ) 10 | 11 | // FS is the global asset filesystem 12 | // todo: remove this global variable to avoid the temptation of using it everywhere 13 | var FS fs.Filesystem 14 | 15 | const AssetFolderEnv = "ASSET_PATH" 16 | 17 | type Asset interface { 18 | Key() string 19 | Version() int 20 | } 21 | 22 | func init() { 23 | layeredFs := fs.NewLayered(BuiltinFilesystem) 24 | FS = layeredFs 25 | 26 | // look for a local asset path 27 | assetFolderName := "assets" 28 | if os.Getenv(AssetFolderEnv) != "" { 29 | assetFolderName = os.Getenv(AssetFolderEnv) 30 | } 31 | cwd, err := os.Getwd() 32 | if err != nil { 33 | panic(err) 34 | } 35 | if localAssetPath, err := FindFileInParents(assetFolderName, cwd); err == nil { 36 | log.Println("adding local file system layer rooted at", localAssetPath) 37 | layeredFs.Push(fs.NewLocal(localAssetPath)) 38 | } else { 39 | log.Println("no local asset path found") 40 | } 41 | } 42 | 43 | func FindFileInParents(name, path string) (string, error) { 44 | files, err := os.ReadDir(path) 45 | if err != nil { 46 | panic(err) 47 | } 48 | for _, file := range files { 49 | if file.Name() == name { 50 | return filepath.Join(path, name), nil 51 | } 52 | } 53 | parentPath := filepath.Dir(path) 54 | if parentPath == path { 55 | return "", fs.ErrNotFound 56 | } 57 | return FindFileInParents(name, parentPath) 58 | } 59 | -------------------------------------------------------------------------------- /render/pipeline/layout.go: -------------------------------------------------------------------------------- 1 | package pipeline 2 | 3 | import ( 4 | "log" 5 | 6 | "github.com/johanhenriksson/goworld/render/descriptor" 7 | "github.com/johanhenriksson/goworld/render/device" 8 | 9 | "github.com/samber/lo" 10 | "github.com/vkngwrapper/core/v2/core1_0" 11 | ) 12 | 13 | type Layout struct { 14 | ptr core1_0.PipelineLayout 15 | device *device.Device 16 | } 17 | 18 | func NewLayout(device *device.Device, descriptors []descriptor.SetLayout, constants []PushConstant) *Layout { 19 | offset := 0 20 | info := core1_0.PipelineLayoutCreateInfo{ 21 | 22 | SetLayouts: lo.Map(descriptors, func(desc descriptor.SetLayout, _ int) core1_0.DescriptorSetLayout { 23 | return desc.Ptr() 24 | }), 25 | 26 | PushConstantRanges: lo.Map(constants, func(push PushConstant, _ int) core1_0.PushConstantRange { 27 | size := push.Size() 28 | log.Printf("push: %d bytes", size) 29 | rng := core1_0.PushConstantRange{ 30 | StageFlags: core1_0.ShaderStageFlags(push.Stages), 31 | Offset: offset, 32 | Size: size, 33 | } 34 | offset += size 35 | return rng 36 | }), 37 | } 38 | 39 | ptr, _, err := device.Ptr().CreatePipelineLayout(nil, info) 40 | if err != nil { 41 | panic(err) 42 | } 43 | 44 | return &Layout{ 45 | ptr: ptr, 46 | device: device, 47 | } 48 | } 49 | 50 | func (l *Layout) Ptr() core1_0.PipelineLayout { 51 | return l.ptr 52 | } 53 | 54 | func (l *Layout) Destroy() { 55 | if l.ptr != nil { 56 | l.ptr.Destroy(nil) 57 | l.ptr = nil 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /editor/propedit/int_editor.go: -------------------------------------------------------------------------------- 1 | package propedit 2 | 3 | import ( 4 | "strconv" 5 | 6 | "github.com/johanhenriksson/goworld/core/object" 7 | "github.com/johanhenriksson/goworld/gui/node" 8 | ) 9 | 10 | func init() { 11 | Register[int](func(key, name string, prop object.GenericProp) node.T { 12 | return IntegerField(key, name, IntegerProps{ 13 | Value: prop.GetAny().(int), 14 | OnChange: func(f int) { prop.SetAny(f) }, 15 | }) 16 | }) 17 | } 18 | 19 | type IntegerProps struct { 20 | Label string 21 | Value int 22 | OnChange func(int) 23 | Validate func(int) bool 24 | } 25 | 26 | func IntegerField(key string, title string, props IntegerProps) node.T { 27 | return Field(key, title, []node.T{ 28 | Integer(key, props), 29 | }) 30 | } 31 | 32 | func Integer(key string, props IntegerProps) node.T { 33 | return node.Component(key, props, func(props IntegerProps) node.T { 34 | validate := func(int) bool { return true } 35 | if props.Validate != nil { 36 | validate = props.Validate 37 | } 38 | 39 | return String(key, StringProps{ 40 | Label: props.Label, 41 | Value: fmtInteger(props.Value), 42 | OnChange: func(text string) { 43 | f, _ := strconv.ParseInt(text, 10, 64) 44 | props.OnChange(int(f)) 45 | }, 46 | Validate: func(text string) bool { 47 | f, err := strconv.ParseInt(text, 10, 64) 48 | return err == nil && validate(int(f)) 49 | }, 50 | }) 51 | }) 52 | } 53 | 54 | func fmtInteger(v int) string { 55 | return strconv.FormatInt(int64(v), 10) 56 | } 57 | -------------------------------------------------------------------------------- /engine/app/app.go: -------------------------------------------------------------------------------- 1 | package app 2 | 3 | import ( 4 | "log" 5 | "runtime" 6 | 7 | "github.com/johanhenriksson/goworld/core/object" 8 | "github.com/johanhenriksson/goworld/engine" 9 | "github.com/johanhenriksson/goworld/engine/window" 10 | "github.com/johanhenriksson/goworld/engine/window/glfw" 11 | ) 12 | 13 | func Run(args Args, scenefuncs ...object.SceneFunc) { 14 | runtime.LockOSThread() 15 | args.Defaults() 16 | 17 | go engine.RunProfilingServer(6060) 18 | interrupt := NewInterrupter() 19 | 20 | app := engine.New("goworld", 0) 21 | defer app.Destroy() 22 | 23 | // create a window 24 | wnd, err := glfw.NewWindow(app.Instance(), app.Device(), window.WindowArgs{ 25 | Title: args.Title, 26 | Width: args.Width, 27 | Height: args.Height, 28 | Frames: 3, 29 | }) 30 | if err != nil { 31 | panic(err) 32 | } 33 | defer wnd.Destroy() 34 | 35 | // create renderer 36 | renderer := args.Renderer(app, wnd) 37 | defer renderer.Destroy() 38 | 39 | // create scene 40 | pool := object.NewPool() 41 | scene := object.Scene(pool, scenefuncs...) 42 | wnd.SetInputHandler(scene) 43 | 44 | object.Attach(scene, engine.NewStatsGUI(pool)) 45 | 46 | // run the render loop 47 | log.Println("ready") 48 | 49 | counter := engine.NewFrameCounter(60) 50 | for interrupt.Running() && !wnd.ShouldClose() { 51 | // update scene 52 | wnd.Poll() 53 | counter.Update() 54 | scene.Update(scene, counter.Delta()) 55 | 56 | // draw 57 | renderer.Draw(scene, counter.Elapsed(), counter.Delta()) 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /editor/propedit/float_editor.go: -------------------------------------------------------------------------------- 1 | package propedit 2 | 3 | import ( 4 | "strconv" 5 | 6 | "github.com/johanhenriksson/goworld/core/object" 7 | "github.com/johanhenriksson/goworld/gui/node" 8 | ) 9 | 10 | func init() { 11 | Register[float32](func(key, name string, prop object.GenericProp) node.T { 12 | return FloatField(key, name, FloatProps{ 13 | Value: prop.GetAny().(float32), 14 | OnChange: func(f float32) { prop.SetAny(f) }, 15 | }) 16 | }) 17 | } 18 | 19 | type FloatProps struct { 20 | Label string 21 | Value float32 22 | OnChange func(float32) 23 | Validate func(float32) bool 24 | } 25 | 26 | func FloatField(key string, title string, props FloatProps) node.T { 27 | return Field(key, title, []node.T{ 28 | Float(key, props), 29 | }) 30 | } 31 | 32 | func Float(key string, props FloatProps) node.T { 33 | return node.Component(key, props, func(props FloatProps) node.T { 34 | validate := func(float32) bool { return true } 35 | if props.Validate != nil { 36 | validate = props.Validate 37 | } 38 | 39 | return String(key, StringProps{ 40 | Label: props.Label, 41 | Value: fmtFloat(props.Value), 42 | OnChange: func(text string) { 43 | f, _ := strconv.ParseFloat(text, 32) 44 | props.OnChange(float32(f)) 45 | }, 46 | Validate: func(text string) bool { 47 | f, err := strconv.ParseFloat(text, 32) 48 | return err == nil && validate(float32(f)) 49 | }, 50 | }) 51 | }) 52 | } 53 | 54 | func fmtFloat(v float32) string { 55 | return strconv.FormatFloat(float64(v), 'f', -1, 32) 56 | } 57 | -------------------------------------------------------------------------------- /render/material/def.go: -------------------------------------------------------------------------------- 1 | package material 2 | 3 | import ( 4 | "encoding/gob" 5 | "strconv" 6 | 7 | "github.com/johanhenriksson/goworld/assets/fs" 8 | "github.com/johanhenriksson/goworld/render/vertex" 9 | 10 | "github.com/mitchellh/hashstructure/v2" 11 | "github.com/vkngwrapper/core/v2/core1_0" 12 | ) 13 | 14 | type ID uint64 15 | 16 | type Pass string 17 | 18 | const ( 19 | Deferred = Pass("deferred") 20 | Forward = Pass("forward") 21 | ) 22 | 23 | type Def struct { 24 | Shader string 25 | Pass Pass 26 | VertexFormat any 27 | DepthTest bool 28 | DepthWrite bool 29 | DepthClamp bool 30 | DepthFunc core1_0.CompareOp 31 | Primitive vertex.Primitive 32 | CullMode vertex.CullMode 33 | Transparent bool 34 | 35 | id ID 36 | } 37 | 38 | func init() { 39 | gob.Register(&Def{}) 40 | } 41 | 42 | func (d *Def) Hash() ID { 43 | if d == nil { 44 | return 0 45 | } 46 | if d.id == 0 { 47 | // cache the hash 48 | // todo: it might be a problem that this wont ever be invalidated 49 | d.id = Hash(d) 50 | } 51 | return d.id 52 | } 53 | 54 | func (d *Def) Key() string { 55 | return strconv.FormatUint(uint64(d.Hash()), 16) 56 | } 57 | 58 | func (d *Def) Version() int { 59 | return 1 60 | } 61 | 62 | func (d *Def) LoadMaterial(fs.Filesystem) *Def { 63 | return d 64 | } 65 | 66 | func Hash(def *Def) ID { 67 | if def == nil { 68 | return 0 69 | } 70 | hash, err := hashstructure.Hash(*def, hashstructure.FormatV2, nil) 71 | if err != nil { 72 | panic(err) 73 | } 74 | return ID(hash) 75 | } 76 | -------------------------------------------------------------------------------- /gui/test/layout_test.go: -------------------------------------------------------------------------------- 1 | package test 2 | 3 | import ( 4 | "github.com/johanhenriksson/goworld/gui/node" 5 | "github.com/johanhenriksson/goworld/gui/style" 6 | "github.com/johanhenriksson/goworld/gui/widget/rect" 7 | "github.com/johanhenriksson/goworld/math/vec2" 8 | "github.com/kjk/flex" 9 | 10 | . "github.com/onsi/ginkgo/v2" 11 | . "github.com/onsi/gomega" 12 | ) 13 | 14 | var _ = Describe("layout", func() { 15 | It("lays out flex rows properly", func() { 16 | small := rect.New("small", rect.Props{ 17 | Style: rect.Style{ 18 | Grow: style.Grow(0), 19 | Shrink: style.Shrink(0), 20 | Basis: style.Px(15), 21 | }, 22 | }) 23 | big := rect.New("big", rect.Props{ 24 | Style: rect.Style{ 25 | Basis: style.Px(100), 26 | Shrink: style.Shrink(1), 27 | Grow: style.Grow(0), 28 | }, 29 | }) 30 | row := rect.New("row", rect.Props{ 31 | Style: rect.Style{ 32 | Layout: style.Row{}, 33 | Grow: style.Grow(0), 34 | }, 35 | Children: []node.T{ 36 | small, big, 37 | }, 38 | }) 39 | tree := row.Hydrate("root") 40 | flex.CalculateLayout(tree.Flex(), 100, 10, flex.DirectionLTR) 41 | 42 | Expect(tree.Position()).To(Equal(vec2.New(0, 0))) 43 | Expect(tree.Size()).To(Equal(vec2.New(100, 10))) 44 | 45 | Expect(tree.Children()[0].Position()).To(Equal(vec2.New(0, 0))) 46 | Expect(tree.Children()[0].Size()).To(Equal(vec2.New(15, 10))) 47 | 48 | Expect(tree.Children()[1].Position()).To(Equal(vec2.New(15, 0))) 49 | Expect(tree.Children()[1].Size()).To(Equal(vec2.New(85, 10))) 50 | }) 51 | }) 52 | -------------------------------------------------------------------------------- /core/input/keys/event.go: -------------------------------------------------------------------------------- 1 | package keys 2 | 3 | import "fmt" 4 | 5 | type Event interface { 6 | Code() Code 7 | Action() Action 8 | Character() rune 9 | Modifier(Modifier) bool 10 | 11 | Handled() bool 12 | Consume() 13 | } 14 | 15 | type event struct { 16 | handled bool 17 | code Code 18 | char rune 19 | action Action 20 | mods Modifier 21 | } 22 | 23 | func (e event) Code() Code { return e.code } 24 | func (e event) Character() rune { return e.char } 25 | func (e event) Action() Action { return e.action } 26 | func (e event) Handled() bool { return e.handled } 27 | 28 | func (e event) Modifier(mod Modifier) bool { 29 | return e.mods&mod == mod 30 | } 31 | 32 | func (e *event) Consume() { 33 | e.handled = true 34 | } 35 | 36 | func (e event) String() string { 37 | switch e.action { 38 | case Press: 39 | return fmt.Sprintf("KeyEvent: %s %d %d", e.action, e.code, e.mods) 40 | case Release: 41 | return fmt.Sprintf("KeyEvent: %s %d %d", e.action, e.code, e.mods) 42 | case Repeat: 43 | return fmt.Sprintf("KeyEvent: %s %d %d", e.action, e.code, e.mods) 44 | case Char: 45 | return fmt.Sprintf("KeyEvent: %s %c", e.action, e.char) 46 | } 47 | return fmt.Sprintf("KeyEvent: Invalid Action %x", e.action) 48 | } 49 | 50 | func NewCharEvent(char rune, mods Modifier) Event { 51 | return &event{ 52 | action: Char, 53 | char: char, 54 | mods: mods, 55 | } 56 | } 57 | 58 | func NewPressEvent(code Code, action Action, mods Modifier) Event { 59 | return &event{ 60 | code: code, 61 | action: action, 62 | mods: mods, 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /math/mat4/project_test.go: -------------------------------------------------------------------------------- 1 | package mat4_test 2 | 3 | import ( 4 | "testing" 5 | 6 | . "github.com/johanhenriksson/goworld/math/mat4" 7 | "github.com/johanhenriksson/goworld/math/vec3" 8 | 9 | . "github.com/onsi/ginkgo/v2" 10 | . "github.com/onsi/gomega" 11 | ) 12 | 13 | func TestMat4(t *testing.T) { 14 | RegisterFailHandler(Fail) 15 | RunSpecs(t, "math/mat4") 16 | } 17 | 18 | type TransformTest struct { 19 | Input vec3.T 20 | Output vec3.T 21 | } 22 | 23 | func AssertTransforms(t *testing.T, transform T, cases []TransformTest) { 24 | t.Helper() 25 | for _, c := range cases { 26 | point := transform.TransformPoint(c.Input) 27 | if !point.ApproxEqual(c.Output) { 28 | t.Errorf("expected %v was %v", c.Output, point) 29 | } 30 | } 31 | } 32 | 33 | func TestOrthographicRZ(t *testing.T) { 34 | proj := OrthographicRZ(0, 10, 0, 10, -1, 1) 35 | AssertTransforms(t, proj, []TransformTest{ 36 | {vec3.New(5, 5, 0), vec3.New(0, 0, 0.5)}, 37 | {vec3.New(5, 5, 1), vec3.New(0, 0, 0)}, 38 | {vec3.New(5, 5, -1), vec3.New(0, 0, 1)}, 39 | {vec3.New(0, 0, -1), vec3.New(-1, -1, 1)}, 40 | }) 41 | } 42 | 43 | func TestPerspectiveVK(t *testing.T) { 44 | proj := Perspective(45, 1, 1, 100) 45 | AssertTransforms(t, proj, []TransformTest{ 46 | {vec3.New(0, 0, 1), vec3.New(0, 0, 0)}, 47 | {vec3.New(0, 0, 100), vec3.New(0, 0, 1)}, 48 | }) 49 | } 50 | 51 | var _ = Describe("LookAt (LH)", func() { 52 | It("correctly projects", func() { 53 | proj := LookAt(vec3.Zero, vec3.UnitZ, vec3.UnitY) 54 | Expect(proj.Forward().ApproxEqual(vec3.UnitZ)).To(BeTrue()) 55 | }) 56 | }) 57 | -------------------------------------------------------------------------------- /gui/widget/window/modal/inputbox.go: -------------------------------------------------------------------------------- 1 | package modal 2 | 3 | import ( 4 | "github.com/johanhenriksson/goworld/core/input/mouse" 5 | "github.com/johanhenriksson/goworld/gui/hooks" 6 | "github.com/johanhenriksson/goworld/gui/node" 7 | "github.com/johanhenriksson/goworld/gui/style" 8 | "github.com/johanhenriksson/goworld/gui/widget/button" 9 | "github.com/johanhenriksson/goworld/gui/widget/label" 10 | "github.com/johanhenriksson/goworld/gui/widget/textbox" 11 | "github.com/johanhenriksson/goworld/render/color" 12 | ) 13 | 14 | type InputProps struct { 15 | Title string 16 | Message string 17 | OnClose func() 18 | OnAccept func(string) 19 | } 20 | 21 | func NewInput(key string, props InputProps) node.T { 22 | return node.Component(key, props, renderInput) 23 | } 24 | 25 | func renderInput(props InputProps) node.T { 26 | text, setText := hooks.UseState("") 27 | return New("inputbox", Props{ 28 | Title: props.Title, 29 | OnClose: props.OnClose, 30 | Children: []node.T{ 31 | label.New("message", label.Props{ 32 | Text: props.Message, 33 | }), 34 | textbox.New("input", textbox.Props{ 35 | Text: text, 36 | OnChange: setText, 37 | Style: textbox.DefaultStyle, 38 | }), 39 | button.New("ok", button.Props{ 40 | Text: "OK", 41 | OnClick: func(e mouse.Event) { 42 | props.OnAccept(text) 43 | props.OnClose() 44 | }, 45 | Style: button.Style{ 46 | TextColor: color.Black, 47 | BgColor: style.RGBA(0.5, 0.5, 0.5, 1), 48 | Padding: style.RectXY(20, 4), 49 | }, 50 | }), 51 | }, 52 | }) 53 | } 54 | -------------------------------------------------------------------------------- /assets/builtin/shaders/pass/postprocess.fs.glsl: -------------------------------------------------------------------------------- 1 | #version 450 2 | 3 | #include "lib/common.glsl" 4 | 5 | IN(0, vec2, texcoord) 6 | OUT(0, vec4, color) 7 | SAMPLER(0, input) 8 | SAMPLER(1, lut) 9 | 10 | #define MAXCOLOR 15.0 11 | #define COLORS 16.0 12 | #define WIDTH 256.0 13 | #define HEIGHT 16.0 14 | 15 | vec3 lookup_color(sampler2D lut, vec3 clr) { 16 | float cell = clr.b * MAXCOLOR; 17 | 18 | float cell_l = floor(cell); 19 | float cell_h = ceil(cell); 20 | 21 | float half_px_x = 0.5 / WIDTH; 22 | float half_px_y = 0.5 / HEIGHT; 23 | float r_offset = half_px_x + clr.r / COLORS * (MAXCOLOR / COLORS); 24 | float g_offset = half_px_y + clr.g * (MAXCOLOR / COLORS); 25 | 26 | vec2 lut_pos_l = vec2(cell_l / COLORS + r_offset, 1 - g_offset); 27 | vec2 lut_pos_h = vec2(cell_h / COLORS + r_offset, 1 - g_offset); 28 | 29 | vec3 graded_color_l = texture(lut, lut_pos_l).rgb; 30 | vec3 graded_color_h = texture(lut, lut_pos_h).rgb; 31 | 32 | return mix(graded_color_l, graded_color_h, fract(cell)); 33 | } 34 | 35 | void main() { 36 | // todo: expose as uniform setting 37 | float exposure = 1.0; 38 | 39 | // get input color 40 | vec3 hdrColor = texture(tex_input, in_texcoord).rgb; 41 | 42 | // exposure tone mapping 43 | vec3 mapped = vec3(1.0) - exp(-hdrColor * exposure); 44 | 45 | // gamma correction 46 | vec3 corrected = pow(mapped, vec3(1/gamma)); 47 | 48 | // color grading 49 | vec3 graded = lookup_color(tex_lut, corrected); 50 | 51 | // return 52 | out_color = vec4(graded, 1); 53 | } 54 | -------------------------------------------------------------------------------- /render/renderpass/attachment/color_attachment.go: -------------------------------------------------------------------------------- 1 | package attachment 2 | 3 | import ( 4 | "github.com/johanhenriksson/goworld/render/color" 5 | "github.com/johanhenriksson/goworld/render/device" 6 | 7 | "github.com/vkngwrapper/core/v2/core1_0" 8 | ) 9 | 10 | type Color struct { 11 | Name Name 12 | Samples core1_0.SampleCountFlags 13 | LoadOp core1_0.AttachmentLoadOp 14 | StoreOp core1_0.AttachmentStoreOp 15 | InitialLayout core1_0.ImageLayout 16 | FinalLayout core1_0.ImageLayout 17 | Clear color.T 18 | Image Image 19 | Blend Blend 20 | } 21 | 22 | func (desc *Color) defaults() { 23 | if desc.Samples == 0 { 24 | desc.Samples = core1_0.Samples1 25 | } 26 | if desc.Image == nil { 27 | panic("no image reference") 28 | } 29 | } 30 | 31 | func NewColor(device *device.Device, desc Color) T { 32 | desc.defaults() 33 | 34 | clear := core1_0.ClearValueFloat{desc.Clear.R, desc.Clear.G, desc.Clear.B, desc.Clear.A} 35 | 36 | return &attachment{ 37 | name: desc.Name, 38 | image: desc.Image, 39 | clear: clear, 40 | blend: desc.Blend, 41 | desc: core1_0.AttachmentDescription{ 42 | Format: desc.Image.Format(), 43 | Samples: desc.Samples, 44 | LoadOp: desc.LoadOp, 45 | StoreOp: desc.StoreOp, 46 | InitialLayout: desc.InitialLayout, 47 | FinalLayout: desc.FinalLayout, 48 | 49 | // color attachments dont have stencil buffers, so we dont care about them 50 | StencilLoadOp: core1_0.AttachmentLoadOpDontCare, 51 | StencilStoreOp: core1_0.AttachmentStoreOpDontCare, 52 | }, 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /geometry/lines/lines.go: -------------------------------------------------------------------------------- 1 | package lines 2 | 3 | import ( 4 | "github.com/johanhenriksson/goworld/core/mesh" 5 | "github.com/johanhenriksson/goworld/core/object" 6 | "github.com/johanhenriksson/goworld/math/vec3" 7 | "github.com/johanhenriksson/goworld/render/color" 8 | "github.com/johanhenriksson/goworld/render/material" 9 | "github.com/johanhenriksson/goworld/render/vertex" 10 | ) 11 | 12 | type Lines struct { 13 | *mesh.Static 14 | Args 15 | } 16 | 17 | type Args struct { 18 | Lines []Line 19 | 20 | lineMesh vertex.MutableMesh[vertex.Vertex, uint32] 21 | } 22 | 23 | func New(pool object.Pool, args Args) *Lines { 24 | b := object.NewComponent(pool, &Lines{ 25 | Static: mesh.New(pool, material.Lines()), 26 | Args: args, 27 | }) 28 | b.lineMesh = vertex.NewLines(object.Key("lines", b), []vertex.Vertex{}, []uint32{}) 29 | b.VertexData.Set(b.lineMesh) 30 | b.Refresh() 31 | return b 32 | } 33 | 34 | func (li *Lines) Add(from, to vec3.T, clr color.T) { 35 | li.Lines = append(li.Lines, Line{ 36 | Start: from, 37 | End: to, 38 | Color: clr, 39 | }) 40 | } 41 | 42 | func (li *Lines) Clear() { 43 | li.Lines = li.Lines[:0] 44 | } 45 | 46 | func (li *Lines) Count() int { 47 | return len(li.Lines) 48 | } 49 | 50 | func (li *Lines) Refresh() { 51 | count := len(li.Lines) 52 | vertices := make([]vertex.Vertex, 2*count) 53 | for i := 0; i < count; i++ { 54 | line := li.Lines[i] 55 | a := &vertices[2*i+0] 56 | b := &vertices[2*i+1] 57 | a.P = line.Start 58 | a.C = line.Color 59 | b.P = line.End 60 | b.C = line.Color 61 | } 62 | li.lineMesh.Update(vertices, []uint32{}) 63 | } 64 | -------------------------------------------------------------------------------- /render/renderpass/attachment/depth_attachment.go: -------------------------------------------------------------------------------- 1 | package attachment 2 | 3 | import ( 4 | "github.com/johanhenriksson/goworld/render/device" 5 | 6 | "github.com/vkngwrapper/core/v2/core1_0" 7 | ) 8 | 9 | const DepthName Name = "depth" 10 | 11 | type Depth struct { 12 | Samples core1_0.SampleCountFlags 13 | LoadOp core1_0.AttachmentLoadOp 14 | StoreOp core1_0.AttachmentStoreOp 15 | StencilLoadOp core1_0.AttachmentLoadOp 16 | StencilStoreOp core1_0.AttachmentStoreOp 17 | InitialLayout core1_0.ImageLayout 18 | FinalLayout core1_0.ImageLayout 19 | ClearDepth float32 20 | ClearStencil uint32 21 | 22 | // Allocation strategy. Defaults to allocating new images. 23 | Image Image 24 | } 25 | 26 | func (desc *Depth) defaults() { 27 | if desc.Samples == 0 { 28 | desc.Samples = core1_0.Samples1 29 | } 30 | if desc.Image == nil { 31 | panic("no image reference") 32 | } 33 | } 34 | 35 | func NewDepth(device *device.Device, desc Depth) T { 36 | desc.defaults() 37 | 38 | clear := core1_0.ClearValueDepthStencil{ 39 | Depth: desc.ClearDepth, 40 | Stencil: desc.ClearStencil, 41 | } 42 | 43 | return &attachment{ 44 | name: DepthName, 45 | image: desc.Image, 46 | clear: clear, 47 | desc: core1_0.AttachmentDescription{ 48 | Format: desc.Image.Format(), 49 | Samples: desc.Samples, 50 | LoadOp: desc.LoadOp, 51 | StoreOp: desc.StoreOp, 52 | StencilLoadOp: desc.StencilLoadOp, 53 | StencilStoreOp: desc.StencilStoreOp, 54 | InitialLayout: desc.InitialLayout, 55 | FinalLayout: desc.FinalLayout, 56 | }, 57 | } 58 | } 59 | --------------------------------------------------------------------------------