├── .gitignore ├── LICENSE.md ├── Makefile ├── README.md ├── actor ├── fpsActor.go ├── freeMoveActor.go ├── physicsActor.go └── physicsActor2D.go ├── assets ├── cache.go ├── geometryUtils.go ├── importers.go ├── loader.go ├── maps.go └── objImporter.go ├── controller ├── basicMovementController.go ├── controller.go ├── factory.go ├── fpsController.go └── input.go ├── editor ├── editor.go ├── editorController.go ├── fileBrowser.go ├── iconData.go ├── maps.go ├── models │ ├── map.go │ └── node.go ├── nodeEditor.go ├── overview.go ├── progressBar.go ├── screens.go └── ui.go ├── effects ├── particleGroup.go ├── particleSystem.go ├── particleSystem_test.go ├── sprite.go └── util.go ├── emitter ├── emitter.go └── emitter_test.go ├── engine ├── engine.go ├── updatable.go └── util.go ├── examples ├── lighting │ └── main.go ├── multiplayer │ └── main.go ├── platformer │ └── main.go ├── simple │ └── main.go └── ui │ └── main.go ├── glfwController ├── controller.go ├── controllerManager.go ├── controller_test.go └── inputMappings.go ├── go.mod ├── go.sum ├── libs └── freetype │ ├── AUTHORS │ ├── CONTRIBUTORS │ ├── LICENSE │ ├── README │ ├── cmd │ └── print-glyph-points │ │ └── main.c │ ├── example │ ├── drawer │ │ ├── main.go │ │ └── out.png │ ├── freetype │ │ ├── main.go │ │ └── out.png │ ├── gamma │ │ └── main.go │ ├── raster │ │ └── main.go │ ├── round │ │ └── main.go │ └── truetype │ │ └── main.go │ ├── freetype.go │ ├── licenses │ ├── ftl.txt │ └── gpl.txt │ ├── raster │ ├── geom.go │ ├── paint.go │ ├── raster.go │ └── stroke.go │ └── truetype │ ├── face.go │ ├── glyph.go │ ├── hint.go │ ├── opcodes.go │ └── truetype.go ├── main.go ├── networking ├── client.go ├── clock.go ├── networking.go ├── packet.go ├── packet_test.go ├── server.go └── session.go ├── opengl ├── glRenderer.go ├── postEffects.go └── shaders.go ├── physics ├── chipmunk │ ├── body.go │ └── world.go └── physicsAPI │ ├── characterController.go │ ├── constraints.go │ ├── physicsObject.go │ └── physicsSpace.go ├── renderer ├── Cubemap.go ├── camera.go ├── fpsMeter.go ├── geometry.go ├── light.go ├── material.go ├── node.go ├── primitiveData.go ├── renderer.go ├── sceneGraph.go └── shader.go ├── resources ├── cubemap.png ├── cubemapNightSky.jpg ├── fire.png ├── majic.png ├── smoke.png ├── space.jpg ├── spark.png └── stickman.png ├── serializer ├── serialize.go └── serializer.go ├── shaderBuilder ├── README.md ├── main.go └── parser │ ├── expression.go │ ├── lexer.go │ ├── parser.go │ ├── parser_test.go │ ├── test │ ├── expected.frag │ ├── expected.vert │ ├── includeTest.glsl │ ├── includeTest2.glsl │ └── test.glsl │ └── token.go ├── shaders ├── basic.glsl ├── build │ ├── basic.frag │ ├── basic.vert │ ├── diffuseSpecular.frag │ ├── diffuseSpecular.vert │ ├── pbr.frag │ ├── pbr.vert │ ├── pbrComposite.frag │ ├── pbrComposite.vert │ └── postEffects │ │ ├── cell.frag │ │ ├── cell.vert │ │ ├── glow.frag │ │ └── glow.vert ├── diffuseSpecular.glsl ├── lib │ ├── ambientLight.glsl │ ├── base.glsl │ ├── common.glsl │ ├── directLight.glsl │ ├── directionalLights.glsl │ ├── fresnelEffect.glsl │ ├── glowOutput.glsl │ ├── indirectLight.glsl │ ├── metalnessTexture.glsl │ ├── pbrCompositeTextures.glsl │ ├── pointLights.glsl │ ├── roughnessTexture.glsl │ ├── textures.glsl │ └── worldTransform.glsl ├── pbr.glsl ├── pbrComposite.glsl └── postEffects │ ├── cell.glsl │ └── glow.glsl ├── ui ├── children.go ├── container.go ├── defaultFont.go ├── dropdown.go ├── element.go ├── hitbox.go ├── html.go ├── htmlAssets.go ├── image.go ├── progressBar.go ├── text.go ├── textfield.go ├── uiController.go ├── window.go └── windowAddons.go └── util ├── image.go ├── math.go ├── mathUtils_test.go ├── serialize.go ├── serialize_test.go └── util.go /.gitignore: -------------------------------------------------------------------------------- 1 | /TestAssets 2 | /build 3 | *.exe 4 | tags 5 | *~ 6 | *.swp 7 | debug 8 | .vscode/ 9 | sBuilder 10 | resources/wellScene/ -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | GoEngine - A game engine written in go, intended for 2d and 3d games. 2 | Copyright (C) 2016 Joshua Wales 3 | 4 | This program is free software: you can redistribute it and/or modify 5 | it under the terms of the GNU General Public License as published by 6 | the Free Software Foundation, either version 3 of the License, or 7 | (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, see . 16 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | COVER_DIR = cover 2 | BUILD_DIR = build 3 | SHADER_BUILD_DIR = shaders/build 4 | 5 | build: compileShaderBuilder 6 | mkdir -p $(SHADER_BUILD_DIR) 7 | 8 | ./sBuilder shaders/basic.glsl vert > $(SHADER_BUILD_DIR)/basic.vert 9 | ./sBuilder shaders/basic.glsl frag > $(SHADER_BUILD_DIR)/basic.frag 10 | 11 | ./sBuilder shaders/pbr.glsl vert > $(SHADER_BUILD_DIR)/pbr.vert 12 | ./sBuilder shaders/pbr.glsl frag > $(SHADER_BUILD_DIR)/pbr.frag 13 | 14 | ./sBuilder shaders/diffuseSpecular.glsl vert > $(SHADER_BUILD_DIR)/diffuseSpecular.vert 15 | ./sBuilder shaders/diffuseSpecular.glsl frag > $(SHADER_BUILD_DIR)/diffuseSpecular.frag 16 | 17 | ./sBuilder shaders/pbrComposite.glsl vert > $(SHADER_BUILD_DIR)/pbrComposite.vert 18 | ./sBuilder shaders/pbrComposite.glsl frag > $(SHADER_BUILD_DIR)/pbrComposite.frag 19 | 20 | mkdir -p $(SHADER_BUILD_DIR)/postEffects 21 | 22 | ./sBuilder shaders/postEffects/cell.glsl vert > $(SHADER_BUILD_DIR)/postEffects/cell.vert 23 | ./sBuilder shaders/postEffects/cell.glsl frag > $(SHADER_BUILD_DIR)/postEffects/cell.frag 24 | 25 | ./sBuilder shaders/postEffects/glow.glsl vert > $(SHADER_BUILD_DIR)/postEffects/glow.vert 26 | ./sBuilder shaders/postEffects/glow.glsl frag > $(SHADER_BUILD_DIR)/postEffects/glow.frag 27 | 28 | compileShaderBuilder: 29 | go build -o sBuilder ./shaderBuilder 30 | 31 | testintall: 32 | go get -t github.com/walesey/go-engine/actor 33 | go get -t github.com/walesey/go-engine/assets 34 | go get -t github.com/walesey/go-engine/controller 35 | go get -t github.com/walesey/go-engine/effects 36 | go get -t github.com/walesey/go-engine/emitter 37 | go get -t github.com/walesey/go-engine/engine 38 | go get -t github.com/walesey/go-engine/networking 39 | go get -t github.com/walesey/go-engine/physics/physicsAPI 40 | go get -t github.com/walesey/go-engine/shaderBuilder/parser 41 | go get -t github.com/walesey/go-engine/ui 42 | go get -t github.com/walesey/go-engine/util 43 | 44 | test: testintall 45 | go test github.com/walesey/go-engine/actor 46 | go test github.com/walesey/go-engine/assets 47 | go test github.com/walesey/go-engine/controller 48 | go test github.com/walesey/go-engine/effects 49 | go test github.com/walesey/go-engine/emitter 50 | go test github.com/walesey/go-engine/engine 51 | go test github.com/walesey/go-engine/networking 52 | go test github.com/walesey/go-engine/physics/physicsAPI 53 | go test github.com/walesey/go-engine/shaderBuilder/parser 54 | go test github.com/walesey/go-engine/ui 55 | go test github.com/walesey/go-engine/util 56 | 57 | coverage: testintall 58 | mkdir -p $(COVER_DIR) 59 | go test github.com/walesey/go-engine/actor -coverprofile=$(COVER_DIR)/actor.cover.out && \ 60 | go test github.com/walesey/go-engine/assets -coverprofile=$(COVER_DIR)/assets.cover.out && \ 61 | go test github.com/walesey/go-engine/controller -coverprofile=$(COVER_DIR)/controller.cover.out && \ 62 | go test github.com/walesey/go-engine/effects -coverprofile=$(COVER_DIR)/effects.cover.out && \ 63 | go test github.com/walesey/go-engine/emitter -coverprofile=$(COVER_DIR)/emitter.cover.out && \ 64 | go test github.com/walesey/go-engine/engine -coverprofile=$(COVER_DIR)/engine.cover.out && \ 65 | go test github.com/walesey/go-engine/networking -coverprofile=$(COVER_DIR)/networking.cover.out && \ 66 | go test github.com/walesey/go-engine/physics/physicsAPI -coverprofile=$(COVER_DIR)/physics.cover.out && \ 67 | go test github.com/walesey/go-engine/shaderBuilder/parser -coverprofile=$(COVER_DIR)/shaderBuilderParser.cover.out && \ 68 | go test github.com/walesey/go-engine/ui -coverprofile=$(COVER_DIR)/ui.cover.out && \ 69 | go test github.com/walesey/go-engine/util -coverprofile=$(COVER_DIR)/util.cover.out && \ 70 | rm -f $(COVER_DIR)/coverage.out && \ 71 | echo 'mode: set' > $(COVER_DIR)/coverage.out && \ 72 | cat $(COVER_DIR)/*.cover.out | sed '/mode: set/d' >> $(COVER_DIR)/coverage.out && \ 73 | go tool cover -html=$(COVER_DIR)/coverage.out -o=$(COVER_DIR)/coverage.html && \ 74 | rm $(COVER_DIR)/*.cover.out 75 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # GoEngine 2 | 3 | Go engine is a simple game engine intended for 2D or 3D games. 4 | 5 | ## Features 6 | 7 | - OpenGL renderer 8 | - Obj importer 9 | - Lighting Engine 10 | - Particle System 11 | - UI system 12 | - Controller system (mouse, keyboard, joystick) 13 | - Multiplayer networking library 14 | 15 | ## Platform Support 16 | 17 | - Windows 18 | - macOS 19 | 20 | ## Instructions 21 | 22 | Example programs can be found in `examples/*`. 23 | 24 | Installing deps: 25 | sudo apt-get install mesa-utils 26 | sudo apt install mesa-common-dev 27 | sudo apt-get install libx11-dev 28 | sudo apt-get install libglfw3 29 | sudo apt-get install libxrandr-dev libxcursor-dev libxinerama-dev libxi-dev 30 | sudo apt-get install libxxf86vm-dev 31 | 32 | ## Core Packages 33 | 34 | - renderer - package contains common renderer interface and scenegraph implementation. 35 | - opengl - package contains opengl renderer implementation. 36 | - engine - package Is the high level engine interface that handles a lot of boilerplate stuff. 37 | - controller - package Is the api for keyboard/mouse/joystick controllers. (see examples/simple/main.go) 38 | - assets - asset management for images and obj files. 39 | 40 | ## Important Interfaces and Structs 41 | 42 | - renderer.Entity (interface) - anything that can be moved, rotated and scaled. (eg. Camera/Node/ParticleEmitter) 43 | - renderer.Spatial (interface) - something that can be Drawn by a Renderer (eg. Geometry/Node) 44 | - renderer.Node (struct) - Container for Spatials. 45 | - renderer.Geometry (struct) - A collection of faces and verticies. 46 | - renderer.Material (struct) - used for texturing a geometry. 47 | - renderer.Camera (struct) - Struct used to manage the camera. 48 | - renderer.Light (struct) - Struct used to manage dynamic lights. 49 | - controller.Controller (interface) - Can have (mouse/keyboard...) events bound to. 50 | - engine.Engine (interface) - The main game engine interface 51 | - engine.Updatable (interface) - anything that can be updated every game simulation step. 52 | 53 | ![Demo](http://i.imgur.com/toTtrxp.jpg) 54 | -------------------------------------------------------------------------------- /actor/fpsActor.go: -------------------------------------------------------------------------------- 1 | package actor 2 | 3 | import ( 4 | "github.com/go-gl/mathgl/mgl32" 5 | "github.com/walesey/go-engine/physics/physicsAPI" 6 | "github.com/walesey/go-engine/renderer" 7 | ) 8 | 9 | //an actor that can move around bound by physics (gravity), can jump, can walk/run 10 | type FPSActor struct { 11 | Entity renderer.Entity 12 | Character physicsAPI.CharacterController 13 | MoveSpeed, SprintSpeed float32 14 | LookSpeed float32 15 | lookPitch, lookAngle float32 16 | forwardMove, strafeMove float32 17 | walkDirection mgl32.Vec3 18 | } 19 | 20 | func NewFPSActor(entity renderer.Entity, character physicsAPI.CharacterController) *FPSActor { 21 | return &FPSActor{ 22 | Entity: entity, 23 | Character: character, 24 | MoveSpeed: 0.3, 25 | SprintSpeed: 0.5, 26 | LookSpeed: 0.001, 27 | } 28 | } 29 | 30 | func (actor *FPSActor) Update(dt float64) { 31 | // orientation 32 | vertRot := mgl32.QuatRotate(actor.lookAngle, mgl32.Vec3{0, 1, 0}) 33 | axis := vertRot.Rotate(mgl32.Vec3{1, 0, 0}).Cross(mgl32.Vec3{0, 1, 0}) 34 | horzRot := mgl32.QuatRotate(actor.lookPitch, axis) 35 | orientation := horzRot.Mul(vertRot) 36 | actor.Entity.SetOrientation(orientation) 37 | 38 | // walking direction 39 | if actor.Character.OnGround() { 40 | actor.walkDirection = orientation.Rotate(mgl32.Vec3{actor.forwardMove, 0, actor.strafeMove}) 41 | } 42 | actor.Character.SetWalkDirection(actor.walkDirection) 43 | actor.Entity.SetTranslation(actor.Character.GetPosition()) 44 | } 45 | 46 | func (actor *FPSActor) Look(dx, dy float32) { 47 | actor.lookAngle = actor.lookAngle - actor.LookSpeed*dx 48 | actor.lookPitch = actor.lookPitch - actor.LookSpeed*dy 49 | if actor.lookPitch > 1.5 { 50 | actor.lookPitch = 1.5 51 | } 52 | if actor.lookPitch < -1.5 { 53 | actor.lookPitch = -1.5 54 | } 55 | } 56 | 57 | func (actor *FPSActor) StartMovingForward() { 58 | actor.forwardMove = actor.MoveSpeed 59 | } 60 | 61 | func (actor *FPSActor) StartMovingBackward() { 62 | actor.forwardMove = -actor.MoveSpeed 63 | } 64 | 65 | func (actor *FPSActor) StartStrafingLeft() { 66 | actor.strafeMove = -actor.MoveSpeed 67 | } 68 | 69 | func (actor *FPSActor) StartStrafingRight() { 70 | actor.strafeMove = actor.MoveSpeed 71 | } 72 | 73 | func (actor *FPSActor) StopMovingForward() { 74 | actor.forwardMove = 0 75 | } 76 | 77 | func (actor *FPSActor) StopMovingBackward() { 78 | actor.forwardMove = 0 79 | } 80 | 81 | func (actor *FPSActor) StopStrafingLeft() { 82 | actor.strafeMove = 0 83 | } 84 | 85 | func (actor *FPSActor) StopStrafingRight() { 86 | actor.strafeMove = 0 87 | } 88 | 89 | func (actor *FPSActor) Jump() { 90 | if actor.Character.CanJump() { 91 | actor.Character.Jump() 92 | } 93 | } 94 | 95 | func (actor *FPSActor) StandUp() { 96 | 97 | } 98 | 99 | func (actor *FPSActor) Crouch() { 100 | 101 | } 102 | 103 | func (actor *FPSActor) Prone() { 104 | 105 | } 106 | 107 | func (actor *FPSActor) StartSprinting() { 108 | 109 | } 110 | 111 | func (actor *FPSActor) StopSprinting() { 112 | 113 | } 114 | -------------------------------------------------------------------------------- /actor/freeMoveActor.go: -------------------------------------------------------------------------------- 1 | package actor 2 | 3 | import ( 4 | "github.com/go-gl/mathgl/mgl32" 5 | "github.com/walesey/go-engine/renderer" 6 | ) 7 | 8 | //an actor that can move around freely in space 9 | type FreeMoveActor struct { 10 | Entity renderer.Entity 11 | Location mgl32.Vec3 12 | forwardMove, strafeMove float32 13 | lookPitch, lookAngle float32 14 | MoveSpeed, LookSpeed float32 15 | } 16 | 17 | func NewFreeMoveActor(entity renderer.Entity) *FreeMoveActor { 18 | return &FreeMoveActor{ 19 | Entity: entity, 20 | lookAngle: 0.0, 21 | lookPitch: 0.0, 22 | MoveSpeed: 10.0, 23 | LookSpeed: 0.001, 24 | } 25 | } 26 | 27 | func (actor *FreeMoveActor) Update(dt float64) { 28 | 29 | //orientation 30 | vertRot := mgl32.QuatRotate(actor.lookAngle, mgl32.Vec3{0, 1, 0}) 31 | axis := vertRot.Rotate(mgl32.Vec3{1, 0, 0}).Cross(mgl32.Vec3{0, 1, 0}) 32 | horzRot := mgl32.QuatRotate(actor.lookPitch, axis) 33 | orientation := horzRot.Mul(vertRot) 34 | velocity := orientation.Rotate(mgl32.Vec3{actor.forwardMove, 0, actor.strafeMove}) 35 | actor.Location = actor.Location.Add(velocity.Mul(float32(dt))) 36 | 37 | //update entity 38 | actor.Entity.SetTranslation(actor.Location) 39 | actor.Entity.SetOrientation(orientation) 40 | } 41 | 42 | func (actor *FreeMoveActor) Look(dx, dy float32) { 43 | actor.lookAngle = actor.lookAngle - actor.LookSpeed*dx 44 | actor.lookPitch = actor.lookPitch - actor.LookSpeed*dy 45 | if actor.lookPitch > 1.5 { 46 | actor.lookPitch = 1.5 47 | } 48 | if actor.lookPitch < -1.5 { 49 | actor.lookPitch = -1.5 50 | } 51 | } 52 | 53 | func (actor *FreeMoveActor) StartMovingUp() { 54 | actor.forwardMove = actor.MoveSpeed 55 | } 56 | 57 | func (actor *FreeMoveActor) StartMovingDown() { 58 | actor.forwardMove = -actor.MoveSpeed 59 | } 60 | 61 | func (actor *FreeMoveActor) StartMovingLeft() { 62 | actor.strafeMove = -actor.MoveSpeed 63 | } 64 | 65 | func (actor *FreeMoveActor) StartMovingRight() { 66 | actor.strafeMove = actor.MoveSpeed 67 | } 68 | 69 | func (actor *FreeMoveActor) StopMovingUp() { 70 | actor.forwardMove = 0 71 | } 72 | 73 | func (actor *FreeMoveActor) StopMovingDown() { 74 | actor.forwardMove = 0 75 | } 76 | 77 | func (actor *FreeMoveActor) StopMovingLeft() { 78 | actor.strafeMove = 0 79 | } 80 | 81 | func (actor *FreeMoveActor) StopMovingRight() { 82 | actor.strafeMove = 0 83 | } 84 | -------------------------------------------------------------------------------- /actor/physicsActor.go: -------------------------------------------------------------------------------- 1 | package actor 2 | 3 | import ( 4 | "github.com/walesey/go-engine/physics/physicsAPI" 5 | "github.com/walesey/go-engine/renderer" 6 | ) 7 | 8 | type PhysicsActor struct { 9 | Entity renderer.Entity 10 | Object physicsAPI.PhysicsObject 11 | } 12 | 13 | func NewPhysicsActor(entity renderer.Entity, object physicsAPI.PhysicsObject) *PhysicsActor { 14 | return &PhysicsActor{ 15 | Entity: entity, 16 | Object: object, 17 | } 18 | } 19 | 20 | func (actor *PhysicsActor) Update(dt float64) { 21 | //update entity\ 22 | actor.Entity.SetTranslation(actor.Object.GetPosition()) 23 | actor.Entity.SetOrientation(actor.Object.GetOrientation()) 24 | } 25 | -------------------------------------------------------------------------------- /actor/physicsActor2D.go: -------------------------------------------------------------------------------- 1 | package actor 2 | 3 | import ( 4 | "github.com/go-gl/mathgl/mgl32" 5 | "github.com/walesey/go-engine/physics/physicsAPI" 6 | "github.com/walesey/go-engine/renderer" 7 | ) 8 | 9 | type PhysicsActor2D struct { 10 | Entity renderer.Entity 11 | Object physicsAPI.PhysicsObject2D 12 | Mask mgl32.Vec3 13 | } 14 | 15 | func NewPhysicsActor2D(entity renderer.Entity, object physicsAPI.PhysicsObject2D, mask mgl32.Vec3) *PhysicsActor2D { 16 | return &PhysicsActor2D{ 17 | Entity: entity, 18 | Object: object, 19 | Mask: mask, 20 | } 21 | } 22 | 23 | func (actor *PhysicsActor2D) Update(dt float64) { 24 | objPos := actor.Object.GetPosition() 25 | var position mgl32.Vec3 26 | var orientation mgl32.Quat 27 | if actor.Mask.X() < actor.Mask.Y() && actor.Mask.X() < actor.Mask.Z() { // YZ plane 28 | position = mgl32.Vec3{0, actor.Mask.Y() * objPos.X(), actor.Mask.Z() * objPos.Y()} 29 | orientation = mgl32.QuatRotate(actor.Object.GetAngle(), mgl32.Vec3{1, 0, 0}) 30 | } else if actor.Mask.Y() < actor.Mask.X() && actor.Mask.Y() < actor.Mask.Z() { // XZ plane 31 | position = mgl32.Vec3{actor.Mask.X() * objPos.X(), 0, actor.Mask.Z() * objPos.Y()} 32 | orientation = mgl32.QuatRotate(actor.Object.GetAngle(), mgl32.Vec3{0, 1, 0}) 33 | } else { // XY plane 34 | position = mgl32.Vec3{actor.Mask.X() * objPos.X(), actor.Mask.Y() * objPos.Y(), 0} 35 | orientation = mgl32.QuatRotate(actor.Object.GetAngle(), mgl32.Vec3{0, 0, 1}) 36 | } 37 | actor.Entity.SetTranslation(position) 38 | actor.Entity.SetOrientation(orientation) 39 | } 40 | -------------------------------------------------------------------------------- /assets/cache.go: -------------------------------------------------------------------------------- 1 | package assets 2 | 3 | import ( 4 | "image" 5 | "sync" 6 | 7 | "github.com/walesey/go-engine/renderer" 8 | ) 9 | 10 | type AssetCache struct { 11 | geometries map[string]*renderer.Geometry 12 | materials map[string]*renderer.Material 13 | images map[string]image.Image 14 | fileMutexes map[string]*sync.Mutex 15 | mutex *sync.Mutex 16 | } 17 | 18 | var globalCache *AssetCache 19 | 20 | func init() { 21 | globalCache = NewAssetCache() 22 | } 23 | 24 | func (ac *AssetCache) ImportObj(path string) (geometry *renderer.Geometry, material *renderer.Material, err error) { 25 | ac.lockFilepath(path) 26 | var okGeom, okMat bool 27 | geometry, okGeom = ac.geometries[path] 28 | material, okMat = ac.materials[path] 29 | if !okGeom && !okMat { 30 | geometry, material, err = ImportObj(path) 31 | ac.mutex.Lock() 32 | ac.geometries[path] = geometry 33 | ac.materials[path] = material 34 | ac.mutex.Unlock() 35 | } 36 | ac.unlockFilepath(path) 37 | return 38 | } 39 | 40 | func (ac *AssetCache) ImportImage(path string) (img image.Image, err error) { 41 | ac.lockFilepath(path) 42 | var ok bool 43 | img, ok = ac.images[path] 44 | if !ok { 45 | img, err = ImportImage(path) 46 | ac.mutex.Lock() 47 | ac.images[path] = img 48 | ac.mutex.Unlock() 49 | } 50 | ac.unlockFilepath(path) 51 | return 52 | } 53 | 54 | func (ac *AssetCache) fileMutex(path string) *sync.Mutex { 55 | ac.mutex.Lock() 56 | if _, ok := ac.fileMutexes[path]; !ok { 57 | ac.fileMutexes[path] = &sync.Mutex{} 58 | } 59 | ac.mutex.Unlock() 60 | return ac.fileMutexes[path] 61 | } 62 | 63 | func (ac *AssetCache) lockFilepath(path string) { 64 | ac.fileMutex(path).Lock() 65 | } 66 | 67 | func (ac *AssetCache) unlockFilepath(path string) { 68 | ac.fileMutex(path).Unlock() 69 | } 70 | 71 | func ImportImageCached(path string) (image.Image, error) { 72 | return globalCache.ImportImage(path) 73 | } 74 | 75 | func ImportObjCached(path string) (geometry *renderer.Geometry, material *renderer.Material, err error) { 76 | return globalCache.ImportObj(path) 77 | } 78 | 79 | func NewAssetCache() *AssetCache { 80 | return &AssetCache{ 81 | geometries: make(map[string]*renderer.Geometry), 82 | materials: make(map[string]*renderer.Material), 83 | images: make(map[string]image.Image), 84 | fileMutexes: make(map[string]*sync.Mutex), 85 | mutex: &sync.Mutex{}, 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /assets/geometryUtils.go: -------------------------------------------------------------------------------- 1 | package assets 2 | 3 | import ( 4 | "github.com/go-gl/mathgl/mgl32" 5 | "github.com/walesey/go-engine/renderer" 6 | "github.com/walesey/go-engine/util" 7 | ) 8 | 9 | // IntersectGeometry - returns a list of line segments resulting from the xz plane intersection of the geometry 10 | func IntersectGeometry(geometry *renderer.Geometry) [][2]mgl32.Vec2 { 11 | segments := make([][2]mgl32.Vec2, 0) 12 | for i := 0; i < len(geometry.Indicies); i = i + 3 { 13 | index := geometry.Indicies[i] 14 | v1 := mgl32.Vec3{geometry.Verticies[index*renderer.VertexStride], geometry.Verticies[index*renderer.VertexStride+1], geometry.Verticies[index*renderer.VertexStride+2]} 15 | index = geometry.Indicies[i+1] 16 | v2 := mgl32.Vec3{geometry.Verticies[index*renderer.VertexStride], geometry.Verticies[index*renderer.VertexStride+1], geometry.Verticies[index*renderer.VertexStride+2]} 17 | index = geometry.Indicies[i+2] 18 | v3 := mgl32.Vec3{geometry.Verticies[index*renderer.VertexStride], geometry.Verticies[index*renderer.VertexStride+1], geometry.Verticies[index*renderer.VertexStride+2]} 19 | 20 | va, vb, vc := v1, v2, v3 21 | if (v1.Y() < 0 && v2.Y() > 0 && v3.Y() > 0) || (v1.Y() > 0 && v2.Y() < 0 && v3.Y() < 0) { 22 | va, vb, vc = v1, v2, v3 23 | } else if (v1.Y() > 0 && v2.Y() < 0 && v3.Y() > 0) || (v1.Y() < 0 && v2.Y() > 0 && v3.Y() < 0) { 24 | va, vb, vc = v2, v1, v3 25 | } else if (v1.Y() > 0 && v2.Y() > 0 && v3.Y() < 0) || (v1.Y() < 0 && v2.Y() < 0 && v3.Y() > 0) { 26 | va, vb, vc = v3, v2, v1 27 | } else { 28 | continue 29 | } 30 | 31 | t_ab := -va.Y() / (va.Y() - vb.Y()) 32 | t_ac := -va.Y() / (va.Y() - vc.Y()) 33 | segments = append(segments, [2]mgl32.Vec2{ 34 | mgl32.Vec2{va.X() + (va.X()-vb.X())*t_ab, va.Z() + (va.Z()-vb.Z())*t_ab}, 35 | mgl32.Vec2{va.X() + (va.X()-vc.X())*t_ac, va.Z() + (va.Z()-vc.Z())*t_ac}, 36 | }) 37 | } 38 | return segments 39 | } 40 | 41 | // Converts a geometry directly into points (does threshold culling optimisation) 42 | func PointsFromGeometry(geometry *renderer.Geometry, cullThreshold float32) *[]mgl32.Vec3 { 43 | verticies := make([]mgl32.Vec3, 0, len(geometry.Indicies)) 44 | for i := 0; i < len(geometry.Indicies); i = i + 1 { 45 | index := geometry.Indicies[i] 46 | v := mgl32.Vec3{ 47 | geometry.Verticies[index*renderer.VertexStride], 48 | geometry.Verticies[index*renderer.VertexStride+1], 49 | geometry.Verticies[index*renderer.VertexStride+2], 50 | } 51 | //do culling 52 | include := true 53 | for _, vert := range verticies { 54 | if util.Vec3LenSq(vert.Sub(v)) < cullThreshold*cullThreshold { 55 | include = false 56 | break 57 | } 58 | } 59 | if include { 60 | verticies = append(verticies, v) 61 | } 62 | } 63 | return &verticies 64 | } 65 | -------------------------------------------------------------------------------- /assets/importers.go: -------------------------------------------------------------------------------- 1 | package assets 2 | 3 | import ( 4 | "fmt" 5 | "image" 6 | _ "image/jpeg" 7 | _ "image/png" 8 | "io" 9 | "os" 10 | 11 | "io/ioutil" 12 | 13 | "github.com/disintegration/imaging" 14 | "github.com/walesey/go-engine/renderer" 15 | ) 16 | 17 | func ImportImage(file string) (image.Image, error) { 18 | imgFile, err := os.Open(file) 19 | if err != nil { 20 | fmt.Printf("Error opening image file: %v\n", err) 21 | return nil, err 22 | } 23 | return DecodeImage(imgFile) 24 | } 25 | 26 | func DecodeImage(data io.Reader) (image.Image, error) { 27 | img, _, err := image.Decode(data) 28 | if err != nil { 29 | fmt.Printf("Error decoding geometry file: %v\n", err) 30 | return nil, err 31 | } 32 | img = imaging.FlipV(img) 33 | return img, nil 34 | } 35 | 36 | func ImportShader(vertexFile, fragmentFile string) (*renderer.Shader, error) { 37 | vertsrc, err := ioutil.ReadFile(vertexFile) 38 | if err != nil { 39 | fmt.Printf("Error vertex file: %v\n", err) 40 | return nil, err 41 | } 42 | 43 | fragsrc, err := ioutil.ReadFile(fragmentFile) 44 | if err != nil { 45 | fmt.Printf("Error fragment file: %v\n", err) 46 | return nil, err 47 | } 48 | 49 | shader := renderer.NewShader() 50 | shader.VertSrc = string(vertsrc) 51 | shader.FragSrc = string(fragsrc) 52 | return shader, nil 53 | } 54 | -------------------------------------------------------------------------------- /assets/loader.go: -------------------------------------------------------------------------------- 1 | package assets 2 | 3 | import ( 4 | "log" 5 | 6 | "github.com/walesey/go-engine/editor/models" 7 | "github.com/walesey/go-engine/renderer" 8 | ) 9 | 10 | type geomImport struct { 11 | geometry *renderer.Geometry 12 | material *renderer.Material 13 | callback func(geometry *renderer.Geometry, material *renderer.Material) 14 | } 15 | 16 | type mapImport struct { 17 | node *renderer.Node 18 | model *editorModels.NodeModel 19 | callback func(node *renderer.Node, model *editorModels.NodeModel) 20 | } 21 | 22 | // The loader allows asyncronous loading of obj and map files 23 | type Loader struct { 24 | geoms chan geomImport 25 | maps chan mapImport 26 | } 27 | 28 | func NewLoader() *Loader { 29 | return &Loader{ 30 | geoms: make(chan geomImport, 256), 31 | maps: make(chan mapImport, 256), 32 | } 33 | } 34 | 35 | func (loader *Loader) Update(dt float64) { 36 | for { 37 | select { 38 | case g := <-loader.geoms: 39 | g.callback(g.geometry, g.material) 40 | case m := <-loader.maps: 41 | m.callback(m.node, m.model) 42 | default: 43 | return 44 | } 45 | } 46 | } 47 | 48 | func (loader *Loader) LoadMap(path string, callback func(node *renderer.Node, model *editorModels.NodeModel)) { 49 | go func() { 50 | srcModel := LoadMap(path) 51 | destNode := renderer.NewNode() 52 | loadedModel := LoadMapToNode(srcModel.Root, destNode) 53 | loader.maps <- mapImport{ 54 | node: destNode, 55 | model: loadedModel, 56 | callback: callback, 57 | } 58 | }() 59 | } 60 | 61 | func (loader *Loader) LoadObj(path string, callback func(geometry *renderer.Geometry, material *renderer.Material)) { 62 | go func() { 63 | loadedGeometry, loadedMaterial, err := ImportObjCached(path) 64 | if err != nil { 65 | log.Println("Error Loading Obj: ", err) 66 | } else { 67 | loader.geoms <- geomImport{ 68 | geometry: loadedGeometry, 69 | material: loadedMaterial, 70 | callback: callback, 71 | } 72 | } 73 | }() 74 | } 75 | -------------------------------------------------------------------------------- /assets/maps.go: -------------------------------------------------------------------------------- 1 | package assets 2 | 3 | import ( 4 | "encoding/json" 5 | "fmt" 6 | "io/ioutil" 7 | "log" 8 | 9 | "github.com/walesey/go-engine/editor/models" 10 | "github.com/walesey/go-engine/renderer" 11 | ) 12 | 13 | func LoadMap(path string) *editorModels.MapModel { 14 | data, err := ioutil.ReadFile(path) 15 | if err != nil { 16 | log.Printf("Error Reading map file: %v\n", err) 17 | return nil 18 | } 19 | 20 | var mapModel editorModels.MapModel 21 | err = json.Unmarshal(data, &mapModel) 22 | if err != nil { 23 | log.Printf("Error unmarshaling map model: %v\n", err) 24 | return nil 25 | } 26 | 27 | return &mapModel 28 | } 29 | 30 | func LoadMapToNode(srcModel *editorModels.NodeModel, destNode *renderer.Node) *editorModels.NodeModel { 31 | copy := srcModel.Copy(func(name string) string { return name }) 32 | loadMapRecursive(copy, srcModel, destNode) 33 | return copy 34 | } 35 | 36 | func loadMapRecursive(model, srcModel *editorModels.NodeModel, destNode *renderer.Node) { 37 | model.SetNode(destNode) 38 | if model.Geometry != nil { 39 | geometry, material, err := ImportObjCached(*model.Geometry) 40 | if err == nil { 41 | destNode.Add(geometry) 42 | destNode.Material = material 43 | } 44 | } 45 | destNode.SetScale(model.Scale) 46 | destNode.SetTranslation(model.Translation) 47 | destNode.SetOrientation(model.Orientation) 48 | if model.Reference != nil { 49 | copyRef(model, srcModel) 50 | } 51 | for _, childModel := range model.Children { 52 | newNode := renderer.NewNode() 53 | destNode.Add(newNode) 54 | loadMapRecursive(childModel, srcModel, newNode) 55 | } 56 | } 57 | 58 | func copyRef(model, srcModel *editorModels.NodeModel) { 59 | if model.Reference != nil { 60 | if refModel := FindNodeById(*model.Reference, srcModel); refModel != nil { 61 | for _, childModel := range refModel.Children { 62 | childCopy := childModel.Copy(func(name string) string { 63 | return fmt.Sprintf("%v::%v", *model.Reference, name) 64 | }) 65 | model.Children = append(model.Children, childCopy) 66 | } 67 | model.Reference = nil 68 | } 69 | } 70 | for _, child := range model.Children { 71 | copyRef(child, srcModel) 72 | } 73 | } 74 | 75 | func FindNodeById(nodeId string, model *editorModels.NodeModel) *editorModels.NodeModel { 76 | if model.Id == nodeId { 77 | return model 78 | } 79 | for _, childModel := range model.Children { 80 | node := FindNodeById(nodeId, childModel) 81 | if node != nil { 82 | return node 83 | } 84 | } 85 | return nil 86 | } 87 | 88 | func FindNodeByClass(class string, model *editorModels.NodeModel) []*editorModels.NodeModel { 89 | return findNodeByClass(class, model, model) 90 | } 91 | 92 | func findNodeByClass(class string, model, srcModel *editorModels.NodeModel) []*editorModels.NodeModel { 93 | results := []*editorModels.NodeModel{} 94 | if hasClass(class, model) { 95 | results = append(results, model) 96 | } 97 | for _, childModel := range model.Children { 98 | results = append(results, findNodeByClass(class, childModel, srcModel)...) 99 | } 100 | if model.Reference != nil { 101 | if refModel := FindNodeById(*model.Reference, srcModel); refModel != nil { 102 | results = append(results, findNodeByClass(class, refModel, srcModel)...) 103 | } 104 | } 105 | return results 106 | } 107 | 108 | func hasClass(class string, model *editorModels.NodeModel) bool { 109 | for _, modelClass := range model.Classes { 110 | if class == modelClass { 111 | return true 112 | } 113 | } 114 | return false 115 | } 116 | -------------------------------------------------------------------------------- /controller/basicMovementController.go: -------------------------------------------------------------------------------- 1 | package controller 2 | 3 | type BasicMovementActor interface { 4 | Look(dx, dy float32) 5 | StartMovingUp() 6 | StartMovingDown() 7 | StartMovingRight() 8 | StartMovingLeft() 9 | StopMovingUp() 10 | StopMovingDown() 11 | StopMovingRight() 12 | StopMovingLeft() 13 | } 14 | 15 | func NewBasicMovementController(actor BasicMovementActor, useDrag bool) Controller { 16 | c := CreateController() 17 | var x, y float32 = 0.0, 0.0 18 | drag := false 19 | doLook := func(xpos, ypos float32) { 20 | if drag || !useDrag { 21 | actor.Look(xpos-x, ypos-y) 22 | } 23 | x, y = xpos, ypos 24 | } 25 | mouseClick := func() { 26 | drag = true 27 | } 28 | mouseRelease := func() { 29 | drag = false 30 | } 31 | c.BindAxisAction(doLook) 32 | c.BindMouseAction(mouseClick, MouseButtonLeft, Press) 33 | c.BindMouseAction(mouseRelease, MouseButtonLeft, Release) 34 | 35 | c.BindKeyAction(actor.StartMovingUp, KeyUp, Press) 36 | c.BindKeyAction(actor.StartMovingDown, KeyDown, Press) 37 | c.BindKeyAction(actor.StartMovingLeft, KeyLeft, Press) 38 | c.BindKeyAction(actor.StartMovingRight, KeyRight, Press) 39 | c.BindKeyAction(actor.StopMovingUp, KeyUp, Release) 40 | c.BindKeyAction(actor.StopMovingDown, KeyDown, Release) 41 | c.BindKeyAction(actor.StopMovingLeft, KeyLeft, Release) 42 | c.BindKeyAction(actor.StopMovingRight, KeyRight, Release) 43 | 44 | c.BindKeyAction(actor.StartMovingUp, KeyW, Press) 45 | c.BindKeyAction(actor.StartMovingDown, KeyS, Press) 46 | c.BindKeyAction(actor.StartMovingLeft, KeyA, Press) 47 | c.BindKeyAction(actor.StartMovingRight, KeyD, Press) 48 | c.BindKeyAction(actor.StopMovingUp, KeyW, Release) 49 | c.BindKeyAction(actor.StopMovingDown, KeyS, Release) 50 | c.BindKeyAction(actor.StopMovingLeft, KeyA, Release) 51 | c.BindKeyAction(actor.StopMovingRight, KeyD, Release) 52 | return c 53 | } 54 | -------------------------------------------------------------------------------- /controller/controller.go: -------------------------------------------------------------------------------- 1 | package controller 2 | 3 | type Controller interface { 4 | SetKeyAction(function func(key Key, action Action)) 5 | BindKeyAction(function func(), key Key, action Action) 6 | SetMouseAction(function func(button MouseButton, action Action)) 7 | BindMouseAction(function func(), button MouseButton, action Action) 8 | BindAxisAction(function func(xpos, ypos float32)) 9 | BindScrollAction(function func(xoffset, yoffset float32)) 10 | } 11 | -------------------------------------------------------------------------------- /controller/factory.go: -------------------------------------------------------------------------------- 1 | package controller 2 | 3 | var defaultConstructor func() Controller 4 | 5 | func CreateController() Controller { 6 | if defaultConstructor != nil { 7 | return defaultConstructor() 8 | } 9 | return nil 10 | } 11 | 12 | func SetDefaultConstructor(constructor func() Controller) { 13 | defaultConstructor = constructor 14 | } 15 | -------------------------------------------------------------------------------- /controller/fpsController.go: -------------------------------------------------------------------------------- 1 | package controller 2 | 3 | type FPSActor interface { 4 | Look(dx, dy float32) 5 | StartMovingForward() 6 | StartMovingBackward() 7 | StartStrafingLeft() 8 | StartStrafingRight() 9 | StopMovingForward() 10 | StopMovingBackward() 11 | StopStrafingLeft() 12 | StopStrafingRight() 13 | Jump() 14 | StandUp() 15 | Crouch() 16 | Prone() 17 | StartSprinting() 18 | StopSprinting() 19 | } 20 | 21 | func NewFPSController(actor FPSActor) Controller { 22 | c := CreateController() 23 | var x, y float32 = 0.0, 0.0 24 | doLook := func(xpos, ypos float32) { 25 | actor.Look(xpos-x, ypos-y) 26 | x, y = xpos, ypos 27 | } 28 | c.BindAxisAction(doLook) 29 | c.BindKeyAction(actor.StartMovingForward, KeyW, Press) 30 | c.BindKeyAction(actor.StartMovingBackward, KeyS, Press) 31 | c.BindKeyAction(actor.StopMovingForward, KeyW, Release) 32 | c.BindKeyAction(actor.StopMovingBackward, KeyS, Release) 33 | c.BindKeyAction(actor.StartStrafingLeft, KeyA, Press) 34 | c.BindKeyAction(actor.StopStrafingLeft, KeyA, Release) 35 | c.BindKeyAction(actor.StartStrafingRight, KeyD, Press) 36 | c.BindKeyAction(actor.StopStrafingRight, KeyD, Release) 37 | c.BindKeyAction(actor.Jump, KeySpace, Press) 38 | c.BindKeyAction(actor.Crouch, KeyLeftControl, Press) 39 | c.BindKeyAction(actor.StandUp, KeyLeftControl, Release) 40 | c.BindKeyAction(actor.Prone, KeyZ, Press) 41 | c.BindKeyAction(actor.StartSprinting, KeyLeftShift, Press) 42 | c.BindKeyAction(actor.StopSprinting, KeyLeftShift, Release) 43 | return c 44 | } 45 | -------------------------------------------------------------------------------- /editor/editorController.go: -------------------------------------------------------------------------------- 1 | package editor 2 | 3 | import ( 4 | "github.com/go-gl/mathgl/mgl32" 5 | "github.com/walesey/go-engine/controller" 6 | ) 7 | 8 | const mouseSpeed = 0.01 9 | 10 | func NewEditorController(e *Editor) controller.Controller { 11 | c := controller.CreateController() 12 | var mouseDown bool 13 | var x, y float32 = 0.0, 0.0 14 | axisLock := mgl32.Vec3{1, 1, 1} 15 | 16 | doMouseMove := func(xpos, ypos float32) { 17 | if mouseDown { 18 | switch { 19 | case e.mouseMode == "scale": 20 | e.ScaleSelectedNodeModel(xpos-x, ypos-y, axisLock) 21 | case e.mouseMode == "translate": 22 | e.MoveSelectedNodeModel(xpos-x, ypos-y, axisLock) 23 | case e.mouseMode == "rotate": 24 | e.RotateSelectedNodeModel(xpos-x, ypos-y, axisLock) 25 | } 26 | } 27 | x, y = xpos, ypos 28 | } 29 | c.BindAxisAction(doMouseMove) 30 | c.BindMouseAction(func() { mouseDown = true }, controller.MouseButtonRight, controller.Press) 31 | c.BindMouseAction(func() { mouseDown = false }, controller.MouseButtonRight, controller.Release) 32 | c.BindKeyAction(func() { axisLock = mgl32.Vec3{1, 0, 0} }, controller.KeyX, controller.Press) 33 | c.BindKeyAction(func() { axisLock = mgl32.Vec3{0, 1, 0} }, controller.KeyY, controller.Press) 34 | c.BindKeyAction(func() { axisLock = mgl32.Vec3{0, 0, 1} }, controller.KeyZ, controller.Press) 35 | 36 | c.BindKeyAction(func() { 37 | e.mouseMode = "scale" 38 | axisLock = mgl32.Vec3{1, 1, 1} 39 | }, controller.KeyT, controller.Press) 40 | c.BindKeyAction(func() { 41 | e.mouseMode = "translate" 42 | axisLock = mgl32.Vec3{1, 0, 0} 43 | }, controller.KeyG, controller.Press) 44 | c.BindKeyAction(func() { 45 | e.mouseMode = "rotate" 46 | axisLock = mgl32.Vec3{0, 1, 0} 47 | }, controller.KeyR, controller.Press) 48 | return c 49 | } 50 | 51 | func (e *Editor) ScaleSelectedNodeModel(x, y float32, axisLock mgl32.Vec3) { 52 | selectedModel, _ := e.overviewMenu.getSelectedNode(e.currentMap.Root) 53 | if selectedModel != nil { 54 | selectedModel.Scale = selectedModel.Scale.Add(axisLock.Mul(x * mouseSpeed)) 55 | } 56 | updateMap(e.currentMap.Root) 57 | } 58 | 59 | func (e *Editor) MoveSelectedNodeModel(x, y float32, axisLock mgl32.Vec3) { 60 | selectedModel, _ := e.overviewMenu.getSelectedNode(e.currentMap.Root) 61 | if selectedModel != nil { 62 | selectedModel.Translation = selectedModel.Translation.Add(axisLock.Mul(x * mouseSpeed)) 63 | } 64 | updateMap(e.currentMap.Root) 65 | } 66 | 67 | func (e *Editor) RotateSelectedNodeModel(x, y float32, axisLock mgl32.Vec3) { 68 | selectedModel, _ := e.overviewMenu.getSelectedNode(e.currentMap.Root) 69 | if selectedModel != nil { 70 | selectedModel.Orientation = mgl32.QuatRotate(x*mouseSpeed, axisLock).Mul(selectedModel.Orientation).Normalize() 71 | } 72 | updateMap(e.currentMap.Root) 73 | } 74 | -------------------------------------------------------------------------------- /editor/maps.go: -------------------------------------------------------------------------------- 1 | package editor 2 | 3 | import ( 4 | "encoding/json" 5 | "fmt" 6 | "io/ioutil" 7 | "log" 8 | "os" 9 | 10 | "github.com/Invictus321/invictus321-countdown" 11 | "github.com/walesey/go-engine/assets" 12 | "github.com/walesey/go-engine/editor/models" 13 | "github.com/walesey/go-engine/engine" 14 | "github.com/walesey/go-engine/renderer" 15 | ) 16 | 17 | type MapLoadUpdate struct { 18 | node *renderer.Node 19 | geomsLoaded int 20 | } 21 | 22 | func (e *Editor) saveMap(filepath string) { 23 | data, err := json.MarshalIndent(e.currentMap, "", " ") 24 | if err != nil { 25 | log.Printf("Error Marshaling map file: %v\n", err) 26 | return 27 | } 28 | 29 | ioutil.WriteFile(filepath, data, os.ModePerm) 30 | } 31 | 32 | func (e *Editor) loadMap(path string) { 33 | e.currentMap = assets.LoadMap(path) 34 | e.refreshMap() 35 | e.overviewMenu.updateTree(e.currentMap) 36 | } 37 | 38 | func (e *Editor) refreshMap() { 39 | e.rootMapNode.RemoveAll(true) 40 | 41 | cd := countdown.Countdown{} 42 | cd.Start(countGeometries(e.currentMap.Root)) 43 | e.openProgressBar() 44 | e.setProgressBar(0) 45 | e.setProgressTime("Loading Map...") 46 | 47 | mapLoadChan := loadMapToNode(e.currentMap.Root) 48 | 49 | var loader engine.Updatable 50 | loader = engine.UpdatableFunc(func(dt float64) { 51 | select { 52 | case mapLoadUpdate := <-mapLoadChan: 53 | if mapLoadUpdate.node == nil { 54 | cd.Count() 55 | e.setProgressBar(cd.PercentageComplete() / 5) 56 | e.setProgressTime(fmt.Sprintf("Loading Map... %v seconds remaining", cd.SecondsRemaining())) 57 | } else { 58 | e.rootMapNode.Add(mapLoadUpdate.node) 59 | e.gameEngine.RemoveUpdatable(loader) 60 | e.closeProgressBar() 61 | } 62 | default: 63 | } 64 | }) 65 | e.gameEngine.AddUpdatable(loader) 66 | } 67 | 68 | func loadMapToNode(model *editorModels.NodeModel) chan MapLoadUpdate { 69 | 70 | out := make(chan MapLoadUpdate) 71 | geomsLoaded := 0 72 | 73 | var updateNode func(srcModel *editorModels.NodeModel, destNode *renderer.Node) 74 | updateNode = func(srcModel *editorModels.NodeModel, destNode *renderer.Node) { 75 | srcModel.SetNode(destNode) 76 | if srcModel.Geometry != nil { 77 | geometry, material, err := assets.ImportObjCached(*srcModel.Geometry) 78 | if err == nil { 79 | destNode.Material = material 80 | destNode.Add(geometry) 81 | } 82 | geomsLoaded++ 83 | out <- MapLoadUpdate{nil, geomsLoaded} 84 | } 85 | destNode.SetScale(srcModel.Scale) 86 | destNode.SetTranslation(srcModel.Translation) 87 | destNode.SetOrientation(srcModel.Orientation) 88 | if srcModel.Reference != nil { 89 | if refModel, _ := findNodeById(*srcModel.Reference, model); refModel != nil { 90 | for _, childModel := range refModel.Children { 91 | refNode := childModel.GetNode() 92 | if refNode != nil { 93 | destNode.Add(refNode) 94 | } 95 | } 96 | } 97 | } 98 | for _, childModel := range srcModel.Children { 99 | newNode := renderer.NewNode() 100 | destNode.Add(newNode) 101 | updateNode(childModel, newNode) 102 | } 103 | } 104 | 105 | node := renderer.NewNode() 106 | go func() { 107 | updateNode(model, node) 108 | out <- MapLoadUpdate{node, geomsLoaded} 109 | }() 110 | return out 111 | } 112 | 113 | func updateMap(model *editorModels.NodeModel) { 114 | node := model.GetNode() 115 | if node != nil { 116 | node.SetScale(model.Scale) 117 | node.SetTranslation(model.Translation) 118 | node.SetOrientation(model.Orientation) 119 | } 120 | for _, childModel := range model.Children { 121 | updateMap(childModel) 122 | } 123 | } 124 | 125 | func countGeometries(nodeModel *editorModels.NodeModel) int { 126 | count := 0 127 | if nodeModel.Geometry != nil { 128 | count++ 129 | } 130 | for _, child := range nodeModel.Children { 131 | count += countGeometries(child) 132 | } 133 | return count 134 | } 135 | -------------------------------------------------------------------------------- /editor/models/map.go: -------------------------------------------------------------------------------- 1 | package editorModels 2 | 3 | type MapModel struct { 4 | Name string `json:"name"` 5 | Root *NodeModel `json:"root"` 6 | } 7 | -------------------------------------------------------------------------------- /editor/models/node.go: -------------------------------------------------------------------------------- 1 | package editorModels 2 | 3 | import ( 4 | "github.com/go-gl/mathgl/mgl32" 5 | "github.com/walesey/go-engine/renderer" 6 | ) 7 | 8 | type NodeModel struct { 9 | Id string `json:"id"` 10 | Classes []string `json:"classes"` 11 | Scale mgl32.Vec3 `json:"scale"` 12 | Translation mgl32.Vec3 `json:"translation"` 13 | Orientation mgl32.Quat `json:"orientation"` 14 | Geometry *string `json:"geometry"` 15 | Reference *string `json:"reference"` 16 | Children []*NodeModel `json:"children"` 17 | node *renderer.Node 18 | } 19 | 20 | func (n *NodeModel) GetNode() *renderer.Node { 21 | return n.node 22 | } 23 | 24 | func (n *NodeModel) SetNode(node *renderer.Node) { 25 | n.node = node 26 | } 27 | 28 | func (n *NodeModel) CopyShallow(nameGenerator func(name string) string) *NodeModel { 29 | copiedNode := &NodeModel{ 30 | Id: nameGenerator(n.Id), 31 | Classes: n.Classes, 32 | Scale: n.Scale, 33 | Translation: n.Translation, 34 | Orientation: n.Orientation, 35 | Children: make([]*NodeModel, len(n.Children)), 36 | } 37 | if n.Geometry != nil { 38 | copiedNode.Geometry = n.Geometry 39 | } 40 | if n.Reference != nil { 41 | ref := *n.Reference 42 | copiedNode.Reference = &ref 43 | } 44 | return copiedNode 45 | } 46 | 47 | func (n *NodeModel) Copy(nameGenerator func(name string) string) *NodeModel { 48 | copiedNode := n.CopyShallow(nameGenerator) 49 | for i, child := range n.Children { 50 | copiedNode.Children[i] = child.Copy(nameGenerator) 51 | } 52 | return copiedNode 53 | } 54 | 55 | func NewNodeModel(id string) *NodeModel { 56 | return &NodeModel{ 57 | Id: id, 58 | Scale: mgl32.Vec3{1, 1, 1}, 59 | Translation: mgl32.Vec3{}, 60 | Orientation: mgl32.QuatIdent(), 61 | Children: make([]*NodeModel, 0), 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /editor/nodeEditor.go: -------------------------------------------------------------------------------- 1 | package editor 2 | 3 | import ( 4 | "fmt" 5 | "strings" 6 | 7 | "github.com/go-gl/mathgl/mgl32" 8 | "github.com/walesey/go-engine/editor/models" 9 | "github.com/walesey/go-engine/glfwController" 10 | "github.com/walesey/go-engine/ui" 11 | ) 12 | 13 | func (e *Editor) closeNodeEditor() { 14 | if e.fileBrowserOpen { 15 | e.gameEngine.RemoveSpatial(e.fileBrowser.window, false) 16 | e.fileBrowserOpen = false 17 | } 18 | } 19 | 20 | func (e *Editor) openNodeEditor(node *editorModels.NodeModel, callback func()) { 21 | window, container, _ := e.defaultWindow() 22 | window.SetTranslation(mgl32.Vec3{100, 100, 1}) 23 | window.SetScale(mgl32.Vec3{500, 0, 1}) 24 | uiController := ui.NewUiController(window).(glfwController.Controller) 25 | 26 | e.uiAssets.AddCallback("nodeEditorOk", func(element ui.Element, args ...interface{}) { 27 | if len(args) >= 2 && !args[1].(bool) { // not on release 28 | newId := window.TextElementById("name").GetText() 29 | if check, _ := findNodeById(newId, e.currentMap.Root); check != nil { 30 | return // id already taken 31 | } 32 | node.Id = newId 33 | node.Classes = []string{} 34 | for i := 1; i <= 3; i++ { 35 | className := window.TextElementById(fmt.Sprintf("class%v", i)).GetText() 36 | if len(className) > 0 { 37 | node.Classes = append(node.Classes, className) 38 | } 39 | } 40 | e.gameEngine.RemoveSpatial(window, true) 41 | e.controllerManager.RemoveController(uiController) 42 | callback() 43 | } 44 | }) 45 | 46 | e.uiAssets.AddCallback("nodeEditorCancel", func(element ui.Element, args ...interface{}) { 47 | if len(args) >= 2 && !args[1].(bool) { // not on release 48 | e.gameEngine.RemoveSpatial(window, true) 49 | e.controllerManager.RemoveController(uiController) 50 | } 51 | }) 52 | 53 | e.controllerManager.AddController(uiController) 54 | window.Tabs, _ = ui.LoadHTML(container, strings.NewReader(nodeEditMenuHtml), strings.NewReader(globalCss), e.uiAssets) 55 | 56 | e.gameEngine.AddOrtho(window) 57 | 58 | window.TextElementById("name").SetText(node.Id) 59 | for i, class := range node.Classes { 60 | if i < 3 { 61 | window.TextElementById(fmt.Sprintf("class%v", i+1)).SetText(class) 62 | } 63 | } 64 | window.Render() 65 | } 66 | -------------------------------------------------------------------------------- /editor/progressBar.go: -------------------------------------------------------------------------------- 1 | package editor 2 | 3 | import ( 4 | "fmt" 5 | "strings" 6 | 7 | "github.com/go-gl/mathgl/mgl32" 8 | "github.com/walesey/go-engine/glfwController" 9 | "github.com/walesey/go-engine/ui" 10 | ) 11 | 12 | func (e *Editor) closeProgressBar() { 13 | e.gameEngine.RemoveSpatial(e.progressBar, false) 14 | } 15 | 16 | func (e *Editor) openProgressBar() { 17 | if e.progressBar == nil { 18 | window := ui.NewWindow() 19 | window.SetTranslation(mgl32.Vec3{500, 0, 0}) 20 | window.SetScale(mgl32.Vec3{330, 0, 1}) 21 | 22 | container := ui.NewContainer() 23 | container.SetBackgroundColor(200, 200, 200, 255) 24 | window.SetElement(container) 25 | 26 | e.controllerManager.AddController(ui.NewUiController(window).(glfwController.Controller)) 27 | ui.LoadHTML(container, strings.NewReader(progressBarHtml), strings.NewReader(globalCss), e.uiAssets) 28 | window.Render() 29 | 30 | e.progressBar = window 31 | } 32 | e.gameEngine.AddOrtho(e.progressBar) 33 | } 34 | 35 | func (e *Editor) setProgressBar(progress int) { 36 | for i := 1; i <= 20; i++ { 37 | container, ok := e.progressBar.ElementById(fmt.Sprintf("progress%v", i)).(*ui.Container) 38 | if ok { 39 | if i > progress { 40 | container.SetBackgroundColor(0, 0, 0, 0) 41 | } else { 42 | container.SetBackgroundColor(0, 255, 0, 255) 43 | } 44 | } 45 | } 46 | } 47 | 48 | func (e *Editor) setProgressTime(message string) { 49 | container, ok := e.progressBar.ElementById("progressBarMessage").(*ui.Container) 50 | if ok { 51 | container.RemoveAllChildren() 52 | html := fmt.Sprintf("

%v

", message) 53 | css := "p { font-size: 8px; }" 54 | ui.LoadHTML(container, strings.NewReader(html), strings.NewReader(css), e.uiAssets) 55 | e.progressBar.Render() 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /editor/ui.go: -------------------------------------------------------------------------------- 1 | package editor 2 | 3 | import ( 4 | "bytes" 5 | "os" 6 | "strings" 7 | 8 | "github.com/go-gl/mathgl/mgl32" 9 | "github.com/walesey/go-engine/assets" 10 | "github.com/walesey/go-engine/controller" 11 | "github.com/walesey/go-engine/glfwController" 12 | "github.com/walesey/go-engine/ui" 13 | "github.com/walesey/go-engine/util" 14 | ) 15 | 16 | func (e *Editor) setupUI() { 17 | 18 | //images 19 | loadImageAsset("file", FileIconData, e.uiAssets) 20 | loadImageAsset("copy", CopyIconData, e.uiAssets) 21 | loadImageAsset("reference", LinkIconData, e.uiAssets) 22 | loadImageAsset("folderOpen", FolderOpenData, e.uiAssets) 23 | loadImageAsset("folderClosed", FolderClosedData, e.uiAssets) 24 | loadImageAsset("planetOpen", PlanetOpenData, e.uiAssets) 25 | loadImageAsset("planetClosed", PlanetClosedData, e.uiAssets) 26 | loadImageAsset("trash", TrashIconData, e.uiAssets) 27 | loadImageAsset("geometry", GeometryIconData, e.uiAssets) 28 | loadImageAsset("scale", ScaleIconData, e.uiAssets) 29 | loadImageAsset("translate", TranslateIconData, e.uiAssets) 30 | loadImageAsset("rotate", RotateIconData, e.uiAssets) 31 | loadImageAsset("reset", ResetIconData, e.uiAssets) 32 | loadImageAsset("cog", CogIconData, e.uiAssets) 33 | 34 | // callbacks used to highlight text active text fields 35 | e.uiAssets.AddCallback("inputfocus", func(element ui.Element, args ...interface{}) { 36 | container, ok := element.(*ui.Container) 37 | if ok { 38 | container.SetBackgroundColor(255, 255, 50, 255) 39 | } 40 | }) 41 | e.uiAssets.AddCallback("inputblur", func(element ui.Element, args ...interface{}) { 42 | container, ok := element.(*ui.Container) 43 | if ok { 44 | container.SetBackgroundColor(255, 255, 255, 255) 45 | } 46 | }) 47 | 48 | e.initOverviewMenu() 49 | e.gameEngine.InitFpsDial() 50 | 51 | e.customController.BindKeyAction(func() { 52 | if e.mainMenuOpen { 53 | e.mainMenuOpen = false 54 | e.closeMainMenu() 55 | } else { 56 | e.mainMenuOpen = true 57 | e.openMainMenu() 58 | } 59 | }, controller.KeyEscape, controller.Press) 60 | } 61 | 62 | func loadImageAsset(key, data string, uiAssets ui.HtmlAssets) { 63 | img, _ := assets.DecodeImage(bytes.NewBuffer(util.Base64ToBytes(data))) 64 | uiAssets.AddImage(key, img) 65 | } 66 | 67 | func (e *Editor) closeMainMenu() { 68 | e.gameEngine.RemoveSpatial(e.mainMenu, false) 69 | e.controllerManager.RemoveController(e.mainMenuController) 70 | } 71 | 72 | func (e *Editor) openMainMenu() { 73 | if e.mainMenu == nil { 74 | 75 | e.uiAssets.AddCallback("open", func(element ui.Element, args ...interface{}) { 76 | if len(args) >= 2 && !args[1].(bool) { // not on release 77 | e.openFileBrowser("Open", func(filePath string) { 78 | e.loadMap(filePath) 79 | e.closeFileBrowser() 80 | }, ".json") 81 | } 82 | }) 83 | e.uiAssets.AddCallback("save", func(element ui.Element, args ...interface{}) { 84 | if len(args) >= 2 && !args[1].(bool) { // not on release 85 | e.openFileBrowser("Save", func(filePath string) { 86 | e.saveMap(filePath) 87 | e.closeFileBrowser() 88 | }, ".json") 89 | } 90 | }) 91 | e.uiAssets.AddCallback("exit", func(element ui.Element, args ...interface{}) { 92 | os.Exit(0) 93 | }) 94 | 95 | window, container, _ := e.defaultWindow() 96 | window.SetTranslation(mgl32.Vec3{200, 200, 1}) 97 | window.SetScale(mgl32.Vec3{400, 0, 1}) 98 | 99 | ui.LoadHTML(container, strings.NewReader(mainMenuHtml), strings.NewReader(globalCss), e.uiAssets) 100 | window.Render() 101 | 102 | e.mainMenuController = ui.NewUiController(window).(glfwController.Controller) 103 | e.mainMenu = window 104 | } 105 | e.gameEngine.AddOrtho(e.mainMenu) 106 | e.controllerManager.AddController(e.mainMenuController) 107 | } 108 | 109 | func (e *Editor) defaultWindow() (window *ui.Window, container *ui.Container, tab *ui.Container) { 110 | window = ui.NewWindow() 111 | 112 | tab = ui.NewContainer() 113 | tab.SetBackgroundColor(70, 70, 70, 255) 114 | tab.SetHeight(40) 115 | 116 | mainContainer := ui.NewContainer() 117 | window.SetElement(mainContainer) 118 | container = ui.NewContainer() 119 | container.SetBackgroundColor(200, 200, 200, 255) 120 | mainContainer.AddChildren(tab, container) 121 | ui.ClickAndDragWindow(window, tab.Hitbox, e.customController) 122 | 123 | return 124 | } 125 | -------------------------------------------------------------------------------- /effects/particleGroup.go: -------------------------------------------------------------------------------- 1 | package effects 2 | 3 | import ( 4 | "github.com/go-gl/mathgl/mgl32" 5 | "github.com/walesey/go-engine/renderer" 6 | ) 7 | 8 | type ParticleGroup struct { 9 | Node *renderer.Node 10 | camera *renderer.Camera 11 | particles []*ParticleSystem 12 | } 13 | 14 | func (pg *ParticleGroup) Disable(disable bool) { 15 | for _, particle := range pg.particles { 16 | particle.DisableSpawning = disable 17 | } 18 | } 19 | 20 | func (pg *ParticleGroup) Update(dt float64) { 21 | for _, particle := range pg.particles { 22 | if pg.camera != nil { 23 | particle.SetCameraLocation(pg.camera.Translation) 24 | } 25 | particle.Update(dt) 26 | } 27 | } 28 | 29 | func (pg *ParticleGroup) SetTranslation(translation mgl32.Vec3) { 30 | for _, particle := range pg.particles { 31 | particle.Location = translation 32 | } 33 | } 34 | 35 | func (pg *ParticleGroup) Draw(renderer renderer.Renderer, transform mgl32.Mat4) { 36 | pg.Node.Draw(renderer, transform) 37 | } 38 | 39 | func (pg *ParticleGroup) Destroy(renderer renderer.Renderer) { 40 | pg.Node.Destroy(renderer) 41 | } 42 | 43 | func (pg *ParticleGroup) Center() mgl32.Vec3 { 44 | return pg.Node.Center() 45 | } 46 | 47 | func (pg *ParticleGroup) SetParent(parent *renderer.Node) { 48 | pg.Node.SetParent(parent) 49 | } 50 | 51 | func (pg *ParticleGroup) Optimize(geometry *renderer.Geometry, transform mgl32.Mat4) { 52 | pg.Node.Optimize(geometry, transform) 53 | } 54 | 55 | func (pg *ParticleGroup) BoundingRadius() float32 { 56 | return pg.Node.BoundingRadius() 57 | } 58 | 59 | func (pg *ParticleGroup) OrthoOrder() int { 60 | return pg.Node.OrthoOrder() 61 | } 62 | 63 | func NewParticleGroup(camera *renderer.Camera, particles ...*ParticleSystem) *ParticleGroup { 64 | node := renderer.NewNode() 65 | for _, particle := range particles { 66 | node.Add(particle) 67 | } 68 | return &ParticleGroup{ 69 | Node: node, 70 | camera: camera, 71 | particles: particles, 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /effects/particleSystem_test.go: -------------------------------------------------------------------------------- 1 | package effects 2 | 3 | import ( 4 | "image/color" 5 | "testing" 6 | 7 | "github.com/stretchr/testify/assert" 8 | ) 9 | 10 | func TestLerpColor(t *testing.T) { 11 | c := lerpColor(color.NRGBA{123, 10, 55, 50}, color.NRGBA{223, 60, 20, 240}, 0.0) 12 | assert.EqualValues(t, color.NRGBA{123, 10, 55, 50}, c, "Lerp Color") 13 | 14 | c = lerpColor(color.NRGBA{123, 10, 55, 50}, color.NRGBA{223, 60, 20, 240}, 1.0) 15 | assert.EqualValues(t, color.NRGBA{223, 60, 20, 240}, c, "Lerp Color") 16 | 17 | c = lerpColor(color.NRGBA{120, 10, 50, 50}, color.NRGBA{220, 60, 20, 240}, 0.5) 18 | assert.EqualValues(t, color.NRGBA{170, 35, 35, 145}, c, "Lerp Color") 19 | } 20 | -------------------------------------------------------------------------------- /effects/sprite.go: -------------------------------------------------------------------------------- 1 | package effects 2 | 3 | import ( 4 | "image/color" 5 | 6 | "github.com/go-gl/mathgl/mgl32" 7 | "github.com/walesey/go-engine/renderer" 8 | "github.com/walesey/go-engine/util" 9 | ) 10 | 11 | type Sprite struct { 12 | Node *renderer.Node 13 | geometry *renderer.Geometry 14 | Rotation float32 15 | FaceCamera bool 16 | cameraPosition mgl32.Vec3 17 | frame, totalFrames, framesX, framesY int 18 | } 19 | 20 | func CreateSprite(totalFrames, framesX, framesY int, material *renderer.Material) *Sprite { 21 | sprite := Sprite{ 22 | frame: 0, 23 | FaceCamera: true, 24 | totalFrames: totalFrames, 25 | framesX: framesX, 26 | framesY: framesY, 27 | } 28 | geometry := renderer.CreateBox(1, 1) 29 | sprite.geometry = geometry 30 | sprite.Node = renderer.NewNode() 31 | sprite.Node.Material = material 32 | sprite.Node.Add(sprite.geometry) 33 | return &sprite 34 | } 35 | 36 | func BoxFlipbook(geometry *renderer.Geometry, frame, framesX, framesY int) { 37 | frameSizeX := 1.0 / float32(framesX) 38 | frameSizeY := 1.0 / float32(framesY) 39 | indexX := float32(frame % framesX) 40 | indexY := float32(framesY - (frame / framesY) - 1) 41 | u1, u2 := frameSizeX*indexX, frameSizeX*(indexX+1.0) 42 | v1, v2 := frameSizeY*indexY, frameSizeY*(indexY+1.0) 43 | geometry.SetUVs(u1, v1, u2, v1, u2, v2, u1, v2) 44 | } 45 | 46 | func (sprite *Sprite) Draw(r renderer.Renderer, transform mgl32.Mat4) { 47 | sprite.Node.Draw(r, transform) 48 | } 49 | 50 | func (sprite *Sprite) Destroy(renderer renderer.Renderer) { 51 | sprite.Node.Destroy(renderer) 52 | } 53 | 54 | func (sprite *Sprite) Center() mgl32.Vec3 { 55 | return sprite.Node.Center() 56 | } 57 | 58 | func (sprite *Sprite) SetParent(parent *renderer.Node) { 59 | sprite.Node.SetParent(parent) 60 | } 61 | 62 | func (sprite *Sprite) NextFrame() { 63 | sprite.frame = sprite.frame + 1 64 | if sprite.frame >= sprite.totalFrames { 65 | sprite.frame = 0 66 | } 67 | BoxFlipbook(sprite.geometry, sprite.frame, sprite.framesX, sprite.framesY) 68 | } 69 | 70 | func (sprite *Sprite) SetColor(color color.NRGBA) { 71 | sprite.geometry.SetColor(color) 72 | } 73 | 74 | func (sprite *Sprite) Optimize(geometry *renderer.Geometry, transform mgl32.Mat4) { 75 | sprite.geometry.Optimize(geometry, transform) 76 | } 77 | 78 | func (sprite *Sprite) BoundingRadius() float32 { 79 | return sprite.Node.BoundingRadius() 80 | } 81 | 82 | func (sprite *Sprite) OrthoOrder() int { 83 | return sprite.Node.OrthoOrder() 84 | } 85 | 86 | func (sprite *Sprite) SetTranslation(translation mgl32.Vec3) { 87 | sprite.Node.SetTranslation(translation) 88 | } 89 | 90 | func (sprite *Sprite) SetScale(scale mgl32.Vec3) { 91 | sprite.Node.SetScale(scale) 92 | } 93 | 94 | func (sprite *Sprite) SetOrientation(orientation mgl32.Quat) { 95 | sprite.Node.SetOrientation(orientation) 96 | } 97 | 98 | func (sprite *Sprite) SetCameraLocation(cameraLocation mgl32.Vec3) { 99 | sprite.cameraPosition = cameraLocation 100 | } 101 | 102 | func (sprite *Sprite) Update(dt float64) { 103 | if sprite.FaceCamera { 104 | orientation := util.FacingOrientation(sprite.Rotation, sprite.cameraPosition.Sub(sprite.Node.Translation), mgl32.Vec3{0, 0, 1}, mgl32.Vec3{-1, 0, 0}) 105 | sprite.Node.SetOrientation(orientation) 106 | } 107 | } 108 | -------------------------------------------------------------------------------- /effects/util.go: -------------------------------------------------------------------------------- 1 | package effects 2 | 3 | import ( 4 | "github.com/walesey/go-engine/engine" 5 | "github.com/walesey/go-engine/renderer" 6 | ) 7 | 8 | type TimedParticleGroup struct { 9 | GameEngine engine.Engine 10 | TargetNode *renderer.Node 11 | Particles *ParticleGroup 12 | Life, Cleanup float64 13 | } 14 | 15 | func TriggerTimedParticleGroup(tpg TimedParticleGroup) { 16 | tpg.TargetNode.Add(tpg.Particles) 17 | tpg.GameEngine.AddUpdatable(tpg.Particles) 18 | particleLife := tpg.Life 19 | 20 | var updater engine.Updatable 21 | updater = engine.UpdatableFunc(func(dt float64) { 22 | particleLife -= dt 23 | if particleLife < 0 { 24 | tpg.Particles.Disable(true) 25 | } 26 | if particleLife < -tpg.Cleanup { 27 | tpg.TargetNode.Remove(tpg.Particles, true) 28 | tpg.GameEngine.RemoveUpdatable(tpg.Particles) 29 | tpg.GameEngine.RemoveUpdatable(updater) 30 | } 31 | }) 32 | tpg.GameEngine.AddUpdatable(updater) 33 | } 34 | -------------------------------------------------------------------------------- /emitter/emitter.go: -------------------------------------------------------------------------------- 1 | package emitter 2 | 3 | import "sync" 4 | 5 | type Event interface{} 6 | 7 | type EventChan <-chan Event 8 | 9 | type EventEmitter interface { 10 | On(topic string, handlers ...func(Event)) EventChan 11 | Off(topic string, channels ...EventChan) 12 | Listeners(topic string) []EventChan 13 | Emit(topic string, event Event) 14 | Do(topic string, event Event) 15 | Close() 16 | FlushAll() 17 | Flush(topic string) 18 | } 19 | 20 | type listener struct { 21 | handlers []func(Event) 22 | ch chan Event 23 | } 24 | 25 | type listeners []listener 26 | 27 | type Emitter struct { 28 | topics map[string]listeners 29 | channelSize int 30 | mux *sync.Mutex 31 | } 32 | 33 | func New(channelSize int) *Emitter { 34 | return &Emitter{ 35 | topics: make(map[string]listeners), 36 | channelSize: channelSize, 37 | mux: &sync.Mutex{}, 38 | } 39 | } 40 | 41 | // On - creates a new topic listener with optional handlers and returns the 42 | func (e *Emitter) On(topic string, handlers ...func(Event)) EventChan { 43 | l := listener{ 44 | handlers: handlers, 45 | ch: make(chan Event, e.channelSize), 46 | } 47 | if topicListeners, ok := e.getListeners(topic); ok { 48 | e.setListeners(topic, append(topicListeners, l)) 49 | } else { 50 | e.setListeners(topic, listeners{l}) 51 | } 52 | return l.ch 53 | } 54 | 55 | func (e *Emitter) Off(topic string, channels ...EventChan) { 56 | if topicListeners, ok := e.getListeners(topic); ok { 57 | if len(channels) > 0 { 58 | for _, ch := range channels { 59 | for i := len(topicListeners) - 1; i >= 0; i-- { 60 | if topicListeners[i].ch == ch { 61 | close(topicListeners[i].ch) 62 | topicListeners = append(topicListeners[:i], topicListeners[i+1:]...) 63 | } 64 | } 65 | } 66 | e.setListeners(topic, topicListeners) 67 | } else { 68 | for _, l := range topicListeners { 69 | close(l.ch) 70 | } 71 | e.deleteListeners(topic) 72 | } 73 | } 74 | } 75 | 76 | func (e *Emitter) Listeners(topic string) []EventChan { 77 | if topicListeners, ok := e.getListeners(topic); ok { 78 | listeners := make([]EventChan, len(topicListeners)) 79 | for i, l := range topicListeners { 80 | listeners[i] = l.ch 81 | } 82 | return listeners 83 | } 84 | return []EventChan{} 85 | } 86 | 87 | // Emit - writes an event to the given topic 88 | func (e *Emitter) Emit(topic string, event Event) { 89 | e.mux.Lock() 90 | defer e.mux.Unlock() 91 | if topicListeners, ok := e.topics[topic]; ok { 92 | for _, l := range topicListeners { 93 | l.ch <- event 94 | } 95 | } 96 | } 97 | 98 | // Do - writes an event to the given topic, then flushes the topic 99 | func (e *Emitter) Do(topic string, event Event) { 100 | e.Emit(topic, event) 101 | e.Flush(topic) 102 | } 103 | 104 | // Close - closes all channels and removes all topics 105 | func (e *Emitter) Close() { 106 | e.mux.Lock() 107 | defer e.mux.Unlock() 108 | for _, topicListeners := range e.topics { 109 | for _, l := range topicListeners { 110 | close(l.ch) 111 | } 112 | } 113 | e.topics = make(map[string]listeners) 114 | } 115 | 116 | // FlushAll - flushes all events from all channels and calls handlers for them 117 | func (e *Emitter) FlushAll() { 118 | e.mux.Lock() 119 | defer e.mux.Unlock() 120 | for topic := range e.topics { 121 | e.mux.Unlock() 122 | e.Flush(topic) 123 | e.mux.Lock() 124 | } 125 | } 126 | 127 | // Flush - flushes all events from the topic channels and calls handlers for them 128 | func (e *Emitter) Flush(topic string) { 129 | topicListeners, ok := e.getListeners(topic) 130 | if ok { 131 | for _, l := range topicListeners { 132 | l.flush() 133 | } 134 | } 135 | } 136 | 137 | func (l listener) flush() { 138 | Loop: 139 | for { 140 | select { 141 | case event := <-l.ch: 142 | if event == nil { 143 | break Loop 144 | } 145 | for _, h := range l.handlers { 146 | h(event) 147 | } 148 | default: 149 | break Loop 150 | } 151 | } 152 | } 153 | 154 | func (e *Emitter) getListeners(topic string) (l listeners, ok bool) { 155 | e.mux.Lock() 156 | defer e.mux.Unlock() 157 | l, ok = e.topics[topic] 158 | return 159 | } 160 | 161 | func (e *Emitter) setListeners(topic string, l listeners) { 162 | e.mux.Lock() 163 | defer e.mux.Unlock() 164 | e.topics[topic] = l 165 | } 166 | 167 | func (e *Emitter) deleteListeners(topic string) { 168 | e.mux.Lock() 169 | defer e.mux.Unlock() 170 | delete(e.topics, topic) 171 | } 172 | -------------------------------------------------------------------------------- /emitter/emitter_test.go: -------------------------------------------------------------------------------- 1 | package emitter 2 | 3 | import ( 4 | "fmt" 5 | "testing" 6 | 7 | "github.com/stretchr/testify/assert" 8 | ) 9 | 10 | type eventChecker struct { 11 | event Event 12 | } 13 | 14 | func (e *eventChecker) handler(event Event) { 15 | e.event = event 16 | } 17 | 18 | func TestSyncronousEvents(t *testing.T) { 19 | e := New(5) 20 | helloEvent := "helloEvent" 21 | otherEvent := "otherEvent" 22 | checker := &eventChecker{} 23 | otherChecker := &eventChecker{} 24 | e.On("hello", checker.handler) 25 | e.On("other", otherChecker.handler) 26 | 27 | e.Emit("hello", helloEvent) 28 | e.FlushAll() 29 | assert.EqualValues(t, checker.event, helloEvent) 30 | assert.NotEqual(t, otherChecker.event, helloEvent) 31 | 32 | e.Emit("other", otherEvent) 33 | e.FlushAll() 34 | assert.NotEqual(t, checker.event, otherEvent) 35 | assert.EqualValues(t, otherChecker.event, otherEvent) 36 | } 37 | 38 | func TestASyncronousEvents(t *testing.T) { 39 | e := New(5) 40 | helloEvent := "helloEvent" 41 | otherEvent := "otherEvent" 42 | helloChan := e.On("hello") 43 | otherChan := e.On("other") 44 | 45 | go e.Emit("hello", helloEvent) 46 | result := <-helloChan 47 | assert.EqualValues(t, result, helloEvent) 48 | 49 | go e.Emit("other", otherEvent) 50 | result = <-otherChan 51 | assert.EqualValues(t, result, otherEvent) 52 | } 53 | 54 | func TestOffCleanup(t *testing.T) { 55 | e := New(5) 56 | helloChan := e.On("hello") 57 | helloChan2 := e.On("hello") 58 | assert.EqualValues(t, len(e.Listeners("hello")), 2) 59 | 60 | e.Off("hello", helloChan) 61 | assert.EqualValues(t, len(e.Listeners("hello")), 1) 62 | assert.EqualValues(t, e.Listeners("hello")[0], helloChan2) 63 | 64 | helloChan3 := e.On("hello") 65 | e.Off("hello", helloChan3) 66 | assert.EqualValues(t, len(e.Listeners("hello")), 1) 67 | assert.EqualValues(t, e.Listeners("hello")[0], helloChan2) 68 | } 69 | 70 | func TestOffCleanupNested(t *testing.T) { 71 | e := New(5) 72 | var helloChan EventChan 73 | helloChan = e.On("hello", func(event Event) { 74 | fmt.Println("hellooo") 75 | e.Off("hello", helloChan) 76 | }) 77 | assert.EqualValues(t, 1, len(e.Listeners("hello"))) 78 | 79 | e.Emit("hello", "event") 80 | e.FlushAll() 81 | assert.EqualValues(t, 0, len(e.Listeners("hello"))) 82 | } 83 | -------------------------------------------------------------------------------- /engine/updatable.go: -------------------------------------------------------------------------------- 1 | package engine 2 | 3 | type Updatable interface { 4 | Update(dt float64) 5 | } 6 | 7 | type updatableImpl struct { 8 | updateFunc func(dt float64) 9 | } 10 | 11 | func (updatable *updatableImpl) Update(dt float64) { 12 | updatable.updateFunc(dt) 13 | } 14 | 15 | func UpdatableFunc(update func(dt float64)) Updatable { 16 | return &updatableImpl{update} 17 | } 18 | 19 | func UpdatableFanIn(updatables ...Updatable) Updatable { 20 | return UpdatableFunc(func(dt float64) { 21 | for _, u := range updatables { 22 | u.Update(dt) 23 | } 24 | }) 25 | } 26 | 27 | type UpdatableStore struct { 28 | updatables []Updatable 29 | } 30 | 31 | func NewUpdatableStore() *UpdatableStore { 32 | return &UpdatableStore{ 33 | updatables: make([]Updatable, 0), 34 | } 35 | } 36 | 37 | func (store *UpdatableStore) UpdateAll(dt float64) { 38 | for _, updatable := range store.updatables { 39 | if updatable != nil { 40 | updatable.Update(dt) 41 | } 42 | } 43 | } 44 | 45 | func (store *UpdatableStore) Add(updatable Updatable) { 46 | store.updatables = append(store.updatables, updatable) 47 | } 48 | 49 | func (store *UpdatableStore) Remove(updatable Updatable) { 50 | for i, u := range store.updatables { 51 | if updatable == u { 52 | store.updatables[i] = store.updatables[len(store.updatables)-1] 53 | store.updatables[len(store.updatables)-1] = nil 54 | store.updatables = store.updatables[:len(store.updatables)-1] 55 | break 56 | } 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /engine/util.go: -------------------------------------------------------------------------------- 1 | package engine 2 | 3 | func Timeout(e Engine, time float64, fn func()) Updatable { 4 | timer := time 5 | var updater Updatable 6 | updater = UpdatableFunc(func(dt float64) { 7 | if timer -= dt; timer <= 0 { 8 | fn() 9 | e.RemoveUpdatable(updater) 10 | } 11 | }) 12 | e.AddUpdatable(updater) 13 | return updater 14 | } 15 | 16 | func Interval(e Engine, time float64, fn func()) Updatable { 17 | timer := 0.0 18 | var updater Updatable 19 | updater = UpdatableFunc(func(dt float64) { 20 | for timer += dt; timer >= time; timer -= time { 21 | fn() 22 | } 23 | }) 24 | e.AddUpdatable(updater) 25 | return updater 26 | } 27 | -------------------------------------------------------------------------------- /examples/simple/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "go/build" 5 | "image/color" 6 | "os" 7 | "runtime" 8 | 9 | "github.com/go-gl/mathgl/mgl32" 10 | "github.com/walesey/go-engine/actor" 11 | "github.com/walesey/go-engine/assets" 12 | "github.com/walesey/go-engine/controller" 13 | "github.com/walesey/go-engine/engine" 14 | "github.com/walesey/go-engine/glfwController" 15 | "github.com/walesey/go-engine/opengl" 16 | "github.com/walesey/go-engine/renderer" 17 | ) 18 | 19 | func init() { 20 | // Use all cpu cores 21 | runtime.GOMAXPROCS(runtime.NumCPU()) 22 | //Set default glfw controller 23 | controller.SetDefaultConstructor(glfwController.NewActionMap) 24 | // set working dir to access assets 25 | p, _ := build.Import("github.com/walesey/go-engine", "", build.FindOnly) 26 | os.Chdir(p.Dir) 27 | } 28 | 29 | // 30 | func main() { 31 | 32 | glRenderer := opengl.NewOpenglRenderer("Simple", 800, 800, false) 33 | gameEngine := engine.NewEngine(glRenderer) 34 | gameEngine.InitFpsDial() 35 | 36 | gameEngine.Start(func() { 37 | 38 | if shader, err := assets.ImportShader("shaders/build/basic.vert", "shaders/build/basic.frag"); err == nil { 39 | gameEngine.DefaultShader(shader) 40 | } 41 | 42 | // sky cube 43 | skyImg, err := assets.ImportImage("resources/cubemap.png") 44 | if err == nil { 45 | geom := renderer.CreateSkyBox() 46 | geom.Transform(mgl32.Scale3D(10000, 10000, 10000)) 47 | skyNode := renderer.NewNode() 48 | skyNode.SetOrientation(mgl32.QuatRotate(1.57, mgl32.Vec3{0, 1, 0})) 49 | skyNode.Material = renderer.NewMaterial(renderer.NewTexture("diffuseMap", skyImg, false)) 50 | skyNode.RendererParams = renderer.NewRendererParams() 51 | skyNode.RendererParams.CullBackface = false 52 | skyNode.RendererParams.Unlit = true 53 | skyNode.Add(geom) 54 | gameEngine.AddSpatial(skyNode) 55 | } 56 | 57 | // Add some light to the scene 58 | ambientLight := renderer.NewLight(renderer.AMBIENT) 59 | ambientLight.Color = [3]float32{0.3, 0.3, 0.3} 60 | gameEngine.AddLight(ambientLight) 61 | 62 | // Create a red box geometry, attach to a node, add the node to the scenegraph 63 | boxGeometry := renderer.CreateBox(10, 10) 64 | boxGeometry.SetColor(color.NRGBA{254, 0, 0, 254}) 65 | boxNode := renderer.NewNode() 66 | boxNode.RendererParams = renderer.NewRendererParams() 67 | boxNode.RendererParams.CullBackface = false 68 | boxNode.Material = renderer.NewMaterial() 69 | boxNode.SetTranslation(mgl32.Vec3{30, 0}) 70 | boxNode.Add(boxGeometry) 71 | gameEngine.AddSpatial(boxNode) 72 | 73 | // make the box spin 74 | var angle float64 75 | gameEngine.AddUpdatable(engine.UpdatableFunc(func(dt float64) { 76 | angle += dt 77 | q := mgl32.QuatRotate(float32(angle), mgl32.Vec3{0, 1, 0}) 78 | boxNode.SetOrientation(q) 79 | })) 80 | 81 | // input/controller manager 82 | controllerManager := glfwController.NewControllerManager(glRenderer.Window) 83 | 84 | // camera + wasd controls 85 | camera := gameEngine.Camera() 86 | freeMoveActor := actor.NewFreeMoveActor(camera) 87 | freeMoveActor.Location = mgl32.Vec3{} 88 | mainController := controller.NewBasicMovementController(freeMoveActor, false) 89 | controllerManager.AddController(mainController.(glfwController.Controller)) 90 | gameEngine.AddUpdatable(freeMoveActor) 91 | 92 | //lock the cursor 93 | glRenderer.LockCursor(true) 94 | 95 | // custom key bindings 96 | customController := controller.CreateController() 97 | controllerManager.AddController(customController.(glfwController.Controller)) 98 | 99 | // close window and exit on escape 100 | customController.BindKeyAction(func() { 101 | glRenderer.Window.SetShouldClose(true) 102 | }, controller.KeyEscape, controller.Press) 103 | }) 104 | } 105 | -------------------------------------------------------------------------------- /glfwController/controller.go: -------------------------------------------------------------------------------- 1 | package glfwController 2 | 3 | import ( 4 | "github.com/go-gl/glfw/v3.1/glfw" 5 | "github.com/walesey/go-engine/controller" 6 | ) 7 | 8 | type Controller interface { 9 | controller.Controller 10 | KeyCallback(window *glfw.Window, key glfw.Key, scancode int, action glfw.Action, mods glfw.ModifierKey) 11 | MouseButtonCallback(window *glfw.Window, button glfw.MouseButton, action glfw.Action, mods glfw.ModifierKey) 12 | CursorPosCallback(window *glfw.Window, xpos, ypos float32) 13 | ScrollCallback(window *glfw.Window, xoffset, yoffset float32) 14 | } 15 | 16 | type KeyAction struct { 17 | key controller.Key 18 | action controller.Action 19 | } 20 | 21 | type MouseButtonAction struct { 22 | button controller.MouseButton 23 | action controller.Action 24 | } 25 | 26 | type ActionMap struct { 27 | keyAction func(key controller.Key, action controller.Action) 28 | mouseAction func(button controller.MouseButton, action controller.Action) 29 | keyActionMap map[KeyAction][]func() 30 | mouseButtonActionMap map[MouseButtonAction][]func() 31 | axisActions []func(xpos, ypos float32) 32 | scrollActions []func(xoffset, yoffset float32) 33 | } 34 | 35 | func NewActionMap() controller.Controller { 36 | am := &ActionMap{ 37 | keyAction: func(key controller.Key, action controller.Action) {}, 38 | mouseAction: func(button controller.MouseButton, action controller.Action) {}, 39 | keyActionMap: make(map[KeyAction][]func()), 40 | mouseButtonActionMap: make(map[MouseButtonAction][]func()), 41 | axisActions: make([]func(xpos, ypos float32), 0, 0), 42 | scrollActions: make([]func(xoffset, yoffset float32), 0, 0), 43 | } 44 | return am 45 | } 46 | 47 | func (am *ActionMap) SetKeyAction(function func(key controller.Key, action controller.Action)) { 48 | am.keyAction = function 49 | } 50 | 51 | func (am *ActionMap) SetMouseAction(function func(button controller.MouseButton, action controller.Action)) { 52 | am.mouseAction = function 53 | } 54 | 55 | //Bindings 56 | func (am *ActionMap) BindKeyAction(function func(), key controller.Key, action controller.Action) { 57 | ka := KeyAction{key, action} 58 | if m, ok := am.keyActionMap[ka]; ok { 59 | am.keyActionMap[ka] = append(m, function) 60 | } else { 61 | am.keyActionMap[ka] = []func(){function} 62 | } 63 | } 64 | 65 | func (am *ActionMap) BindMouseAction(function func(), button controller.MouseButton, action controller.Action) { 66 | mba := MouseButtonAction{button, action} 67 | if m, ok := am.mouseButtonActionMap[mba]; ok { 68 | am.mouseButtonActionMap[mba] = append(m, function) 69 | } else { 70 | am.mouseButtonActionMap[mba] = []func(){function} 71 | } 72 | } 73 | 74 | func (am *ActionMap) BindAxisAction(function func(xpos, ypos float32)) { 75 | am.axisActions = append(am.axisActions, function) 76 | } 77 | 78 | func (am *ActionMap) BindScrollAction(function func(xoffset, yoffset float32)) { 79 | am.scrollActions = append(am.scrollActions, function) 80 | } 81 | 82 | //Callbacks 83 | func (am *ActionMap) KeyCallback(window *glfw.Window, key glfw.Key, scancode int, action glfw.Action, mods glfw.ModifierKey) { 84 | am.keyAction(getKey(key), getAction(action)) 85 | ka := KeyAction{getKey(key), getAction(action)} 86 | if m, ok := am.keyActionMap[ka]; ok { 87 | for _, function := range m { 88 | function() 89 | } 90 | } 91 | } 92 | 93 | func (am *ActionMap) MouseButtonCallback(window *glfw.Window, button glfw.MouseButton, action glfw.Action, mods glfw.ModifierKey) { 94 | am.mouseAction(getMouseButton(button), getAction(action)) 95 | mba := MouseButtonAction{getMouseButton(button), getAction(action)} 96 | if m, ok := am.mouseButtonActionMap[mba]; ok { 97 | for _, function := range m { 98 | function() 99 | } 100 | } 101 | } 102 | 103 | func (am *ActionMap) CursorPosCallback(window *glfw.Window, xpos, ypos float32) { 104 | for _, action := range am.axisActions { 105 | action(xpos, ypos) 106 | } 107 | } 108 | 109 | func (am *ActionMap) ScrollCallback(window *glfw.Window, xoffset, yoffset float32) { 110 | for _, action := range am.scrollActions { 111 | action(xoffset, yoffset) 112 | } 113 | } 114 | -------------------------------------------------------------------------------- /glfwController/controllerManager.go: -------------------------------------------------------------------------------- 1 | package glfwController 2 | 3 | import ( 4 | "github.com/go-gl/glfw/v3.1/glfw" 5 | ) 6 | 7 | type ControllerManager struct { 8 | controllerList []Controller 9 | } 10 | 11 | //Key Callback 12 | func (c *ControllerManager) KeyCallback(window *glfw.Window, key glfw.Key, scancode int, action glfw.Action, mods glfw.ModifierKey) { 13 | for _, cont := range c.controllerList { 14 | cont.KeyCallback(window, key, scancode, action, mods) 15 | } 16 | } 17 | 18 | //Mouse click callback 19 | func (c *ControllerManager) MouseButtonCallback(window *glfw.Window, button glfw.MouseButton, action glfw.Action, mods glfw.ModifierKey) { 20 | for _, cont := range c.controllerList { 21 | cont.MouseButtonCallback(window, button, action, mods) 22 | } 23 | } 24 | 25 | //Mouse movement callback 26 | func (c *ControllerManager) CursorPosCallback(window *glfw.Window, xpos, ypos float64) { 27 | for _, cont := range c.controllerList { 28 | cont.CursorPosCallback(window, float32(xpos), float32(ypos)) 29 | } 30 | } 31 | 32 | //Mouse scrollwheel callback 33 | func (c *ControllerManager) ScrollCallback(window *glfw.Window, xoffset, yoffset float64) { 34 | for _, cont := range c.controllerList { 35 | cont.ScrollCallback(window, float32(xoffset), float32(yoffset)) 36 | } 37 | } 38 | 39 | func (c *ControllerManager) AddController(newCont Controller) { 40 | c.controllerList = append(c.controllerList, newCont) 41 | } 42 | 43 | func (c *ControllerManager) RemoveController(controller Controller) { 44 | for index, cont := range c.controllerList { 45 | if cont == controller { 46 | c.controllerList = append(c.controllerList[:index], c.controllerList[index+1:]...) 47 | } 48 | } 49 | } 50 | 51 | func NewControllerManager(window *glfw.Window) *ControllerManager { 52 | var controllerList []Controller 53 | c := &ControllerManager{controllerList} 54 | window.SetKeyCallback(c.KeyCallback) 55 | window.SetMouseButtonCallback(c.MouseButtonCallback) 56 | window.SetCursorPosCallback(c.CursorPosCallback) 57 | window.SetScrollCallback(c.ScrollCallback) 58 | return c 59 | } 60 | -------------------------------------------------------------------------------- /glfwController/controller_test.go: -------------------------------------------------------------------------------- 1 | package glfwController 2 | 3 | import ( 4 | "fmt" 5 | "os" 6 | "testing" 7 | 8 | "github.com/go-gl/glfw/v3.1/glfw" 9 | "github.com/stretchr/testify/assert" 10 | "github.com/walesey/go-engine/controller" 11 | ) 12 | 13 | type TestObject struct { 14 | testState bool 15 | } 16 | 17 | func (to *TestObject) testAction() { 18 | to.testState = true 19 | } 20 | func (to *TestObject) otherTestAction() { 21 | to.testState = false 22 | } 23 | 24 | func TestMain(m *testing.M) { 25 | os.Exit(m.Run()) 26 | } 27 | 28 | func TestActionMap(t *testing.T) { 29 | var controllerList []Controller 30 | manager := &ControllerManager{controllerList} 31 | 32 | to := TestObject{false} 33 | c := NewActionMap() 34 | c.BindKeyAction(to.testAction, controller.KeyW, controller.Press) 35 | c.BindKeyAction(to.otherTestAction, controller.KeyE, controller.Release) 36 | manager.AddController(c.(Controller)) 37 | 38 | fmt.Println("About to trigger custom actions") 39 | manager.KeyCallback(nil, glfw.KeyW, 0, glfw.Press, 0) 40 | assert.True(t, to.testState, "test state not triggered by key binding") 41 | manager.KeyCallback(nil, glfw.KeyE, 0, glfw.Release, 0) 42 | assert.False(t, to.testState, "test state not triggered by key binding") 43 | } 44 | -------------------------------------------------------------------------------- /glfwController/inputMappings.go: -------------------------------------------------------------------------------- 1 | package glfwController 2 | 3 | import ( 4 | "github.com/go-gl/glfw/v3.1/glfw" 5 | "github.com/walesey/go-engine/controller" 6 | ) 7 | 8 | func getJoystick(joystick glfw.Joystick) controller.Joystick { 9 | return controller.Joystick(joystick) 10 | } 11 | 12 | func getKey(key glfw.Key) controller.Key { 13 | return controller.Key(key) 14 | } 15 | 16 | func getModifierKey(key glfw.ModifierKey) controller.ModifierKey { 17 | return controller.ModifierKey(key) 18 | } 19 | 20 | func getMouseButton(mouseButton glfw.MouseButton) controller.MouseButton { 21 | return controller.MouseButton(mouseButton) 22 | } 23 | 24 | func getAction(mouseButton glfw.Action) controller.Action { 25 | return controller.Action(mouseButton) 26 | } 27 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/walesey/go-engine 2 | 3 | go 1.17 4 | 5 | require ( 6 | github.com/Invictus321/invictus321-countdown v0.0.0-20160831012427-1226ab952c2f // indirect 7 | github.com/aymerick/douceur v0.2.0 // indirect 8 | github.com/disintegration/imaging v1.6.2 // indirect 9 | github.com/go-gl/gl v0.0.0-20211210172815-726fda9656d6 // indirect 10 | github.com/go-gl/glfw v0.0.0-20211213063430-748e38ca8aec // indirect 11 | github.com/go-gl/mathgl v1.0.0 // indirect 12 | github.com/gorilla/css v1.0.0 // indirect 13 | github.com/pkg/errors v0.9.1 // indirect 14 | github.com/sirupsen/logrus v1.8.1 // indirect 15 | github.com/vova616/chipmunk v0.0.0-20180914035118-c3710bbc8933 // indirect 16 | github.com/walesey/go-fileserver v0.0.0-20180813233004-768d611d4fb8 // indirect 17 | golang.org/x/image v0.0.0-20191009234506-e7c1f5e7dbb8 // indirect 18 | golang.org/x/net v0.0.0-20220114011407-0dd24b26b47d // indirect 19 | golang.org/x/sys v0.0.0-20210423082822-04245dca01da // indirect 20 | ) 21 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/Invictus321/invictus321-countdown v0.0.0-20160831012427-1226ab952c2f h1:wXYsTpGdioJ2a8/Y7oU2tlqeFD3OsGQ9sLjk8ih2QVw= 2 | github.com/Invictus321/invictus321-countdown v0.0.0-20160831012427-1226ab952c2f/go.mod h1:Ry4yXbmmNb29MHV18ojJ4tqsLaE3cIy7tCLTzgryNvo= 3 | github.com/aymerick/douceur v0.2.0 h1:Mv+mAeH1Q+n9Fr+oyamOlAkUNPWPlA8PPGR0QAaYuPk= 4 | github.com/aymerick/douceur v0.2.0/go.mod h1:wlT5vV2O3h55X9m7iVYN0TBM0NH/MmbLnd30/FjWUq4= 5 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 6 | github.com/disintegration/imaging v1.6.2 h1:w1LecBlG2Lnp8B3jk5zSuNqd7b4DXhcjwek1ei82L+c= 7 | github.com/disintegration/imaging v1.6.2/go.mod h1:44/5580QXChDfwIclfc/PCwrr44amcmDAg8hxG0Ewe4= 8 | github.com/go-gl/gl v0.0.0-20211210172815-726fda9656d6 h1:zDw5v7qm4yH7N8C8uWd+8Ii9rROdgWxQuGoJ9WDXxfk= 9 | github.com/go-gl/gl v0.0.0-20211210172815-726fda9656d6/go.mod h1:9YTyiznxEY1fVinfM7RvRcjRHbw2xLBJ3AAGIT0I4Nw= 10 | github.com/go-gl/glfw v0.0.0-20211213063430-748e38ca8aec h1:um18JldLG6QwC9tj6mSfQnb+kor5aezfPPtq1GmHek0= 11 | github.com/go-gl/glfw v0.0.0-20211213063430-748e38ca8aec/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= 12 | github.com/go-gl/mathgl v1.0.0 h1:t9DznWJlXxxjeeKLIdovCOVJQk/GzDEL7h/h+Ro2B68= 13 | github.com/go-gl/mathgl v1.0.0/go.mod h1:yhpkQzEiH9yPyxDUGzkmgScbaBVlhC06qodikEM0ZwQ= 14 | github.com/gorilla/css v1.0.0 h1:BQqNyPTi50JCFMTw/b67hByjMVXZRwGha6wxVGkeihY= 15 | github.com/gorilla/css v1.0.0/go.mod h1:Dn721qIggHpt4+EFCcTLTU/vk5ySda2ReITrtgBl60c= 16 | github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= 17 | github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= 18 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 19 | github.com/sirupsen/logrus v1.8.1 h1:dJKuHgqk1NNQlqoA6BTlM1Wf9DOH3NBjQyu0h9+AZZE= 20 | github.com/sirupsen/logrus v1.8.1/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= 21 | github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= 22 | github.com/vova616/chipmunk v0.0.0-20180914035118-c3710bbc8933 h1:/0NyCSo8ffYPuc6KyanXavxaF3D1YE16PrrmtOnXqGk= 23 | github.com/vova616/chipmunk v0.0.0-20180914035118-c3710bbc8933/go.mod h1:ptbc/tNoFjKw1fVLyvtXQwISNbF7EJ3abLsKI+LFUEk= 24 | github.com/walesey/go-fileserver v0.0.0-20180813233004-768d611d4fb8 h1:emFMqxWXJgOmSdMUOIF9OL3ac8dL5ZRyuHMpYKOWntI= 25 | github.com/walesey/go-fileserver v0.0.0-20180813233004-768d611d4fb8/go.mod h1:tMqjDicUoKiNJfjBsRouCYUPo5tbOu7AoNCcEc1C22E= 26 | golang.org/x/image v0.0.0-20190321063152-3fc05d484e9f/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= 27 | golang.org/x/image v0.0.0-20191009234506-e7c1f5e7dbb8 h1:hVwzHzIUGRjiF7EcUjqNxk3NCfkPxbDKRdnNE1Rpg0U= 28 | golang.org/x/image v0.0.0-20191009234506-e7c1f5e7dbb8/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= 29 | golang.org/x/net v0.0.0-20220114011407-0dd24b26b47d h1:1n1fc535VhN8SYtD4cDUyNlfpAF2ROMM9+11equK3hs= 30 | golang.org/x/net v0.0.0-20220114011407-0dd24b26b47d/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= 31 | golang.org/x/sys v0.0.0-20191026070338-33540a1f6037 h1:YyJpGZS1sBuBCzLAR1VEpK193GlqGZbnPFnPV/5Rsb4= 32 | golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 33 | golang.org/x/sys v0.0.0-20210423082822-04245dca01da h1:b3NXsE2LusjYGGjL5bxEVZZORm/YEFFrWFjR8eFrw/c= 34 | golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 35 | golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= 36 | -------------------------------------------------------------------------------- /libs/freetype/AUTHORS: -------------------------------------------------------------------------------- 1 | # This is the official list of Freetype-Go authors for copyright purposes. 2 | # This file is distinct from the CONTRIBUTORS files. 3 | # See the latter for an explanation. 4 | # 5 | # Freetype-Go is derived from Freetype, which is written in C. The latter 6 | # is copyright 1996-2010 David Turner, Robert Wilhelm, and Werner Lemberg. 7 | 8 | # Names should be added to this file as 9 | # Name or Organization 10 | # The email address is not required for organizations. 11 | 12 | # Please keep the list sorted. 13 | 14 | Google Inc. 15 | Jeff R. Allen 16 | Rémy Oudompheng 17 | Roger Peppe 18 | Steven Edwards 19 | -------------------------------------------------------------------------------- /libs/freetype/CONTRIBUTORS: -------------------------------------------------------------------------------- 1 | # This is the official list of people who can contribute 2 | # (and typically have contributed) code to the Freetype-Go repository. 3 | # The AUTHORS file lists the copyright holders; this file 4 | # lists people. For example, Google employees are listed here 5 | # but not in AUTHORS, because Google holds the copyright. 6 | # 7 | # The submission process automatically checks to make sure 8 | # that people submitting code are listed in this file (by email address). 9 | # 10 | # Names should be added to this file only after verifying that 11 | # the individual or the individual's organization has agreed to 12 | # the appropriate Contributor License Agreement, found here: 13 | # 14 | # http://code.google.com/legal/individual-cla-v1.0.html 15 | # http://code.google.com/legal/corporate-cla-v1.0.html 16 | # 17 | # The agreement for individuals can be filled out on the web. 18 | # 19 | # When adding J Random Contributor's name to this file, 20 | # either J's name or J's organization's name should be 21 | # added to the AUTHORS file, depending on whether the 22 | # individual or corporate CLA was used. 23 | 24 | # Names should be added to this file like so: 25 | # Name 26 | 27 | # Please keep the list sorted. 28 | 29 | Andrew Gerrand 30 | Jeff R. Allen 31 | Nigel Tao 32 | Rémy Oudompheng 33 | Rob Pike 34 | Roger Peppe 35 | Russ Cox 36 | Steven Edwards 37 | -------------------------------------------------------------------------------- /libs/freetype/LICENSE: -------------------------------------------------------------------------------- 1 | Use of the Freetype-Go software is subject to your choice of exactly one of 2 | the following two licenses: 3 | * The FreeType License, which is similar to the original BSD license with 4 | an advertising clause, or 5 | * The GNU General Public License (GPL), version 2 or later. 6 | 7 | The text of these licenses are available in the licenses/ftl.txt and the 8 | licenses/gpl.txt files respectively. They are also available at 9 | http://freetype.sourceforge.net/license.html 10 | 11 | The Luxi fonts in the testdata directory are licensed separately. See the 12 | testdata/COPYING file for details. 13 | -------------------------------------------------------------------------------- /libs/freetype/README: -------------------------------------------------------------------------------- 1 | The Freetype font rasterizer in the Go programming language. 2 | 3 | To download and install from source: 4 | $ go get github.com/golang/freetype 5 | 6 | It is an incomplete port: 7 | * It only supports TrueType fonts, and not Type 1 fonts nor bitmap fonts. 8 | * It only supports the Unicode encoding. 9 | 10 | There are also some implementation differences: 11 | * It uses a 26.6 fixed point co-ordinate system everywhere internally, 12 | as opposed to the original Freetype's mix of 26.6 (or 10.6 for 16-bit 13 | systems) in some places, and 24.8 in the "smooth" rasterizer. 14 | 15 | Freetype-Go is derived from Freetype, which is written in C. Freetype is 16 | copyright 1996-2010 David Turner, Robert Wilhelm, and Werner Lemberg. 17 | Freetype-Go is copyright The Freetype-Go Authors, who are listed in the 18 | AUTHORS file. 19 | 20 | Unless otherwise noted, the Freetype-Go source files are distributed 21 | under the BSD-style license found in the LICENSE file. 22 | -------------------------------------------------------------------------------- /libs/freetype/cmd/print-glyph-points/main.c: -------------------------------------------------------------------------------- 1 | /* 2 | gcc main.c -I/usr/include/freetype2 -lfreetype && ./a.out 12 ../../testdata/luxisr.ttf with_hinting 3 | */ 4 | 5 | #include 6 | #include 7 | #include FT_FREETYPE_H 8 | 9 | void usage(char** argv) { 10 | fprintf(stderr, "usage: %s font_size font_file [with_hinting|sans_hinting]\n", argv[0]); 11 | } 12 | 13 | int main(int argc, char** argv) { 14 | FT_Error error; 15 | FT_Library library; 16 | FT_Face face; 17 | FT_Glyph_Metrics* m; 18 | FT_Outline* o; 19 | FT_Int major, minor, patch; 20 | int i, j, font_size, no_hinting; 21 | 22 | if (argc != 4) { 23 | usage(argv); 24 | return 1; 25 | } 26 | font_size = atoi(argv[1]); 27 | if (font_size <= 0) { 28 | fprintf(stderr, "invalid font_size\n"); 29 | usage(argv); 30 | return 1; 31 | } 32 | if (!strcmp(argv[3], "with_hinting")) { 33 | no_hinting = 0; 34 | } else if (!strcmp(argv[3], "sans_hinting")) { 35 | no_hinting = 1; 36 | } else { 37 | fprintf(stderr, "neither \"with_hinting\" nor \"sans_hinting\"\n"); 38 | usage(argv); 39 | return 1; 40 | }; 41 | error = FT_Init_FreeType(&library); 42 | if (error) { 43 | fprintf(stderr, "FT_Init_FreeType: error #%d\n", error); 44 | return 1; 45 | } 46 | FT_Library_Version(library, &major, &minor, &patch); 47 | printf("freetype version %d.%d.%d\n", major, minor, patch); 48 | error = FT_New_Face(library, argv[2], 0, &face); 49 | if (error) { 50 | fprintf(stderr, "FT_New_Face: error #%d\n", error); 51 | return 1; 52 | } 53 | error = FT_Set_Char_Size(face, 0, font_size*64, 0, 0); 54 | if (error) { 55 | fprintf(stderr, "FT_Set_Char_Size: error #%d\n", error); 56 | return 1; 57 | } 58 | for (i = 0; i < face->num_glyphs; i++) { 59 | error = FT_Load_Glyph(face, i, no_hinting ? FT_LOAD_NO_HINTING : FT_LOAD_DEFAULT); 60 | if (error) { 61 | fprintf(stderr, "FT_Load_Glyph: glyph %d: error #%d\n", i, error); 62 | return 1; 63 | } 64 | if (face->glyph->format != FT_GLYPH_FORMAT_OUTLINE) { 65 | fprintf(stderr, "glyph format for glyph %d is not FT_GLYPH_FORMAT_OUTLINE\n", i); 66 | return 1; 67 | } 68 | m = &face->glyph->metrics; 69 | /* Print what Go calls the AdvanceWidth, and then: XMin, YMin, XMax, YMax. */ 70 | printf("%ld %ld %ld %ld %ld;", 71 | m->horiAdvance, 72 | m->horiBearingX, 73 | m->horiBearingY - m->height, 74 | m->horiBearingX + m->width, 75 | m->horiBearingY); 76 | /* Print the glyph points. */ 77 | o = &face->glyph->outline; 78 | for (j = 0; j < o->n_points; j++) { 79 | if (j != 0) { 80 | printf(", "); 81 | } 82 | printf("%ld %ld %d", o->points[j].x, o->points[j].y, o->tags[j] & 0x01); 83 | } 84 | printf("\n"); 85 | } 86 | return 0; 87 | } 88 | -------------------------------------------------------------------------------- /libs/freetype/example/drawer/main.go: -------------------------------------------------------------------------------- 1 | // Copyright 2015 The Freetype-Go Authors. All rights reserved. 2 | // Use of this source code is governed by your choice of either the 3 | // FreeType License or the GNU General Public License version 2 (or 4 | // any later version), both of which can be found in the LICENSE file. 5 | 6 | // +build ignore 7 | // 8 | // This build tag means that "go install github.com/walesey/go-engine/libs/freetype/..." 9 | // doesn't install this example program. Use "go run main.go" to run it. 10 | 11 | package main 12 | 13 | import ( 14 | "bufio" 15 | "flag" 16 | "fmt" 17 | "image" 18 | "image/color" 19 | "image/draw" 20 | "image/png" 21 | "io/ioutil" 22 | "log" 23 | "math" 24 | "os" 25 | 26 | "github.com/walesey/go-engine/libs/freetype/truetype" 27 | "golang.org/x/image/font" 28 | "golang.org/x/image/math/fixed" 29 | ) 30 | 31 | var ( 32 | dpi = flag.Float64("dpi", 72, "screen resolution in Dots Per Inch") 33 | fontfile = flag.String("fontfile", "../../testdata/luxisr.ttf", "filename of the ttf font") 34 | hinting = flag.String("hinting", "none", "none | full") 35 | size = flag.Float64("size", 12, "font size in points") 36 | spacing = flag.Float64("spacing", 1.5, "line spacing (e.g. 2 means double spaced)") 37 | wonb = flag.Bool("whiteonblack", false, "white text on a black background") 38 | ) 39 | 40 | const title = "Jabberwocky" 41 | 42 | var text = []string{ 43 | "’Twas brillig, and the slithy toves", 44 | "Did gyre and gimble in the wabe;", 45 | "All mimsy were the borogoves,", 46 | "And the mome raths outgrabe.", 47 | "", 48 | "“Beware the Jabberwock, my son!", 49 | "The jaws that bite, the claws that catch!", 50 | "Beware the Jubjub bird, and shun", 51 | "The frumious Bandersnatch!”", 52 | "", 53 | "He took his vorpal sword in hand:", 54 | "Long time the manxome foe he sought—", 55 | "So rested he by the Tumtum tree,", 56 | "And stood awhile in thought.", 57 | "", 58 | "And as in uffish thought he stood,", 59 | "The Jabberwock, with eyes of flame,", 60 | "Came whiffling through the tulgey wood,", 61 | "And burbled as it came!", 62 | "", 63 | "One, two! One, two! and through and through", 64 | "The vorpal blade went snicker-snack!", 65 | "He left it dead, and with its head", 66 | "He went galumphing back.", 67 | "", 68 | "“And hast thou slain the Jabberwock?", 69 | "Come to my arms, my beamish boy!", 70 | "O frabjous day! Callooh! Callay!”", 71 | "He chortled in his joy.", 72 | "", 73 | "’Twas brillig, and the slithy toves", 74 | "Did gyre and gimble in the wabe;", 75 | "All mimsy were the borogoves,", 76 | "And the mome raths outgrabe.", 77 | } 78 | 79 | func main() { 80 | flag.Parse() 81 | 82 | // Read the font data. 83 | fontBytes, err := ioutil.ReadFile(*fontfile) 84 | if err != nil { 85 | log.Println(err) 86 | return 87 | } 88 | f, err := truetype.Parse(fontBytes) 89 | if err != nil { 90 | log.Println(err) 91 | return 92 | } 93 | 94 | // Draw the background and the guidelines. 95 | fg, bg := image.Black, image.White 96 | ruler := color.RGBA{0xdd, 0xdd, 0xdd, 0xff} 97 | if *wonb { 98 | fg, bg = image.White, image.Black 99 | ruler = color.RGBA{0x22, 0x22, 0x22, 0xff} 100 | } 101 | const imgW, imgH = 640, 480 102 | rgba := image.NewRGBA(image.Rect(0, 0, imgW, imgH)) 103 | draw.Draw(rgba, rgba.Bounds(), bg, image.ZP, draw.Src) 104 | for i := 0; i < 200; i++ { 105 | rgba.Set(10, 10+i, ruler) 106 | rgba.Set(10+i, 10, ruler) 107 | } 108 | 109 | // Draw the text. 110 | h := font.HintingNone 111 | switch *hinting { 112 | case "full": 113 | h = font.HintingFull 114 | } 115 | d := &font.Drawer{ 116 | Dst: rgba, 117 | Src: fg, 118 | Face: truetype.NewFace(f, &truetype.Options{ 119 | Size: *size, 120 | DPI: *dpi, 121 | Hinting: h, 122 | }), 123 | } 124 | y := 10 + int(math.Ceil(*size**dpi/72)) 125 | dy := int(math.Ceil(*size * *spacing * *dpi / 72)) 126 | d.Dot = fixed.Point26_6{ 127 | X: (fixed.I(imgW) - d.MeasureString(title)) / 2, 128 | Y: fixed.I(y), 129 | } 130 | d.DrawString(title) 131 | y += dy 132 | for _, s := range text { 133 | d.Dot = fixed.P(10, y) 134 | d.DrawString(s) 135 | y += dy 136 | } 137 | 138 | // Save that RGBA image to disk. 139 | outFile, err := os.Create("out.png") 140 | if err != nil { 141 | log.Println(err) 142 | os.Exit(1) 143 | } 144 | defer outFile.Close() 145 | b := bufio.NewWriter(outFile) 146 | err = png.Encode(b, rgba) 147 | if err != nil { 148 | log.Println(err) 149 | os.Exit(1) 150 | } 151 | err = b.Flush() 152 | if err != nil { 153 | log.Println(err) 154 | os.Exit(1) 155 | } 156 | fmt.Println("Wrote out.png OK.") 157 | } 158 | -------------------------------------------------------------------------------- /libs/freetype/example/drawer/out.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/walesey/go-engine/e229e0e7d42b1d984bf7803a62878acfe39bde9d/libs/freetype/example/drawer/out.png -------------------------------------------------------------------------------- /libs/freetype/example/freetype/main.go: -------------------------------------------------------------------------------- 1 | // Copyright 2010 The Freetype-Go Authors. All rights reserved. 2 | // Use of this source code is governed by your choice of either the 3 | // FreeType License or the GNU General Public License version 2 (or 4 | // any later version), both of which can be found in the LICENSE file. 5 | 6 | // +build ignore 7 | // 8 | // This build tag means that "go install github.com/walesey/go-engine/libs/freetype/..." 9 | // doesn't install this example program. Use "go run main.go" to run it. 10 | 11 | package main 12 | 13 | import ( 14 | "bufio" 15 | "flag" 16 | "fmt" 17 | "image" 18 | "image/draw" 19 | "image/png" 20 | "io/ioutil" 21 | "log" 22 | "os" 23 | 24 | "github.com/walesey/go-engine/libs/freetype" 25 | "golang.org/x/image/font" 26 | "golang.org/x/image/math/fixed" 27 | ) 28 | 29 | var ( 30 | dpi = flag.Float64("dpi", 75, "screen resolution in Dots Per Inch") 31 | fontfile = flag.String("fontfile", "../../testdata/luxisr.ttf", "filename of the ttf font") 32 | hinting = flag.String("hinting", "none", "none | full") 33 | size = flag.Float64("size", 12, "font size in points") 34 | spacing = flag.Float64("spacing", 1.5, "line spacing (e.g. 2 means double spaced)") 35 | wonb = flag.Bool("whiteonblack", false, "white text on a black background") 36 | ) 37 | 38 | var text = []string{ 39 | "’Twas Twas Twas Twas www", 40 | } 41 | 42 | func main() { 43 | flag.Parse() 44 | 45 | // Read the font data. 46 | fontBytes, err := ioutil.ReadFile(*fontfile) 47 | if err != nil { 48 | log.Println(err) 49 | return 50 | } 51 | f, err := freetype.ParseFont(fontBytes) 52 | if err != nil { 53 | log.Println(err) 54 | return 55 | } 56 | 57 | // Initialize the context. 58 | fg, bg := image.Black, image.White 59 | if *wonb { 60 | fg, bg = image.White, image.Black 61 | } 62 | c := freetype.NewContext() 63 | c.SetDPI(*dpi) 64 | c.SetFont(f) 65 | c.SetFontSize(*size) 66 | c.SetSrc(fg) 67 | switch *hinting { 68 | default: 69 | c.SetHinting(font.HintingNone) 70 | case "full": 71 | c.SetHinting(font.HintingFull) 72 | } 73 | 74 | var width fixed.Int26_6 75 | var height fixed.Int26_6 76 | for _, s := range text { 77 | dimensions, _ := c.StringDimensions(s) 78 | height = height + dimensions.Y 79 | if dimensions.X > width { 80 | width = dimensions.X 81 | } 82 | } 83 | imgWidth := int(width >> 6) 84 | imgHeight := int(height >> 6) 85 | fmt.Println(imgWidth, imgHeight) 86 | rgba := image.NewRGBA(image.Rect(0, 0, imgWidth, imgHeight)) 87 | draw.Draw(rgba, rgba.Bounds(), bg, image.ZP, draw.Src) 88 | c.SetClip(rgba.Bounds()) 89 | c.SetDst(rgba) 90 | 91 | // Draw the text. 92 | pt := freetype.Pt(0, int(c.PointToFixed(*size)>>6)) 93 | for _, s := range text { 94 | _, err = c.DrawString(s, pt) 95 | if err != nil { 96 | log.Println(err) 97 | return 98 | } 99 | pt.Y += c.PointToFixed(*size * *spacing) 100 | } 101 | 102 | // Save that RGBA image to disk. 103 | outFile, err := os.Create("out.png") 104 | if err != nil { 105 | log.Println(err) 106 | os.Exit(1) 107 | } 108 | defer outFile.Close() 109 | b := bufio.NewWriter(outFile) 110 | err = png.Encode(b, rgba) 111 | if err != nil { 112 | log.Println(err) 113 | os.Exit(1) 114 | } 115 | err = b.Flush() 116 | if err != nil { 117 | log.Println(err) 118 | os.Exit(1) 119 | } 120 | fmt.Println("Wrote out.png OK.") 121 | } 122 | -------------------------------------------------------------------------------- /libs/freetype/example/freetype/out.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/walesey/go-engine/e229e0e7d42b1d984bf7803a62878acfe39bde9d/libs/freetype/example/freetype/out.png -------------------------------------------------------------------------------- /libs/freetype/example/gamma/main.go: -------------------------------------------------------------------------------- 1 | // Copyright 2010 The Freetype-Go Authors. All rights reserved. 2 | // Use of this source code is governed by your choice of either the 3 | // FreeType License or the GNU General Public License version 2 (or 4 | // any later version), both of which can be found in the LICENSE file. 5 | 6 | // +build ignore 7 | // 8 | // This build tag means that "go install github.com/walesey/go-engine/libs/freetype/..." 9 | // doesn't install this example program. Use "go run main.go" to run it. 10 | 11 | package main 12 | 13 | import ( 14 | "bufio" 15 | "fmt" 16 | "image" 17 | "image/draw" 18 | "image/png" 19 | "log" 20 | "os" 21 | 22 | "github.com/walesey/go-engine/libs/freetype/raster" 23 | "golang.org/x/image/math/fixed" 24 | ) 25 | 26 | func p(x, y int) fixed.Point26_6 { 27 | return fixed.Point26_6{ 28 | X: fixed.Int26_6(x * 64), 29 | Y: fixed.Int26_6(y * 64), 30 | } 31 | } 32 | 33 | func main() { 34 | // Draw a rounded corner that is one pixel wide. 35 | r := raster.NewRasterizer(50, 50) 36 | r.Start(p(5, 5)) 37 | r.Add1(p(5, 25)) 38 | r.Add2(p(5, 45), p(25, 45)) 39 | r.Add1(p(45, 45)) 40 | r.Add1(p(45, 44)) 41 | r.Add1(p(26, 44)) 42 | r.Add2(p(6, 44), p(6, 24)) 43 | r.Add1(p(6, 5)) 44 | r.Add1(p(5, 5)) 45 | 46 | // Rasterize that curve multiple times at different gammas. 47 | const ( 48 | w = 600 49 | h = 200 50 | ) 51 | rgba := image.NewRGBA(image.Rect(0, 0, w, h)) 52 | draw.Draw(rgba, image.Rect(0, 0, w, h/2), image.Black, image.ZP, draw.Src) 53 | draw.Draw(rgba, image.Rect(0, h/2, w, h), image.White, image.ZP, draw.Src) 54 | mask := image.NewAlpha(image.Rect(0, 0, 50, 50)) 55 | painter := raster.NewAlphaSrcPainter(mask) 56 | gammas := []float64{1.0 / 10.0, 1.0 / 3.0, 1.0 / 2.0, 2.0 / 3.0, 4.0 / 5.0, 1.0, 5.0 / 4.0, 3.0 / 2.0, 2.0, 3.0, 10.0} 57 | for i, g := range gammas { 58 | draw.Draw(mask, mask.Bounds(), image.Transparent, image.ZP, draw.Src) 59 | r.Rasterize(raster.NewGammaCorrectionPainter(painter, g)) 60 | x, y := 50*i+25, 25 61 | draw.DrawMask(rgba, image.Rect(x, y, x+50, y+50), image.White, image.ZP, mask, image.ZP, draw.Over) 62 | y += 100 63 | draw.DrawMask(rgba, image.Rect(x, y, x+50, y+50), image.Black, image.ZP, mask, image.ZP, draw.Over) 64 | } 65 | 66 | // Save that RGBA image to disk. 67 | outFile, err := os.Create("out.png") 68 | if err != nil { 69 | log.Println(err) 70 | os.Exit(1) 71 | } 72 | defer outFile.Close() 73 | b := bufio.NewWriter(outFile) 74 | err = png.Encode(b, rgba) 75 | if err != nil { 76 | log.Println(err) 77 | os.Exit(1) 78 | } 79 | err = b.Flush() 80 | if err != nil { 81 | log.Println(err) 82 | os.Exit(1) 83 | } 84 | fmt.Println("Wrote out.png OK.") 85 | } 86 | -------------------------------------------------------------------------------- /libs/freetype/example/raster/main.go: -------------------------------------------------------------------------------- 1 | // Copyright 2010 The Freetype-Go Authors. All rights reserved. 2 | // Use of this source code is governed by your choice of either the 3 | // FreeType License or the GNU General Public License version 2 (or 4 | // any later version), both of which can be found in the LICENSE file. 5 | 6 | // +build ignore 7 | // 8 | // This build tag means that "go install github.com/walesey/go-engine/libs/freetype/..." 9 | // doesn't install this example program. Use "go run main.go" to run it. 10 | 11 | package main 12 | 13 | import ( 14 | "bufio" 15 | "fmt" 16 | "image" 17 | "image/color" 18 | "image/draw" 19 | "image/png" 20 | "log" 21 | "os" 22 | 23 | "github.com/walesey/go-engine/libs/freetype/raster" 24 | "golang.org/x/image/math/fixed" 25 | ) 26 | 27 | type node struct { 28 | x, y, degree int 29 | } 30 | 31 | // These contours "outside" and "inside" are from the 'A' glyph from the Droid 32 | // Serif Regular font. 33 | 34 | var outside = []node{ 35 | node{414, 489, 1}, 36 | node{336, 274, 2}, 37 | node{327, 250, 0}, 38 | node{322, 226, 2}, 39 | node{317, 203, 0}, 40 | node{317, 186, 2}, 41 | node{317, 134, 0}, 42 | node{350, 110, 2}, 43 | node{384, 86, 0}, 44 | node{453, 86, 1}, 45 | node{500, 86, 1}, 46 | node{500, 0, 1}, 47 | node{0, 0, 1}, 48 | node{0, 86, 1}, 49 | node{39, 86, 2}, 50 | node{69, 86, 0}, 51 | node{90, 92, 2}, 52 | node{111, 99, 0}, 53 | node{128, 117, 2}, 54 | node{145, 135, 0}, 55 | node{160, 166, 2}, 56 | node{176, 197, 0}, 57 | node{195, 246, 1}, 58 | node{649, 1462, 1}, 59 | node{809, 1462, 1}, 60 | node{1272, 195, 2}, 61 | node{1284, 163, 0}, 62 | node{1296, 142, 2}, 63 | node{1309, 121, 0}, 64 | node{1326, 108, 2}, 65 | node{1343, 96, 0}, 66 | node{1365, 91, 2}, 67 | node{1387, 86, 0}, 68 | node{1417, 86, 1}, 69 | node{1444, 86, 1}, 70 | node{1444, 0, 1}, 71 | node{881, 0, 1}, 72 | node{881, 86, 1}, 73 | node{928, 86, 2}, 74 | node{1051, 86, 0}, 75 | node{1051, 184, 2}, 76 | node{1051, 201, 0}, 77 | node{1046, 219, 2}, 78 | node{1042, 237, 0}, 79 | node{1034, 260, 1}, 80 | node{952, 489, 1}, 81 | node{414, 489, -1}, 82 | } 83 | 84 | var inside = []node{ 85 | node{686, 1274, 1}, 86 | node{453, 592, 1}, 87 | node{915, 592, 1}, 88 | node{686, 1274, -1}, 89 | } 90 | 91 | func p(n node) fixed.Point26_6 { 92 | x, y := 20+n.x/4, 380-n.y/4 93 | return fixed.Point26_6{ 94 | X: fixed.Int26_6(x << 6), 95 | Y: fixed.Int26_6(y << 6), 96 | } 97 | } 98 | 99 | func contour(r *raster.Rasterizer, ns []node) { 100 | if len(ns) == 0 { 101 | return 102 | } 103 | i := 0 104 | r.Start(p(ns[i])) 105 | for { 106 | switch ns[i].degree { 107 | case -1: 108 | // -1 signifies end-of-contour. 109 | return 110 | case 1: 111 | i += 1 112 | r.Add1(p(ns[i])) 113 | case 2: 114 | i += 2 115 | r.Add2(p(ns[i-1]), p(ns[i])) 116 | default: 117 | panic("bad degree") 118 | } 119 | } 120 | } 121 | 122 | func showNodes(m *image.RGBA, ns []node) { 123 | for _, n := range ns { 124 | p := p(n) 125 | x, y := int(p.X)/64, int(p.Y)/64 126 | if !(image.Point{x, y}).In(m.Bounds()) { 127 | continue 128 | } 129 | var c color.Color 130 | switch n.degree { 131 | case 0: 132 | c = color.RGBA{0, 255, 255, 255} 133 | case 1: 134 | c = color.RGBA{255, 0, 0, 255} 135 | case 2: 136 | c = color.RGBA{255, 0, 0, 255} 137 | } 138 | if c != nil { 139 | m.Set(x, y, c) 140 | } 141 | } 142 | } 143 | 144 | func main() { 145 | // Rasterize the contours to a mask image. 146 | const ( 147 | w = 400 148 | h = 400 149 | ) 150 | r := raster.NewRasterizer(w, h) 151 | contour(r, outside) 152 | contour(r, inside) 153 | mask := image.NewAlpha(image.Rect(0, 0, w, h)) 154 | p := raster.NewAlphaSrcPainter(mask) 155 | r.Rasterize(p) 156 | 157 | // Draw the mask image (in gray) onto an RGBA image. 158 | rgba := image.NewRGBA(image.Rect(0, 0, w, h)) 159 | gray := image.NewUniform(color.Alpha{0x1f}) 160 | draw.Draw(rgba, rgba.Bounds(), image.Black, image.ZP, draw.Src) 161 | draw.DrawMask(rgba, rgba.Bounds(), gray, image.ZP, mask, image.ZP, draw.Over) 162 | showNodes(rgba, outside) 163 | showNodes(rgba, inside) 164 | 165 | // Save that RGBA image to disk. 166 | outFile, err := os.Create("out.png") 167 | if err != nil { 168 | log.Println(err) 169 | os.Exit(1) 170 | } 171 | defer outFile.Close() 172 | b := bufio.NewWriter(outFile) 173 | err = png.Encode(b, rgba) 174 | if err != nil { 175 | log.Println(err) 176 | os.Exit(1) 177 | } 178 | err = b.Flush() 179 | if err != nil { 180 | log.Println(err) 181 | os.Exit(1) 182 | } 183 | fmt.Println("Wrote out.png OK.") 184 | } 185 | -------------------------------------------------------------------------------- /libs/freetype/example/round/main.go: -------------------------------------------------------------------------------- 1 | // Copyright 2010 The Freetype-Go Authors. All rights reserved. 2 | // Use of this source code is governed by your choice of either the 3 | // FreeType License or the GNU General Public License version 2 (or 4 | // any later version), both of which can be found in the LICENSE file. 5 | 6 | // +build ignore 7 | // 8 | // This build tag means that "go install github.com/walesey/go-engine/libs/freetype/..." 9 | // doesn't install this example program. Use "go run main.go" to run it. 10 | 11 | // This program visualizes the quadratic approximation to the circle, used to 12 | // implement round joins when stroking paths. The approximation is used in the 13 | // stroking code for arcs between 0 and 45 degrees, but is visualized here 14 | // between 0 and 90 degrees. The discrepancy between the approximation and the 15 | // true circle is clearly visible at angles above 65 degrees. 16 | package main 17 | 18 | import ( 19 | "bufio" 20 | "fmt" 21 | "image" 22 | "image/color" 23 | "image/draw" 24 | "image/png" 25 | "log" 26 | "math" 27 | "os" 28 | 29 | "github.com/walesey/go-engine/libs/freetype/raster" 30 | "golang.org/x/image/math/fixed" 31 | ) 32 | 33 | // pDot returns the dot product p·q. 34 | func pDot(p, q fixed.Point26_6) fixed.Int52_12 { 35 | px, py := int64(p.X), int64(p.Y) 36 | qx, qy := int64(q.X), int64(q.Y) 37 | return fixed.Int52_12(px*qx + py*qy) 38 | } 39 | 40 | func main() { 41 | const ( 42 | n = 17 43 | r = 64 * 80 44 | ) 45 | s := fixed.Int26_6(r * math.Sqrt(2) / 2) 46 | t := fixed.Int26_6(r * math.Tan(math.Pi/8)) 47 | 48 | m := image.NewRGBA(image.Rect(0, 0, 800, 600)) 49 | draw.Draw(m, m.Bounds(), image.NewUniform(color.RGBA{63, 63, 63, 255}), image.ZP, draw.Src) 50 | mp := raster.NewRGBAPainter(m) 51 | mp.SetColor(image.Black) 52 | z := raster.NewRasterizer(800, 600) 53 | 54 | for i := 0; i < n; i++ { 55 | cx := fixed.Int26_6(6400 + 12800*(i%4)) 56 | cy := fixed.Int26_6(640 + 8000*(i/4)) 57 | c := fixed.Point26_6{X: cx, Y: cy} 58 | theta := math.Pi * (0.5 + 0.5*float64(i)/(n-1)) 59 | dx := fixed.Int26_6(r * math.Cos(theta)) 60 | dy := fixed.Int26_6(r * math.Sin(theta)) 61 | d := fixed.Point26_6{X: dx, Y: dy} 62 | // Draw a quarter-circle approximated by two quadratic segments, 63 | // with each segment spanning 45 degrees. 64 | z.Start(c) 65 | z.Add1(c.Add(fixed.Point26_6{X: r, Y: 0})) 66 | z.Add2(c.Add(fixed.Point26_6{X: r, Y: t}), c.Add(fixed.Point26_6{X: s, Y: s})) 67 | z.Add2(c.Add(fixed.Point26_6{X: t, Y: r}), c.Add(fixed.Point26_6{X: 0, Y: r})) 68 | // Add another quadratic segment whose angle ranges between 0 and 90 69 | // degrees. For an explanation of the magic constants 128, 150, 181 and 70 | // 256, read the comments in the freetype/raster package. 71 | dot := 256 * pDot(d, fixed.Point26_6{X: 0, Y: r}) / (r * r) 72 | multiple := fixed.Int26_6(150-(150-128)*(dot-181)/(256-181)) >> 2 73 | z.Add2(c.Add(fixed.Point26_6{X: dx, Y: r + dy}.Mul(multiple)), c.Add(d)) 74 | // Close the curve. 75 | z.Add1(c) 76 | } 77 | z.Rasterize(mp) 78 | 79 | for i := 0; i < n; i++ { 80 | cx := fixed.Int26_6(6400 + 12800*(i%4)) 81 | cy := fixed.Int26_6(640 + 8000*(i/4)) 82 | for j := 0; j < n; j++ { 83 | theta := math.Pi * float64(j) / (n - 1) 84 | dx := fixed.Int26_6(r * math.Cos(theta)) 85 | dy := fixed.Int26_6(r * math.Sin(theta)) 86 | m.Set(int((cx+dx)/64), int((cy+dy)/64), color.RGBA{255, 255, 0, 255}) 87 | } 88 | } 89 | 90 | // Save that RGBA image to disk. 91 | outFile, err := os.Create("out.png") 92 | if err != nil { 93 | log.Println(err) 94 | os.Exit(1) 95 | } 96 | defer outFile.Close() 97 | b := bufio.NewWriter(outFile) 98 | err = png.Encode(b, m) 99 | if err != nil { 100 | log.Println(err) 101 | os.Exit(1) 102 | } 103 | err = b.Flush() 104 | if err != nil { 105 | log.Println(err) 106 | os.Exit(1) 107 | } 108 | fmt.Println("Wrote out.png OK.") 109 | } 110 | -------------------------------------------------------------------------------- /libs/freetype/example/truetype/main.go: -------------------------------------------------------------------------------- 1 | // Copyright 2010 The Freetype-Go Authors. All rights reserved. 2 | // Use of this source code is governed by your choice of either the 3 | // FreeType License or the GNU General Public License version 2 (or 4 | // any later version), both of which can be found in the LICENSE file. 5 | 6 | // +build ignore 7 | // 8 | // This build tag means that "go install github.com/walesey/go-engine/libs/freetype/..." 9 | // doesn't install this example program. Use "go run main.go" to run it. 10 | 11 | package main 12 | 13 | import ( 14 | "flag" 15 | "fmt" 16 | "io/ioutil" 17 | "log" 18 | 19 | "github.com/walesey/go-engine/libs/freetype/truetype" 20 | "golang.org/x/image/font" 21 | "golang.org/x/image/math/fixed" 22 | ) 23 | 24 | var fontfile = flag.String("fontfile", "../../testdata/luxisr.ttf", "filename of the ttf font") 25 | 26 | func printBounds(b fixed.Rectangle26_6) { 27 | fmt.Printf("Min.X:%d Min.Y:%d Max.X:%d Max.Y:%d\n", b.Min.X, b.Min.Y, b.Max.X, b.Max.Y) 28 | } 29 | 30 | func printGlyph(g *truetype.GlyphBuf) { 31 | printBounds(g.Bounds) 32 | fmt.Print("Points:\n---\n") 33 | e := 0 34 | for i, p := range g.Points { 35 | fmt.Printf("%4d, %4d", p.X, p.Y) 36 | if p.Flags&0x01 != 0 { 37 | fmt.Print(" on\n") 38 | } else { 39 | fmt.Print(" off\n") 40 | } 41 | if i+1 == int(g.Ends[e]) { 42 | fmt.Print("---\n") 43 | e++ 44 | } 45 | } 46 | } 47 | 48 | func main() { 49 | flag.Parse() 50 | fmt.Printf("Loading fontfile %q\n", *fontfile) 51 | b, err := ioutil.ReadFile(*fontfile) 52 | if err != nil { 53 | log.Println(err) 54 | return 55 | } 56 | f, err := truetype.Parse(b) 57 | if err != nil { 58 | log.Println(err) 59 | return 60 | } 61 | fupe := fixed.Int26_6(f.FUnitsPerEm()) 62 | printBounds(f.Bounds(fupe)) 63 | fmt.Printf("FUnitsPerEm:%d\n\n", fupe) 64 | 65 | c0, c1 := 'A', 'V' 66 | 67 | i0 := f.Index(c0) 68 | hm := f.HMetric(fupe, i0) 69 | g := &truetype.GlyphBuf{} 70 | err = g.Load(f, fupe, i0, font.HintingNone) 71 | if err != nil { 72 | log.Println(err) 73 | return 74 | } 75 | fmt.Printf("'%c' glyph\n", c0) 76 | fmt.Printf("AdvanceWidth:%d LeftSideBearing:%d\n", hm.AdvanceWidth, hm.LeftSideBearing) 77 | printGlyph(g) 78 | i1 := f.Index(c1) 79 | fmt.Printf("\n'%c', '%c' Kern:%d\n", c0, c1, f.Kern(fupe, i0, i1)) 80 | } 81 | -------------------------------------------------------------------------------- /libs/freetype/licenses/ftl.txt: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/walesey/go-engine/e229e0e7d42b1d984bf7803a62878acfe39bde9d/libs/freetype/licenses/ftl.txt -------------------------------------------------------------------------------- /main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "os" 6 | "runtime" 7 | 8 | "github.com/walesey/go-engine/controller" 9 | "github.com/walesey/go-engine/editor" 10 | "github.com/walesey/go-engine/glfwController" 11 | ) 12 | 13 | func init() { 14 | // Use all cpu cores 15 | runtime.GOMAXPROCS(runtime.NumCPU()) 16 | //Set default glfw controller 17 | controller.SetDefaultConstructor(glfwController.NewActionMap) 18 | } 19 | 20 | func main() { 21 | assetDir := "." 22 | if len(os.Args) >= 2 { 23 | assetDir = os.Args[1] 24 | } 25 | 26 | fmt.Println("Using assetDir: ", assetDir) 27 | editor.New(assetDir).Start() 28 | } 29 | -------------------------------------------------------------------------------- /networking/client.go: -------------------------------------------------------------------------------- 1 | package networking 2 | 3 | import ( 4 | "bytes" 5 | "compress/gzip" 6 | "fmt" 7 | "io/ioutil" 8 | "net" 9 | "sync" 10 | ) 11 | 12 | const clientPacketBufferSize = 100 13 | 14 | type Client struct { 15 | token string 16 | conn *net.UDPConn 17 | onPacketReceived func(packet Packet) 18 | bytesSent int64 19 | bytesReceived int64 20 | bytesSentByEvent map[string]int64 21 | bytesReceivedByEvent map[string]int64 22 | bytesByEventMux *sync.Mutex 23 | } 24 | 25 | func NewClient() *Client { 26 | return &Client{ 27 | bytesSentByEvent: make(map[string]int64), 28 | bytesReceivedByEvent: make(map[string]int64), 29 | bytesByEventMux: &sync.Mutex{}, 30 | } 31 | } 32 | 33 | func (c *Client) Connect(addr string) error { 34 | serverAddr, err := net.ResolveUDPAddr("udp", addr) 35 | if err != nil { 36 | fmt.Println("Error resolving server udp address: ", err) 37 | return err 38 | } 39 | 40 | c.conn, err = net.DialUDP("udp", nil, serverAddr) 41 | if err != nil { 42 | fmt.Println("Error connecting to udp server address: ", err) 43 | return err 44 | } 45 | 46 | data := make([]byte, 65500) 47 | go func() { 48 | for c.conn != nil { 49 | n, _, err := c.conn.ReadFromUDP(data) 50 | if err != nil { 51 | fmt.Println("Error reading udp packet: ", err) 52 | continue 53 | } 54 | 55 | dataBuf := bytes.NewBuffer(data[0:n]) 56 | gzipReader, err := gzip.NewReader(dataBuf) 57 | if err != nil { 58 | fmt.Println("Error creating gzip Reader for udp packet: ", err) 59 | continue 60 | } 61 | 62 | unzipped, err := ioutil.ReadAll(gzipReader) 63 | if err != nil { 64 | fmt.Println("Error unzipping udp packet: ", err) 65 | continue 66 | } 67 | 68 | var packet Packet 69 | for i := 0; i < len(unzipped); { 70 | j := i 71 | packet, err, i = Decode(unzipped, i) 72 | if err != nil { 73 | fmt.Println("Error decoding udp packet: ", err) 74 | continue 75 | } 76 | c.updateBytesReceived(packet.Command, int64(i-j)) 77 | 78 | c.token = packet.Token 79 | 80 | if c.onPacketReceived != nil { 81 | c.onPacketReceived(packet) 82 | } 83 | } 84 | } 85 | }() 86 | return nil 87 | } 88 | 89 | func (c *Client) PacketReceived(callback func(packet Packet)) { 90 | c.onPacketReceived = callback 91 | } 92 | 93 | func (c *Client) WriteMessage(command string, data []byte) { 94 | packet := Packet{ 95 | Token: c.token, 96 | Command: command, 97 | Data: data, 98 | } 99 | 100 | packetData := Encode(packet) 101 | var gzipBuf bytes.Buffer 102 | gzipWriter := gzip.NewWriter(&gzipBuf) 103 | _, err := gzipWriter.Write(packetData) 104 | if err != nil { 105 | fmt.Println("Error Gzip compressing udp message: ", err) 106 | return 107 | } 108 | 109 | if err := gzipWriter.Flush(); err != nil { 110 | fmt.Println("Error Flushing Gzip writer for udp message: ", err) 111 | return 112 | } 113 | 114 | if err := gzipWriter.Close(); err != nil { 115 | fmt.Println("Error Closing Gzip writer for udp message: ", err) 116 | return 117 | } 118 | 119 | gzipData := gzipBuf.Bytes() 120 | c.updateBytesSent(command, int64(len(gzipData))) 121 | _, err = c.conn.Write(gzipData) 122 | if err != nil { 123 | fmt.Println("Error writing udp message: ", err) 124 | } 125 | } 126 | 127 | func (c *Client) Close() { 128 | c.conn.Close() 129 | } 130 | 131 | func (c *Client) updateBytesSent(event string, sent int64) { 132 | c.bytesByEventMux.Lock() 133 | c.bytesSent += sent 134 | total, ok := c.bytesSentByEvent[event] 135 | if !ok { 136 | c.bytesSentByEvent[event], total = 0, 0 137 | } 138 | c.bytesSentByEvent[event] = sent + total 139 | c.bytesByEventMux.Unlock() 140 | } 141 | 142 | func (c *Client) updateBytesReceived(event string, sent int64) { 143 | c.bytesByEventMux.Lock() 144 | c.bytesReceived += sent 145 | total, ok := c.bytesReceivedByEvent[event] 146 | if !ok { 147 | c.bytesReceivedByEvent[event], total = 0, 0 148 | } 149 | c.bytesReceivedByEvent[event] = sent + total 150 | c.bytesByEventMux.Unlock() 151 | } 152 | 153 | func (c *Client) GetBytesSentByEvent() (byEvent map[string]int64) { 154 | c.bytesByEventMux.Lock() 155 | byEvent = make(map[string]int64) 156 | for k, v := range c.bytesSentByEvent { 157 | byEvent[k] = v 158 | } 159 | c.bytesByEventMux.Unlock() 160 | return byEvent 161 | } 162 | 163 | func (c *Client) GetBytesReceivedByEvent() (byEvent map[string]int64) { 164 | c.bytesByEventMux.Lock() 165 | byEvent = make(map[string]int64) 166 | for k, v := range c.bytesReceivedByEvent { 167 | byEvent[k] = v 168 | } 169 | c.bytesByEventMux.Unlock() 170 | return byEvent 171 | } 172 | -------------------------------------------------------------------------------- /networking/clock.go: -------------------------------------------------------------------------------- 1 | package networking 2 | 3 | //TODO 4 | -------------------------------------------------------------------------------- /networking/packet.go: -------------------------------------------------------------------------------- 1 | package networking 2 | 3 | import ( 4 | "bytes" 5 | "encoding/binary" 6 | "fmt" 7 | ) 8 | 9 | type Packet struct { 10 | Token string 11 | Command string 12 | Data []byte 13 | } 14 | 15 | func Encode(packet Packet) []byte { 16 | tokenLen := len(packet.Token) 17 | commandLen := len(packet.Command) 18 | dataLen := len(packet.Data) 19 | data := new(bytes.Buffer) 20 | data.WriteByte(byte(tokenLen)) 21 | data.WriteByte(byte(commandLen)) 22 | binary.Write(data, binary.LittleEndian, uint16(dataLen)) 23 | data.WriteString(packet.Token) 24 | data.WriteString(packet.Command) 25 | data.Write(packet.Data) 26 | return data.Bytes() 27 | } 28 | 29 | func Decode(data []byte, i int) (Packet, error, int) { 30 | if len(data)-i < 4 { 31 | return Packet{}, fmt.Errorf("No data provided to Decode: len=%v", len(data)), len(data) 32 | } 33 | tokenLen := int(data[i]) 34 | commandLen := int(data[i+1]) 35 | dataLen := int(binary.LittleEndian.Uint16(data[i+2 : i+4])) 36 | i += 4 37 | token := string(data[i : i+tokenLen]) 38 | i += tokenLen 39 | command := string(data[i : i+commandLen]) 40 | i += commandLen 41 | packetData := data[i : i+dataLen] 42 | i += dataLen 43 | return Packet{ 44 | Token: token, 45 | Command: command, 46 | Data: packetData, 47 | }, nil, i 48 | } 49 | -------------------------------------------------------------------------------- /networking/packet_test.go: -------------------------------------------------------------------------------- 1 | package networking 2 | 3 | import ( 4 | "encoding/binary" 5 | "testing" 6 | 7 | "github.com/stretchr/testify/assert" 8 | ) 9 | 10 | func TestEncode(t *testing.T) { 11 | packet := Packet{ 12 | Token: "123", 13 | Command: "testCommand", 14 | Data: []byte("test Data"), 15 | } 16 | var bytes [2]byte 17 | binary.LittleEndian.PutUint16(bytes[:], uint16(len(packet.Data))) 18 | expectedData := []byte{byte(len(packet.Token)), byte(len(packet.Command)), bytes[0], bytes[1]} 19 | expectedData = append(expectedData, []byte(packet.Token)...) 20 | expectedData = append(expectedData, []byte(packet.Command)...) 21 | expectedData = append(expectedData, packet.Data...) 22 | 23 | data := Encode(packet) 24 | assert.EqualValues(t, expectedData, data, "Encode packet didn't work") 25 | } 26 | 27 | func TestDecode(t *testing.T) { 28 | expectedPacket := Packet{ 29 | Token: "123", 30 | Command: "testCommand", 31 | Data: []byte("test Data"), 32 | } 33 | data := Encode(expectedPacket) 34 | 35 | packet, err, i := Decode(data, 0) 36 | assert.Nil(t, err, "decode should not return an error") 37 | assert.EqualValues(t, len(data), i, "Decode should return the correct read index") 38 | assert.EqualValues(t, expectedPacket, packet, "Decode packet didn't work") 39 | } 40 | 41 | func TestDecodeMultiple(t *testing.T) { 42 | testPacket1 := Packet{ 43 | Token: "123", 44 | Command: "testCommand", 45 | Data: []byte("test Data"), 46 | } 47 | testPacket2 := Packet{ 48 | Token: "12345678", 49 | Command: "cmdTest", 50 | Data: []byte("123 4567 abcd"), 51 | } 52 | 53 | data := append(Encode(testPacket1), Encode(testPacket2)...) 54 | 55 | packet, err, i := Decode(data, 0) 56 | assert.Nil(t, err, "decode should not return an error") 57 | assert.EqualValues(t, testPacket1, packet, "Decode first packet didn't work") 58 | 59 | packet, err, i = Decode(data, i) 60 | assert.EqualValues(t, len(data), i, "Decode should return the correct read index") 61 | assert.EqualValues(t, testPacket2, packet, "Decode second packet didn't work") 62 | } 63 | -------------------------------------------------------------------------------- /networking/session.go: -------------------------------------------------------------------------------- 1 | package networking 2 | 3 | import ( 4 | "bytes" 5 | "fmt" 6 | "net" 7 | "time" 8 | ) 9 | 10 | var tokens int64 11 | 12 | type Session struct { 13 | token string 14 | addr *net.UDPAddr 15 | idleTimer time.Time 16 | packetBuffer *bytes.Buffer 17 | } 18 | 19 | func NewSession(addr *net.UDPAddr) *Session { 20 | return &Session{ 21 | token: generateToken(), 22 | addr: addr, 23 | idleTimer: time.Now(), 24 | packetBuffer: new(bytes.Buffer), 25 | } 26 | } 27 | 28 | func generateToken() string { 29 | tokens++ 30 | return fmt.Sprintf("%v", tokens) 31 | } 32 | -------------------------------------------------------------------------------- /opengl/shaders.go: -------------------------------------------------------------------------------- 1 | package opengl 2 | 3 | import ( 4 | "errors" 5 | "fmt" 6 | "strings" 7 | 8 | "github.com/go-gl/gl/v4.1-core/gl" 9 | "github.com/go-gl/mathgl/mgl32" 10 | "github.com/walesey/go-engine/renderer" 11 | ) 12 | 13 | func newProgram(shaders ...uint32) (uint32, error) { 14 | program := gl.CreateProgram() 15 | 16 | for _, shader := range shaders { 17 | gl.AttachShader(program, shader) 18 | } 19 | gl.LinkProgram(program) 20 | 21 | var status int32 22 | gl.GetProgramiv(program, gl.LINK_STATUS, &status) 23 | if status == gl.FALSE { 24 | var logLength int32 25 | gl.GetProgramiv(program, gl.INFO_LOG_LENGTH, &logLength) 26 | 27 | log := strings.Repeat("\x00", int(logLength+1)) 28 | gl.GetProgramInfoLog(program, logLength, nil, gl.Str(log)) 29 | 30 | return 0, errors.New(fmt.Sprintf("failed to link program: %v", log)) 31 | } 32 | 33 | for _, shader := range shaders { 34 | gl.DeleteShader(shader) 35 | } 36 | 37 | return program, nil 38 | } 39 | 40 | func compileShader(source string, shaderType uint32) (uint32, error) { 41 | shader := gl.CreateShader(shaderType) 42 | csource, free := gl.Strs(source) 43 | gl.ShaderSource(shader, 1, csource, nil) 44 | free() 45 | gl.CompileShader(shader) 46 | 47 | var status int32 48 | gl.GetShaderiv(shader, gl.COMPILE_STATUS, &status) 49 | if status == gl.FALSE { 50 | var logLength int32 51 | gl.GetShaderiv(shader, gl.INFO_LOG_LENGTH, &logLength) 52 | 53 | log := strings.Repeat("\x00", int(logLength+1)) 54 | gl.GetShaderInfoLog(shader, logLength, nil, gl.Str(log)) 55 | 56 | return 0, fmt.Errorf("failed to compile %v: %v", source, log) 57 | } 58 | 59 | return shader, nil 60 | } 61 | 62 | func setupUniforms(shader *renderer.Shader) { 63 | for name, uniform := range shader.Uniforms { 64 | uniformLocation := gl.GetUniformLocation(shader.Program, gl.Str(name+"\x00")) 65 | switch t := uniform.(type) { 66 | case bool: 67 | if t { 68 | gl.Uniform1i(uniformLocation, 1) 69 | } else { 70 | gl.Uniform1i(uniformLocation, 0) 71 | } 72 | case float32: 73 | gl.Uniform1f(uniformLocation, t) 74 | case float64: 75 | gl.Uniform1f(uniformLocation, float32(t)) 76 | case int32: 77 | gl.Uniform1i(uniformLocation, t) 78 | case int: 79 | gl.Uniform1i(uniformLocation, int32(t)) 80 | case mgl32.Vec2: 81 | gl.Uniform2f(uniformLocation, t[0], t[1]) 82 | case mgl32.Vec3: 83 | gl.Uniform3f(uniformLocation, t[0], t[1], t[2]) 84 | case mgl32.Vec4: 85 | gl.Uniform4f(uniformLocation, t[0], t[1], t[2], t[3]) 86 | case mgl32.Mat4: 87 | gl.UniformMatrix4fv(uniformLocation, 1, false, &t[0]) 88 | case []float32: 89 | gl.Uniform4fv(uniformLocation, (int32)(len(t)), &t[0]) 90 | case []int32: 91 | gl.Uniform4iv(uniformLocation, (int32)(len(t)), &t[0]) 92 | default: 93 | fmt.Printf("unexpected type for shader uniform: %T\n", t) 94 | } 95 | } 96 | } 97 | -------------------------------------------------------------------------------- /physics/chipmunk/body.go: -------------------------------------------------------------------------------- 1 | package chipmunkPhysics 2 | 3 | import ( 4 | "github.com/go-gl/mathgl/mgl32" 5 | "github.com/vova616/chipmunk" 6 | "github.com/vova616/chipmunk/vect" 7 | ) 8 | 9 | type ChipmunkBody struct { 10 | Body *chipmunk.Body 11 | } 12 | 13 | func NewChipmunkBody(mass, i float32) *ChipmunkBody { 14 | return &ChipmunkBody{ 15 | Body: chipmunk.NewBody(vect.Float(mass), vect.Float(i)), 16 | } 17 | } 18 | 19 | func NewChipmunkBodyStatic() *ChipmunkBody { 20 | return &ChipmunkBody{ 21 | Body: chipmunk.NewBodyStatic(), 22 | } 23 | } 24 | 25 | func (cBody *ChipmunkBody) KineticEnergy() float32 { 26 | return float32(cBody.Body.KineticEnergy()) 27 | } 28 | 29 | func (cBody *ChipmunkBody) SetMass(mass float32) { 30 | cBody.Body.SetMass(vect.Float(mass)) 31 | } 32 | 33 | func (cBody *ChipmunkBody) SetMoment(moment float32) { 34 | cBody.Body.SetMoment(vect.Float(moment)) 35 | } 36 | 37 | func (cBody *ChipmunkBody) GetMoment() float32 { 38 | return cBody.Body.Moment() 39 | } 40 | 41 | func (cBody *ChipmunkBody) SetAngle(angle float32) { 42 | cBody.Body.SetAngle(vect.Float(angle)) 43 | } 44 | 45 | func (cBody *ChipmunkBody) AddAngle(angle float32) { 46 | cBody.Body.AddAngle(angle) 47 | } 48 | 49 | func (cBody *ChipmunkBody) GetMass() float32 { 50 | return float32(cBody.Body.Mass()) 51 | } 52 | 53 | func (cBody *ChipmunkBody) SetPosition(pos mgl32.Vec2) { 54 | cBody.Body.SetPosition(convertToVect(pos)) 55 | } 56 | 57 | func (cBody *ChipmunkBody) AddForce(force mgl32.Vec2) { 58 | cBody.Body.AddForce(force.X(), force.Y()) 59 | } 60 | 61 | func (cBody *ChipmunkBody) SetForce(force mgl32.Vec2) { 62 | cBody.Body.SetForce(force.X(), force.Y()) 63 | } 64 | 65 | func (cBody *ChipmunkBody) AddVelocity(velocity mgl32.Vec2) { 66 | cBody.Body.AddVelocity(velocity.X(), velocity.Y()) 67 | } 68 | 69 | func (cBody *ChipmunkBody) SetVelocity(velocity mgl32.Vec2) { 70 | cBody.Body.SetVelocity(velocity.X(), velocity.Y()) 71 | } 72 | 73 | func (cBody *ChipmunkBody) AddTorque(t float32) { 74 | cBody.Body.AddTorque(float32(t)) 75 | } 76 | 77 | func (cBody *ChipmunkBody) GetTorque() float32 { 78 | return cBody.Body.Torque() 79 | } 80 | 81 | func (cBody *ChipmunkBody) GetAngularVelocity() float32 { 82 | return cBody.Body.AngularVelocity() 83 | } 84 | 85 | func (cBody *ChipmunkBody) SetTorque(t float32) { 86 | cBody.Body.SetTorque(float32(t)) 87 | } 88 | 89 | func (cBody *ChipmunkBody) AddAngularVelocity(w float32) { 90 | cBody.Body.AddAngularVelocity(w) 91 | } 92 | 93 | func (cBody *ChipmunkBody) SetAngularVelocity(w float32) { 94 | cBody.Body.SetAngularVelocity(w) 95 | } 96 | 97 | func (cBody *ChipmunkBody) GetVelocity() mgl32.Vec2 { 98 | return convertFromVect(cBody.Body.Velocity()) 99 | } 100 | 101 | func (cBody *ChipmunkBody) GetPosition() mgl32.Vec2 { 102 | return convertFromVect(cBody.Body.Position()) 103 | } 104 | 105 | func (cBody *ChipmunkBody) GetAngle() float32 { 106 | return float32(cBody.Body.Angle()) 107 | } 108 | -------------------------------------------------------------------------------- /physics/chipmunk/world.go: -------------------------------------------------------------------------------- 1 | package chipmunkPhysics 2 | 3 | import ( 4 | "github.com/go-gl/mathgl/mgl32" 5 | "github.com/vova616/chipmunk" 6 | "github.com/vova616/chipmunk/vect" 7 | "github.com/walesey/go-engine/physics/physicsAPI" 8 | ) 9 | 10 | type ChipmonkSpace struct { 11 | Space *chipmunk.Space 12 | OnCollision func(shapeA, shapeB *chipmunk.Shape) 13 | } 14 | 15 | func NewChipmonkSpace() *ChipmonkSpace { 16 | return &ChipmonkSpace{ 17 | Space: chipmunk.NewSpace(), 18 | } 19 | } 20 | 21 | func (cSpace *ChipmonkSpace) Update(dt float64) { 22 | cSpace.Space.Step(vect.Float(dt)) 23 | if cSpace.OnCollision != nil { 24 | for _, a := range cSpace.Space.Arbiters { 25 | cSpace.OnCollision(a.ShapeA, a.ShapeB) 26 | } 27 | } 28 | } 29 | 30 | func (cSpace *ChipmonkSpace) SetOnCollision(onCollision func(shapeA, shapeB *chipmunk.Shape)) { 31 | cSpace.OnCollision = onCollision 32 | } 33 | 34 | func (cSpace *ChipmonkSpace) AddBody(body physicsAPI.PhysicsObject2D) { 35 | cBody, ok := body.(*ChipmunkBody) 36 | if ok { 37 | cSpace.Space.AddBody(cBody.Body) 38 | } 39 | } 40 | 41 | func (cSpace *ChipmonkSpace) RemoveBody(body physicsAPI.PhysicsObject2D) { 42 | cBody, ok := body.(*ChipmunkBody) 43 | if ok { 44 | cSpace.Space.RemoveBody(cBody.Body) 45 | } 46 | } 47 | 48 | func (cSpace *ChipmonkSpace) SetGravity(gravity mgl32.Vec2) { 49 | cSpace.Space.Gravity = convertToVect(gravity) 50 | } 51 | 52 | func (cSpace *ChipmonkSpace) GetGravity() mgl32.Vec2 { 53 | return convertFromVect(cSpace.Space.Gravity) 54 | } 55 | 56 | func convertFromVect(v vect.Vect) mgl32.Vec2 { 57 | return mgl32.Vec2{float32(v.X), float32(v.Y)} 58 | } 59 | 60 | func convertToVect(v mgl32.Vec2) vect.Vect { 61 | return vect.Vect{X: vect.Float(v.X()), Y: vect.Float(v.Y())} 62 | } 63 | -------------------------------------------------------------------------------- /physics/physicsAPI/characterController.go: -------------------------------------------------------------------------------- 1 | package physicsAPI 2 | 3 | import "github.com/go-gl/mathgl/mgl32" 4 | 5 | type CharacterController interface { 6 | Delete() 7 | Warp(position mgl32.Vec3) 8 | Jump() 9 | SetWalkDirection(dir mgl32.Vec3) 10 | SetVelocityForTimeInterval(speed mgl32.Vec3, time float32) 11 | 12 | SetUpAxis(axis int) 13 | SetFallSpeed(speed float32) 14 | SetJumpSpeed(speed float32) 15 | SetMaxJumpHeight(height float32) 16 | SetGravity(gravity float32) 17 | SetMaxSlope(radian float32) 18 | 19 | GetPosition() mgl32.Vec3 20 | CanJump() bool 21 | GetGravity() float32 22 | GetMaxSlope() float32 23 | OnGround() bool 24 | } 25 | -------------------------------------------------------------------------------- /physics/physicsAPI/constraints.go: -------------------------------------------------------------------------------- 1 | package physicsAPI 2 | 3 | type ConstraintSolver interface { 4 | SolveGroup(stepTime float32, constraints *[]Constraint) 5 | } 6 | 7 | type Constraint interface{} 8 | -------------------------------------------------------------------------------- /physics/physicsAPI/physicsObject.go: -------------------------------------------------------------------------------- 1 | package physicsAPI 2 | 3 | import ( 4 | "github.com/go-gl/mathgl/mgl32" 5 | ) 6 | 7 | type PhysicsObject interface { 8 | Delete() 9 | ApplyForce(force, position mgl32.Vec3) 10 | ApplyTorque(torque mgl32.Vec3) 11 | 12 | GetPosition() mgl32.Vec3 13 | GetVelocity() mgl32.Vec3 14 | GetOrientation() mgl32.Quat 15 | GetAngularVelocityVector() mgl32.Vec3 16 | GetMass() float32 17 | GetRadius() float32 18 | GetFriction() float32 19 | GetRestitution() float32 20 | InertiaTensor() mgl32.Mat3 21 | IsStatic() bool 22 | 23 | SetPosition(position mgl32.Vec3) 24 | SetVelocity(velocity mgl32.Vec3) 25 | SetOrientation(orientation mgl32.Quat) 26 | SetAngularVelocityVector(av mgl32.Vec3) 27 | SetMass(mass float32) 28 | SetRadius(radius float32) 29 | SetFriction(friction float32) 30 | SetRestitution(restitution float32) 31 | } 32 | 33 | type PhysicsObject2D interface { 34 | KineticEnergy() float32 35 | SetMass(mass float32) 36 | SetMoment(moment float32) 37 | GetMoment() float32 38 | SetAngle(angle float32) 39 | AddAngle(angle float32) 40 | GetMass() float32 41 | SetPosition(pos mgl32.Vec2) 42 | AddForce(force mgl32.Vec2) 43 | SetForce(force mgl32.Vec2) 44 | AddVelocity(velocity mgl32.Vec2) 45 | SetVelocity(velocity mgl32.Vec2) 46 | AddTorque(t float32) 47 | GetTorque() float32 48 | GetAngularVelocity() float32 49 | SetTorque(t float32) 50 | AddAngularVelocity(w float32) 51 | SetAngularVelocity(w float32) 52 | GetVelocity() mgl32.Vec2 53 | GetPosition() mgl32.Vec2 54 | GetAngle() float32 55 | } 56 | -------------------------------------------------------------------------------- /physics/physicsAPI/physicsSpace.go: -------------------------------------------------------------------------------- 1 | package physicsAPI 2 | 3 | import "github.com/go-gl/mathgl/mgl32" 4 | 5 | type PhysicsSpace interface { 6 | Update(dt float64) 7 | SimulateStep(stepTime float32, subSteps int) 8 | Delete() 9 | AddObject(objects ...PhysicsObject) 10 | RemoveObject(objects ...PhysicsObject) 11 | AddCharacterController(characterController CharacterController) 12 | SetConstraintSolver(solver ConstraintSolver) 13 | AddConstraint(constraint Constraint) 14 | RemoveConstraints(constraint ...Constraint) 15 | SetGravity(gravity mgl32.Vec3) 16 | GetGravity() mgl32.Vec3 17 | } 18 | 19 | type PhysicsSpace2D interface { 20 | Update(dt float64) 21 | AddBody(body PhysicsObject2D) 22 | RemoveBody(body PhysicsObject2D) 23 | SetGravity(gravity mgl32.Vec2) 24 | GetGravity() mgl32.Vec2 25 | } 26 | -------------------------------------------------------------------------------- /renderer/Cubemap.go: -------------------------------------------------------------------------------- 1 | package renderer 2 | 3 | import ( 4 | "image" 5 | 6 | "github.com/disintegration/imaging" 7 | ) 8 | 9 | type CubeMap struct { 10 | Id uint32 11 | Lod bool 12 | Loaded bool 13 | 14 | Right, Left, Top, Bottom, Back, Front image.Image 15 | } 16 | 17 | func NewCubemap(baseImage image.Image, lod bool) *CubeMap { 18 | cubeMap := new(CubeMap) 19 | 20 | x := baseImage.Bounds().Max.X 21 | y := baseImage.Bounds().Max.Y 22 | 23 | cubeMap.Lod = lod 24 | cubeMap.Right = imaging.Crop(baseImage, image.Rect(x/2, y/3, 3*x/4, 2*y/3)) 25 | cubeMap.Left = imaging.Crop(baseImage, image.Rect(0, y/3, x/4, 2*y/3)) 26 | cubeMap.Top = imaging.Crop(baseImage, image.Rect(x/4, 0, x/2, y/3)) 27 | cubeMap.Bottom = imaging.Crop(baseImage, image.Rect(x/4, 2*y/3, x/2, y)) 28 | cubeMap.Back = imaging.Crop(baseImage, image.Rect(3*x/4, y/3, x, 2*y/3)) 29 | cubeMap.Front = imaging.Crop(baseImage, image.Rect(x/4, y/3, x/2, 2*y/3)) 30 | 31 | return cubeMap 32 | } 33 | 34 | func (cm *CubeMap) Destroy(renderer Renderer) { 35 | if cm != nil { 36 | renderer.DestroyCubeMap(cm) 37 | cm.Loaded = false 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /renderer/camera.go: -------------------------------------------------------------------------------- 1 | package renderer 2 | 3 | import ( 4 | "log" 5 | 6 | "github.com/go-gl/mathgl/mgl32" 7 | "github.com/walesey/go-engine/util" 8 | ) 9 | 10 | // The camera Entity 11 | type Camera struct { 12 | Translation, Lookat, Up mgl32.Vec3 13 | Angle, Near, Far float32 14 | Ortho bool 15 | } 16 | 17 | func CreateCamera() *Camera { 18 | cam := Camera{ 19 | Translation: mgl32.Vec3{0, 0, 0}, 20 | Lookat: mgl32.Vec3{1, 0, 0}, 21 | Up: mgl32.Vec3{0, 1, 0}, 22 | Angle: 45.0, 23 | Near: 0.1, 24 | Far: 999999999.0, 25 | } 26 | 27 | return &cam 28 | } 29 | 30 | func (c *Camera) GetDirection() mgl32.Vec3 { 31 | return c.Lookat.Sub(c.Translation).Normalize() 32 | } 33 | 34 | // GetMouseVector - Returns a normal vector given by the mouse position 35 | func (c *Camera) GetMouseVector(windowSize, mouse mgl32.Vec2) mgl32.Vec3 { 36 | v, err := mgl32.UnProject( 37 | mgl32.Vec3{mouse.X(), windowSize.Y() - mouse.Y(), 0.5}, 38 | mgl32.LookAtV(c.Translation, c.Lookat, c.Up), 39 | mgl32.Perspective(mgl32.DegToRad(c.Angle), windowSize.X()/windowSize.Y(), c.Near, c.Far), 40 | 0, 0, int(windowSize.X()), int(windowSize.Y()), 41 | ) 42 | if err != nil { 43 | log.Println("Error converting camera vector: ", err) 44 | return c.Lookat 45 | } 46 | 47 | return v.Sub(c.Translation).Normalize() 48 | } 49 | 50 | // GetWindowVector - Returns the screen position of the given world vector 51 | func (c *Camera) GetWindowVector(windowSize mgl32.Vec2, point mgl32.Vec3) mgl32.Vec3 { 52 | v := mgl32.Project( 53 | point, 54 | mgl32.LookAtV(c.Translation, c.Lookat, c.Up), 55 | mgl32.Perspective(mgl32.DegToRad(c.Angle), windowSize.X()/windowSize.Y(), c.Near, c.Far), 56 | 0, 0, int(windowSize.X()), int(windowSize.Y()), 57 | ) 58 | 59 | return mgl32.Vec3{v.X(), windowSize.Y() - v.Y(), v.Z()} 60 | } 61 | 62 | // CameraContainsSphere - determines if a sphere in contained in the frustrum given by the camera. 63 | // sphere is given by point and radius 64 | func (c *Camera) CameraContainsSphere(windowSize mgl32.Vec2, radius float32, point mgl32.Vec3) bool { 65 | return c.FrustrumContainsSphere(windowSize, mgl32.Vec2{}, windowSize, radius, point) 66 | } 67 | 68 | // FrustrumContainsSphere - determines if a sphere in contained in the frustrum given by start/end vectors on the screen. 69 | // sphere is given by point and radius 70 | func (c *Camera) FrustrumContainsSphere(windowSize, start, end mgl32.Vec2, radius float32, point mgl32.Vec3) bool { 71 | tlv := c.GetMouseVector(windowSize, start) 72 | trv := c.GetMouseVector(windowSize, mgl32.Vec2{end.X(), start.Y()}) 73 | blv := c.GetMouseVector(windowSize, mgl32.Vec2{start.X(), end.Y()}) 74 | brv := c.GetMouseVector(windowSize, end) 75 | 76 | r := radius 77 | delta := point.Sub(c.Translation) 78 | if point.ApproxEqual(c.Translation) { 79 | delta = mgl32.Vec3{1, 0, 0} 80 | } 81 | 82 | return delta.Dot(tlv.Cross(trv).Normalize()) > -r && 83 | delta.Dot(trv.Cross(brv).Normalize()) > -r && 84 | delta.Dot(brv.Cross(blv).Normalize()) > -r && 85 | delta.Dot(blv.Cross(tlv).Normalize()) > -r && 86 | util.Vec3LenSq(delta) < (c.Far+r)*(c.Far+r) 87 | } 88 | 89 | func (c *Camera) SetScale(scale mgl32.Vec3) {} //na 90 | 91 | func (c *Camera) SetTranslation(translation mgl32.Vec3) { 92 | c.Translation = translation 93 | } 94 | 95 | func (c *Camera) SetOrientation(orientation mgl32.Quat) { 96 | direction := orientation.Rotate(mgl32.Vec3{1, 0, 0}) 97 | c.Lookat = c.Translation.Add(direction) 98 | } 99 | -------------------------------------------------------------------------------- /renderer/fpsMeter.go: -------------------------------------------------------------------------------- 1 | package renderer 2 | 3 | import ( 4 | "time" 5 | ) 6 | 7 | type FPSMeter struct { 8 | start time.Time 9 | last time.Time 10 | frames int 11 | sampleTime float64 12 | FpsCap float64 13 | FrameTime float64 14 | value float64 15 | } 16 | 17 | func CreateFPSMeter(sampleTime float64) *FPSMeter { 18 | return &FPSMeter{start: time.Now(), last: time.Now(), frames: 0, sampleTime: sampleTime, FpsCap: 120} 19 | } 20 | 21 | func (fps *FPSMeter) UpdateFPSMeter() float64 { 22 | fps.frames = fps.frames + 1 23 | elapsed := time.Since(fps.start) 24 | if elapsed.Seconds() >= fps.sampleTime { 25 | fps.value = (float64)(fps.frames) / fps.sampleTime 26 | fps.start = time.Now() 27 | fps.frames = 0 28 | } 29 | 30 | fps.FrameTime = time.Since(fps.last).Seconds() 31 | sleepTime := (time.Duration)((1000.0 / fps.FpsCap) - (1000.0 * fps.FrameTime)) 32 | if sleepTime > 0 { 33 | time.Sleep(sleepTime * time.Millisecond) 34 | } 35 | frameTime := time.Since(fps.last) 36 | fps.last = time.Now() 37 | return frameTime.Seconds() 38 | } 39 | 40 | func (fps *FPSMeter) Value() float64 { 41 | return fps.value 42 | } 43 | -------------------------------------------------------------------------------- /renderer/light.go: -------------------------------------------------------------------------------- 1 | package renderer 2 | 3 | import "github.com/go-gl/mathgl/mgl32" 4 | 5 | type LightType int 6 | 7 | const ( 8 | _ LightType = iota 9 | POINT 10 | DIRECTIONAL 11 | AMBIENT 12 | ) 13 | 14 | type Light struct { 15 | LightType 16 | Color [3]float32 //RGB 17 | Position mgl32.Vec3 18 | Direction mgl32.Vec3 19 | } 20 | 21 | func NewLight(lightType LightType) *Light { 22 | return &Light{ 23 | LightType: lightType, 24 | Color: [3]float32{1, 1, 1}, 25 | Direction: mgl32.Vec3{1, 0, 0}, 26 | } 27 | } 28 | 29 | func (l *Light) SetScale(scale mgl32.Vec3) {} //na 30 | 31 | func (l *Light) SetTranslation(translation mgl32.Vec3) { 32 | if l.LightType == POINT { 33 | l.Position = translation 34 | } 35 | } 36 | 37 | func (l *Light) SetOrientation(orientation mgl32.Quat) { 38 | if l.LightType == DIRECTIONAL { 39 | l.Direction = orientation.Rotate(mgl32.Vec3{1, 0, 0}) 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /renderer/material.go: -------------------------------------------------------------------------------- 1 | package renderer 2 | 3 | import ( 4 | "image" 5 | ) 6 | 7 | type Texture struct { 8 | TextureId uint32 9 | TextureName string 10 | Img image.Image 11 | Lod bool 12 | Loaded bool 13 | } 14 | 15 | type Material struct { 16 | Textures []*Texture 17 | } 18 | 19 | func NewTexture(name string, img image.Image, lod bool) *Texture { 20 | return &Texture{ 21 | TextureName: name, 22 | Img: img, 23 | Lod: lod, 24 | } 25 | } 26 | 27 | func NewMaterial(textures ...*Texture) *Material { 28 | return &Material{ 29 | Textures: textures, 30 | } 31 | } 32 | 33 | func (m *Material) Destroy(renderer Renderer) { 34 | if m != nil { 35 | renderer.DestroyMaterial(m) 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /renderer/primitiveData.go: -------------------------------------------------------------------------------- 1 | package renderer 2 | 3 | var cubeIndicies = []uint32{ 4 | 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 5 | 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 6 | } 7 | 8 | var skyboxVerticies = []float32{ 9 | -1, -1, -1, 1, 1, 1, 0.25, 0.334, 1, 1, 1, 1, 10 | -1, 1, -1, 1, 1, 1, 0.25, 0.6662, 1, 1, 1, 1, 11 | -1, 1, 1, 1, 1, 1, 0.5, 0.6662, 1, 1, 1, 1, 12 | -1, 1, 1, 1, 1, 1, 0.5, 0.6662, 1, 1, 1, 1, 13 | -1, -1, 1, 1, 1, 1, 0.5, 0.334, 1, 1, 1, 1, 14 | -1, -1, -1, 1, 1, 1, 0.25, 0.334, 1, 1, 1, 1, 15 | 1, -1, -1, 1, 1, 1, 1, 0.334, 1, 1, 1, 1, 16 | 1, -1, 1, 1, 1, 1, 0.75, 0.334, 1, 1, 1, 1, 17 | 1, 1, 1, 1, 1, 1, 0.75, 0.666, 1, 1, 1, 1, 18 | 1, 1, 1, 1, 1, 1, 0.75, 0.666, 1, 1, 1, 1, 19 | 1, 1, -1, 1, 1, 1, 1, 0.666, 1, 1, 1, 1, 20 | 1, -1, -1, 1, 1, 1, 1, 0.334, 1, 1, 1, 1, 21 | -1, -1, -1, 1, 1, 1, 0.25, 0.333, 1, 1, 1, 1, 22 | -1, -1, 1, 1, 1, 1, 0.5, 0.333, 1, 1, 1, 1, 23 | 1, -1, 1, 1, 1, 1, 0.5, 0, 1, 1, 1, 1, 24 | 1, -1, 1, 1, 1, 1, 0.5, 0, 1, 1, 1, 1, 25 | 1, -1, -1, 1, 1, 1, 0.25, 0, 1, 1, 1, 1, 26 | -1, -1, -1, 1, 1, 1, 0.25, 0.333, 1, 1, 1, 1, 27 | -1, -1, 1, 1, 1, 1, 0.5, 0.334, 1, 1, 1, 1, 28 | -1, 1, 1, 1, 1, 1, 0.5, 0.666, 1, 1, 1, 1, 29 | 1, 1, 1, 1, 1, 1, 0.75, 0.666, 1, 1, 1, 1, 30 | 1, 1, 1, 1, 1, 1, 0.75, 0.666, 1, 1, 1, 1, 31 | 1, -1, 1, 1, 1, 1, 0.75, 0.334, 1, 1, 1, 1, 32 | -1, -1, 1, 1, 1, 1, 0.5, 0.334, 1, 1, 1, 1, 33 | -1, 1, 1, 1, 1, 1, 0.5, 0.667, 1, 1, 1, 1, 34 | -1, 1, -1, 1, 1, 1, 0.25, 0.667, 1, 1, 1, 1, 35 | 1, 1, -1, 1, 1, 1, 0.25, 1, 1, 1, 1, 1, 36 | 1, 1, -1, 1, 1, 1, 0.25, 1, 1, 1, 1, 1, 37 | 1, 1, 1, 1, 1, 1, 0.5, 1, 1, 1, 1, 1, 38 | -1, 1, 1, 1, 1, 1, 0.5, 0.667, 1, 1, 1, 1, 39 | 1, -1, -1, 1, 1, 1, 0, 0.333, 1, 1, 1, 1, 40 | 1, 1, -1, 1, 1, 1, 0, 0.666, 1, 1, 1, 1, 41 | -1, 1, -1, 1, 1, 1, 0.25, 0.666, 1, 1, 1, 1, 42 | -1, 1, -1, 1, 1, 1, 0.25, 0.666, 1, 1, 1, 1, 43 | -1, -1, -1, 1, 1, 1, 0.25, 0.333, 1, 1, 1, 1, 44 | 1, -1, -1, 1, 1, 1, 0, 0.333, 1, 1, 1, 1, 45 | } 46 | -------------------------------------------------------------------------------- /renderer/renderer.go: -------------------------------------------------------------------------------- 1 | package renderer 2 | 3 | import "github.com/go-gl/mathgl/mgl32" 4 | 5 | type Renderer interface { 6 | SetInit(callback func()) 7 | SetUpdate(callback func()) 8 | SetRender(callback func()) 9 | SetCamera(camera *Camera) 10 | Camera() *Camera 11 | Start() 12 | 13 | BackGroundColor(r, g, b, a float32) 14 | WindowDimensions() mgl32.Vec2 15 | LockCursor(lock bool) 16 | UseRendererParams(params RendererParams) 17 | 18 | DrawGeometry(geometry *Geometry, transform mgl32.Mat4) 19 | DestroyGeometry(geometry *Geometry) 20 | 21 | UseMaterial(material *Material) 22 | DestroyMaterial(material *Material) 23 | 24 | UseCubeMap(cubeMap *CubeMap) 25 | DestroyCubeMap(cubeMap *CubeMap) 26 | 27 | UseShader(shader *Shader) 28 | 29 | CreatePostEffect(shader *Shader) 30 | DestroyPostEffects(shader *Shader) 31 | 32 | AddLight(light *Light) 33 | RemoveLight(light *Light) 34 | } 35 | 36 | //A Spatial is something that can be Added to scenegraph nodes 37 | type Spatial interface { 38 | Draw(renderer Renderer, transform mgl32.Mat4) 39 | Optimize(geometry *Geometry, transform mgl32.Mat4) 40 | Destroy(renderer Renderer) 41 | Center() mgl32.Vec3 42 | BoundingRadius() float32 43 | OrthoOrder() int 44 | SetParent(parent *Node) 45 | } 46 | 47 | //An Entity is something that can be scaled, positioned and rotated (orientation) 48 | type Entity interface { 49 | SetScale(scale mgl32.Vec3) 50 | SetTranslation(translation mgl32.Vec3) 51 | SetOrientation(orientation mgl32.Quat) 52 | } 53 | -------------------------------------------------------------------------------- /renderer/sceneGraph.go: -------------------------------------------------------------------------------- 1 | package renderer 2 | 3 | import ( 4 | "sort" 5 | 6 | "github.com/go-gl/mathgl/mgl32" 7 | "github.com/go-gl/mathgl/mgl32/matstack" 8 | "github.com/walesey/go-engine/util" 9 | ) 10 | 11 | type SceneGraph struct { 12 | opaqueNode *Node 13 | transparentNode *Node 14 | txStack *matstack.TransformStack 15 | bucketCount int 16 | bucket bucketEntries 17 | } 18 | 19 | //factory 20 | func CreateSceneGraph() *SceneGraph { 21 | sceneGraph := &SceneGraph{ 22 | opaqueNode: NewNode(), 23 | transparentNode: NewNode(), 24 | txStack: matstack.NewTransformStack(), 25 | } 26 | return sceneGraph 27 | } 28 | 29 | func (sceneGraph *SceneGraph) AddTransparent(spatial Spatial) { 30 | sceneGraph.transparentNode.Add(spatial) 31 | } 32 | 33 | func (sceneGraph *SceneGraph) RemoveTransparent(spatial Spatial, destroy bool) { 34 | sceneGraph.transparentNode.Remove(spatial, destroy) 35 | } 36 | 37 | func (sceneGraph *SceneGraph) Add(spatial Spatial) { 38 | sceneGraph.opaqueNode.Add(spatial) 39 | } 40 | 41 | func (sceneGraph *SceneGraph) Remove(spatial Spatial, destroy bool) { 42 | sceneGraph.opaqueNode.Remove(spatial, destroy) 43 | } 44 | 45 | func (sceneGraph *SceneGraph) RenderScene(renderer Renderer, cameraLocation mgl32.Vec3) { 46 | //setup buckets 47 | sceneGraph.bucketCount = 0 48 | sceneGraph.buildBuckets(sceneGraph.transparentNode, cameraLocation) 49 | sort.Sort(sceneGraph.bucket) 50 | //render buckets 51 | sceneGraph.opaqueNode.Draw(renderer, mgl32.Ident4()) 52 | for i := 0; i < sceneGraph.bucketCount; i++ { 53 | entry := sceneGraph.bucket[i] 54 | entry.parent.DrawChild(renderer, entry.transform, entry.spatial) 55 | } 56 | } 57 | 58 | type bucketEntry struct { 59 | spatial Spatial 60 | parent *Node 61 | transform mgl32.Mat4 62 | cameraDelta float32 63 | } 64 | 65 | type bucketEntries []bucketEntry 66 | 67 | func (slice bucketEntries) Len() int { 68 | return len(slice) 69 | } 70 | 71 | func (slice bucketEntries) Less(i, j int) bool { 72 | return slice[i].cameraDelta > slice[j].cameraDelta 73 | } 74 | 75 | func (slice bucketEntries) Swap(i, j int) { 76 | slice[i], slice[j] = slice[j], slice[i] 77 | } 78 | 79 | func (sceneGraph *SceneGraph) buildBuckets(node *Node, cameraLocation mgl32.Vec3) { 80 | sceneGraph.txStack.Push(node.Transform) 81 | for _, child := range node.children { 82 | nextNode, ok := child.(*Node) 83 | if ok { 84 | sceneGraph.buildBuckets(nextNode, cameraLocation) 85 | } else { 86 | transform := sceneGraph.txStack.Peek() 87 | entry := bucketEntry{ 88 | spatial: child, 89 | parent: node, 90 | transform: transform, 91 | cameraDelta: util.Vec3LenSq(mgl32.TransformCoordinate(child.Center(), transform).Sub(cameraLocation)), 92 | } 93 | if sceneGraph.bucketCount < len(sceneGraph.bucket) { 94 | sceneGraph.bucket[sceneGraph.bucketCount] = entry 95 | } else { 96 | sceneGraph.bucket = append(sceneGraph.bucket, entry) 97 | } 98 | sceneGraph.bucketCount = sceneGraph.bucketCount + 1 99 | } 100 | } 101 | sceneGraph.txStack.Pop() 102 | } 103 | -------------------------------------------------------------------------------- /renderer/shader.go: -------------------------------------------------------------------------------- 1 | package renderer 2 | 3 | type Shader struct { 4 | Program uint32 5 | Loaded bool 6 | 7 | Uniforms map[string]interface{} 8 | FragDataLocations []string 9 | InputBuffers int 10 | 11 | textureUnitCounter int32 12 | TextureUnits map[string]int32 13 | 14 | FragSrc, VertSrc, GeoSrc string 15 | } 16 | 17 | func NewShader() *Shader { 18 | return &Shader{ 19 | FragDataLocations: []string{"outputColor"}, 20 | Uniforms: make(map[string]interface{}), 21 | TextureUnits: make(map[string]int32), 22 | InputBuffers: 1, 23 | } 24 | } 25 | 26 | // AddTexture - allocates a texture unit to the shader 27 | func (shader *Shader) AddTexture(textureName string) int32 { 28 | index, ok := shader.TextureUnits[textureName] 29 | if !ok { 30 | index = shader.textureUnitCounter 31 | shader.textureUnitCounter = shader.textureUnitCounter + 1 32 | shader.TextureUnits[textureName] = index 33 | } 34 | 35 | shader.Uniforms[textureName] = index 36 | return index 37 | } 38 | 39 | func (shader *Shader) Copy() (copy *Shader) { 40 | copy = NewShader() 41 | copy.FragDataLocations = shader.FragDataLocations 42 | copy.FragSrc = shader.FragSrc 43 | copy.VertSrc = shader.VertSrc 44 | copy.GeoSrc = shader.GeoSrc 45 | copy.InputBuffers = shader.InputBuffers 46 | return 47 | } 48 | -------------------------------------------------------------------------------- /resources/cubemap.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/walesey/go-engine/e229e0e7d42b1d984bf7803a62878acfe39bde9d/resources/cubemap.png -------------------------------------------------------------------------------- /resources/cubemapNightSky.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/walesey/go-engine/e229e0e7d42b1d984bf7803a62878acfe39bde9d/resources/cubemapNightSky.jpg -------------------------------------------------------------------------------- /resources/fire.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/walesey/go-engine/e229e0e7d42b1d984bf7803a62878acfe39bde9d/resources/fire.png -------------------------------------------------------------------------------- /resources/majic.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/walesey/go-engine/e229e0e7d42b1d984bf7803a62878acfe39bde9d/resources/majic.png -------------------------------------------------------------------------------- /resources/smoke.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/walesey/go-engine/e229e0e7d42b1d984bf7803a62878acfe39bde9d/resources/smoke.png -------------------------------------------------------------------------------- /resources/space.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/walesey/go-engine/e229e0e7d42b1d984bf7803a62878acfe39bde9d/resources/space.jpg -------------------------------------------------------------------------------- /resources/spark.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/walesey/go-engine/e229e0e7d42b1d984bf7803a62878acfe39bde9d/resources/spark.png -------------------------------------------------------------------------------- /resources/stickman.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/walesey/go-engine/e229e0e7d42b1d984bf7803a62878acfe39bde9d/resources/stickman.png -------------------------------------------------------------------------------- /serializer/serialize.go: -------------------------------------------------------------------------------- 1 | package serializer 2 | 3 | import ( 4 | "fmt" 5 | "io" 6 | 7 | "github.com/go-gl/mathgl/mgl32" 8 | "github.com/go-gl/mathgl/mgl64" 9 | ) 10 | 11 | var GlobalSerializer = Serializer{ 12 | OnError: func(err error) { 13 | fmt.Println("serializer error: ", err) 14 | }, 15 | } 16 | 17 | func SerializeArgs(args ...interface{}) []byte { 18 | return GlobalSerializer.SerializeArgs(args...) 19 | } 20 | 21 | func Stringfrombytes(r io.Reader) string { 22 | return GlobalSerializer.Stringfrombytes(r) 23 | } 24 | 25 | func Stringbytes(w io.Writer, str string) { 26 | GlobalSerializer.Stringbytes(w, str) 27 | } 28 | 29 | func Float64frombytes(r io.Reader) float64 { 30 | return GlobalSerializer.Float64frombytes(r) 31 | } 32 | 33 | func Float64bytes(w io.Writer, float float64) { 34 | GlobalSerializer.Float64bytes(w, float) 35 | } 36 | 37 | func Float32frombytes(r io.Reader) float32 { 38 | return GlobalSerializer.Float32frombytes(r) 39 | } 40 | 41 | func Float32bytes(w io.Writer, float float32) { 42 | GlobalSerializer.Float32bytes(w, float) 43 | } 44 | 45 | func UInt8frombytes(r io.Reader) uint8 { 46 | return GlobalSerializer.UInt8frombytes(r) 47 | } 48 | 49 | func UInt8Bytes(w io.Writer, i uint8) { 50 | GlobalSerializer.UInt8Bytes(w, i) 51 | } 52 | 53 | func UInt16frombytes(r io.Reader) uint16 { 54 | return GlobalSerializer.UInt16frombytes(r) 55 | } 56 | 57 | func UInt16Bytes(w io.Writer, i uint16) { 58 | GlobalSerializer.UInt16Bytes(w, i) 59 | } 60 | 61 | func UInt32frombytes(r io.Reader) uint32 { 62 | return GlobalSerializer.UInt32frombytes(r) 63 | } 64 | 65 | func UInt32Bytes(w io.Writer, i uint32) { 66 | GlobalSerializer.UInt32Bytes(w, i) 67 | } 68 | 69 | func UInt64frombytes(r io.Reader) uint64 { 70 | return GlobalSerializer.UInt64frombytes(r) 71 | } 72 | 73 | func UInt64Bytes(w io.Writer, i uint64) { 74 | GlobalSerializer.UInt64Bytes(w, i) 75 | } 76 | 77 | func Vector2frombytes(r io.Reader) mgl32.Vec2 { 78 | return GlobalSerializer.Vector2frombytes(r) 79 | } 80 | 81 | func Vector2bytes(w io.Writer, vector mgl32.Vec2) { 82 | GlobalSerializer.Vector2bytes(w, vector) 83 | } 84 | 85 | func Vector2frombytes64(r io.Reader) mgl64.Vec2 { 86 | return GlobalSerializer.Vector2frombytes64(r) 87 | } 88 | 89 | func Vector2bytes64(w io.Writer, vector mgl64.Vec2) { 90 | GlobalSerializer.Vector2bytes64(w, vector) 91 | } 92 | 93 | func Vector3frombytes(r io.Reader) mgl32.Vec3 { 94 | return GlobalSerializer.Vector3frombytes(r) 95 | } 96 | 97 | func Vector3bytes(w io.Writer, vector mgl32.Vec3) { 98 | GlobalSerializer.Vector3bytes(w, vector) 99 | } 100 | 101 | func Vector3frombytes64(r io.Reader) mgl64.Vec3 { 102 | return GlobalSerializer.Vector3frombytes64(r) 103 | } 104 | 105 | func Vector3bytes64(w io.Writer, vector mgl64.Vec3) { 106 | GlobalSerializer.Vector3bytes64(w, vector) 107 | } 108 | 109 | func BoolFromBytes(r io.Reader) bool { 110 | return GlobalSerializer.BoolFromBytes(r) 111 | } 112 | 113 | func BoolBytes(w io.Writer, b bool) { 114 | GlobalSerializer.BoolBytes(w, b) 115 | } 116 | -------------------------------------------------------------------------------- /serializer/serializer.go: -------------------------------------------------------------------------------- 1 | package serializer 2 | 3 | import ( 4 | "io" 5 | 6 | "github.com/walesey/go-engine/util" 7 | 8 | "github.com/go-gl/mathgl/mgl32" 9 | "github.com/go-gl/mathgl/mgl64" 10 | ) 11 | 12 | type Serializer struct { 13 | OnError func(error) 14 | } 15 | 16 | func (s Serializer) SerializeArgs(args ...interface{}) []byte { 17 | result, err := util.SerializeArgs(args...) 18 | if err != nil { 19 | s.OnError(err) 20 | } 21 | return result 22 | } 23 | 24 | func (s Serializer) Stringfrombytes(r io.Reader) string { 25 | result, err := util.Stringfrombytes(r) 26 | if err != nil { 27 | s.OnError(err) 28 | } 29 | return result 30 | } 31 | 32 | func (s Serializer) Stringbytes(w io.Writer, str string) { 33 | if err := util.Stringbytes(w, str); err != nil { 34 | s.OnError(err) 35 | } 36 | } 37 | 38 | func (s Serializer) Float64frombytes(r io.Reader) float64 { 39 | result, err := util.Float64frombytes(r) 40 | if err != nil { 41 | s.OnError(err) 42 | } 43 | return result 44 | } 45 | 46 | func (s Serializer) Float64bytes(w io.Writer, float float64) { 47 | if err := util.Float64bytes(w, float); err != nil { 48 | s.OnError(err) 49 | } 50 | } 51 | 52 | func (s Serializer) Float32frombytes(r io.Reader) float32 { 53 | result, err := util.Float32frombytes(r) 54 | if err != nil { 55 | s.OnError(err) 56 | } 57 | return result 58 | } 59 | 60 | func (s Serializer) Float32bytes(w io.Writer, float float32) { 61 | if err := util.Float32bytes(w, float); err != nil { 62 | s.OnError(err) 63 | } 64 | } 65 | 66 | func (s Serializer) UInt8frombytes(r io.Reader) uint8 { 67 | result, err := util.UInt8frombytes(r) 68 | if err != nil { 69 | s.OnError(err) 70 | } 71 | return result 72 | } 73 | 74 | func (s Serializer) UInt8Bytes(w io.Writer, i uint8) { 75 | if err := util.UInt8Bytes(w, i); err != nil { 76 | s.OnError(err) 77 | } 78 | } 79 | 80 | func (s Serializer) UInt16frombytes(r io.Reader) uint16 { 81 | result, err := util.UInt16frombytes(r) 82 | if err != nil { 83 | s.OnError(err) 84 | } 85 | return result 86 | } 87 | 88 | func (s Serializer) UInt16Bytes(w io.Writer, i uint16) { 89 | if err := util.UInt16Bytes(w, i); err != nil { 90 | s.OnError(err) 91 | } 92 | } 93 | 94 | func (s Serializer) UInt32frombytes(r io.Reader) uint32 { 95 | result, err := util.UInt32frombytes(r) 96 | if err != nil { 97 | s.OnError(err) 98 | } 99 | return result 100 | } 101 | 102 | func (s Serializer) UInt32Bytes(w io.Writer, i uint32) { 103 | if err := util.UInt32Bytes(w, i); err != nil { 104 | s.OnError(err) 105 | } 106 | } 107 | 108 | func (s Serializer) UInt64frombytes(r io.Reader) uint64 { 109 | result, err := util.UInt64frombytes(r) 110 | if err != nil { 111 | s.OnError(err) 112 | } 113 | return result 114 | } 115 | 116 | func (s Serializer) UInt64Bytes(w io.Writer, i uint64) { 117 | if err := util.UInt64Bytes(w, i); err != nil { 118 | s.OnError(err) 119 | } 120 | } 121 | 122 | func (s Serializer) Vector2frombytes(r io.Reader) mgl32.Vec2 { 123 | result, err := util.Vector2frombytes(r) 124 | if err != nil { 125 | s.OnError(err) 126 | } 127 | return result 128 | } 129 | 130 | func (s Serializer) Vector2bytes(w io.Writer, vector mgl32.Vec2) { 131 | if err := util.Vector2bytes(w, vector); err != nil { 132 | s.OnError(err) 133 | } 134 | } 135 | 136 | func (s Serializer) Vector2frombytes64(r io.Reader) mgl64.Vec2 { 137 | result, err := util.Vector2frombytes64(r) 138 | if err != nil { 139 | s.OnError(err) 140 | } 141 | return result 142 | } 143 | 144 | func (s Serializer) Vector2bytes64(w io.Writer, vector mgl64.Vec2) { 145 | if err := util.Vector2bytes64(w, vector); err != nil { 146 | s.OnError(err) 147 | } 148 | } 149 | 150 | func (s Serializer) Vector3frombytes(r io.Reader) mgl32.Vec3 { 151 | result, err := util.Vector3frombytes(r) 152 | if err != nil { 153 | s.OnError(err) 154 | } 155 | return result 156 | } 157 | 158 | func (s Serializer) Vector3bytes(w io.Writer, vector mgl32.Vec3) { 159 | if err := util.Vector3bytes(w, vector); err != nil { 160 | s.OnError(err) 161 | } 162 | } 163 | 164 | func (s Serializer) Vector3frombytes64(r io.Reader) mgl64.Vec3 { 165 | result, err := util.Vector3frombytes64(r) 166 | if err != nil { 167 | s.OnError(err) 168 | } 169 | return result 170 | } 171 | 172 | func (s Serializer) Vector3bytes64(w io.Writer, vector mgl64.Vec3) { 173 | if err := util.Vector3bytes64(w, vector); err != nil { 174 | s.OnError(err) 175 | } 176 | } 177 | 178 | func (s Serializer) BoolFromBytes(r io.Reader) bool { 179 | result, err := util.BoolFromBytes(r) 180 | if err != nil { 181 | s.OnError(err) 182 | } 183 | return result 184 | } 185 | 186 | func (s Serializer) BoolBytes(w io.Writer, b bool) { 187 | if err := util.BoolBytes(w, b); err != nil { 188 | s.OnError(err) 189 | } 190 | } 191 | -------------------------------------------------------------------------------- /shaderBuilder/README.md: -------------------------------------------------------------------------------- 1 | # ShaderBuilder 2 | 3 | A Shader preprocessor that handles the following # macros 4 | * #include - works like c include for local shader files only 5 | * #vert / #endvert - code in this section is only outputted to the vertex shader. 6 | * #frag / #endfrag 7 | * #geo / #geo 8 | 9 | The output of the preprocessor is a single shader file to be compiled by opengl etc. 10 | 11 | ### Example Usage 12 | 13 | ``` 14 | shaderBuilder path/to/file.glsl vert > out.vert 15 | shaderBuilder path/to/file.glsl frag > out.frag 16 | ``` 17 | -------------------------------------------------------------------------------- /shaderBuilder/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "bytes" 5 | "fmt" 6 | "os" 7 | "regexp" 8 | 9 | "github.com/walesey/go-engine/shaderBuilder/parser" 10 | ) 11 | 12 | func main() { 13 | srcFile := "./index.glsl" 14 | if len(os.Args) >= 2 { 15 | srcFile = os.Args[1] 16 | } 17 | 18 | mode := "vert" 19 | if len(os.Args) >= 3 { 20 | mode = os.Args[2] 21 | } 22 | 23 | out := new(bytes.Buffer) 24 | switch mode { 25 | case "vert": 26 | parser.ParseFile(srcFile, out, nil, nil) 27 | case "frag": 28 | parser.ParseFile(srcFile, nil, out, nil) 29 | case "geo": 30 | parser.ParseFile(srcFile, nil, nil, out) 31 | default: 32 | panic("Invalid shader type: " + mode) 33 | } 34 | output := out.String() 35 | 36 | re := regexp.MustCompile("\n[\\s]+\n[\\s]+\n") 37 | fmt.Println(re.ReplaceAllString(output, "\n\n")) 38 | } 39 | -------------------------------------------------------------------------------- /shaderBuilder/parser/expression.go: -------------------------------------------------------------------------------- 1 | package parser 2 | 3 | import ( 4 | "math" 5 | "strconv" 6 | ) 7 | 8 | type Variable struct { 9 | Name string 10 | Value float64 11 | } 12 | 13 | type Expression interface { 14 | Evaluate(variables ...Variable) float64 15 | } 16 | 17 | type BinaryExpression struct { 18 | Operator Token 19 | Left Expression 20 | Right Expression 21 | } 22 | 23 | func (b BinaryExpression) Evaluate(variables ...Variable) float64 { 24 | switch b.Operator { 25 | case PLUS: 26 | return b.Left.Evaluate(variables...) + b.Right.Evaluate(variables...) 27 | case MINUS: 28 | return b.Left.Evaluate(variables...) - b.Right.Evaluate(variables...) 29 | case MULTIPLY: 30 | return b.Left.Evaluate(variables...) * b.Right.Evaluate(variables...) 31 | case SLASH: 32 | return b.Left.Evaluate(variables...) / b.Right.Evaluate(variables...) 33 | case REMAINDER: 34 | leftInt, rightInt := int(b.Left.Evaluate(variables...)), int(b.Right.Evaluate(variables...)) 35 | return float64(leftInt % rightInt) 36 | case POWER: 37 | return math.Pow(b.Left.Evaluate(variables...), b.Right.Evaluate(variables...)) 38 | default: 39 | } 40 | return 0 41 | } 42 | 43 | type Identifier struct { 44 | Name string 45 | } 46 | 47 | func (i Identifier) Evaluate(variables ...Variable) float64 { 48 | for _, variable := range variables { 49 | if variable.Name == i.Name { 50 | return variable.Value 51 | } 52 | } 53 | return 0 54 | } 55 | 56 | type Literal struct { 57 | Value float64 58 | } 59 | 60 | func (l Literal) Evaluate(variables ...Variable) float64 { 61 | return l.Value 62 | } 63 | 64 | func (p *Parser) parseSignedExpression() Expression { 65 | var left Expression = Literal{Value: 0} 66 | 67 | if p.token == PLUS || p.token == MINUS { 68 | if p.token == MINUS { 69 | left = Literal{-1} 70 | } else { 71 | left = Literal{1} 72 | } 73 | p.nextNoWhitespace() 74 | 75 | left = BinaryExpression{ 76 | Operator: MULTIPLY, 77 | Left: left, 78 | Right: p.parseExpression(), 79 | } 80 | } 81 | 82 | return left 83 | } 84 | 85 | func (p *Parser) parseGroupExpression() Expression { 86 | exp := p.parseSignedExpression() 87 | 88 | if p.token == LEFT_PARENTHESIS { 89 | p.nextNoWhitespace() 90 | exp = p.parseExpression() 91 | 92 | if p.token != RIGHT_PARENTHESIS { 93 | p.unexpectedError() 94 | } 95 | p.nextNoWhitespace() 96 | } 97 | 98 | return exp 99 | } 100 | 101 | func (p *Parser) parseIdentifierExpression() Expression { 102 | exp := p.parseGroupExpression() 103 | 104 | if p.token == IDENTIFIER { 105 | exp = Identifier{Name: p.literal} 106 | p.nextNoWhitespace() 107 | } 108 | 109 | return exp 110 | } 111 | 112 | func (p *Parser) parseLiteralExpression() Expression { 113 | exp := p.parseIdentifierExpression() 114 | 115 | if p.token == NUMBER { 116 | value, _ := strconv.ParseFloat(p.literal, 64) 117 | exp = Literal{Value: value} 118 | p.nextNoWhitespace() 119 | } 120 | 121 | return exp 122 | } 123 | 124 | func (p *Parser) parsePowerExpression() Expression { 125 | nextParse := p.parseLiteralExpression 126 | left := nextParse() 127 | 128 | for p.token == POWER { 129 | tkn := p.token 130 | p.nextNoWhitespace() 131 | 132 | left = BinaryExpression{ 133 | Operator: tkn, 134 | Left: left, 135 | Right: nextParse(), 136 | } 137 | } 138 | 139 | return left 140 | } 141 | 142 | func (p *Parser) parseMultiplicativeExpression() Expression { 143 | nextParse := p.parsePowerExpression 144 | left := nextParse() 145 | 146 | for p.token == MULTIPLY || p.token == SLASH || p.token == REMAINDER { 147 | tkn := p.token 148 | p.nextNoWhitespace() 149 | 150 | left = BinaryExpression{ 151 | Operator: tkn, 152 | Left: left, 153 | Right: nextParse(), 154 | } 155 | } 156 | 157 | return left 158 | } 159 | 160 | func (p *Parser) parseAdditiveExpression() Expression { 161 | nextParse := p.parseMultiplicativeExpression 162 | left := nextParse() 163 | 164 | for p.token == PLUS || p.token == MINUS { 165 | tkn := p.token 166 | p.nextNoWhitespace() 167 | 168 | left = BinaryExpression{ 169 | Operator: tkn, 170 | Left: left, 171 | Right: nextParse(), 172 | } 173 | } 174 | 175 | return left 176 | } 177 | 178 | func (p *Parser) parseExpression() Expression { 179 | return p.parseAdditiveExpression() 180 | } 181 | -------------------------------------------------------------------------------- /shaderBuilder/parser/lexer.go: -------------------------------------------------------------------------------- 1 | package parser 2 | 3 | import "bytes" 4 | 5 | const eof = rune(0) 6 | 7 | func (p *Parser) scan() (tkn Token, literal string) { 8 | p.read() 9 | 10 | if isWhitespace(p.chr) { 11 | tkn, literal = p.scanWhitespace() 12 | return 13 | } else if isLetter(p.chr) { 14 | tkn, literal = p.scanIdentifier() 15 | return 16 | } else if isNumeric(p.chr) { 17 | tkn, literal = NUMBER, p.scanNumber() 18 | return 19 | } 20 | 21 | switch p.chr { 22 | case eof: 23 | tkn, literal = EOF, "" 24 | case '(': 25 | tkn, literal = LEFT_PARENTHESIS, string(p.chr) 26 | case ')': 27 | tkn, literal = RIGHT_PARENTHESIS, string(p.chr) 28 | case '#': 29 | tkn, literal = HASH, string(p.chr) 30 | case '+': 31 | tkn, literal = PLUS, string(p.chr) 32 | case '-': 33 | tkn, literal = MINUS, string(p.chr) 34 | case '*': 35 | tkn, literal = MULTIPLY, string(p.chr) 36 | case '/': 37 | tkn, literal = SLASH, string(p.chr) 38 | case '%': 39 | tkn, literal = REMAINDER, string(p.chr) 40 | case '^': 41 | tkn, literal = POWER, string(p.chr) 42 | case '"': 43 | tkn, literal = STRING, p.scanString() 44 | default: 45 | tkn, literal = UNKNOWN, string(p.chr) 46 | } 47 | 48 | return 49 | } 50 | 51 | func (p *Parser) read() { 52 | chr, _, err := p.in.ReadRune() 53 | if err != nil { 54 | p.chr = eof 55 | } else { 56 | p.chr = chr 57 | } 58 | } 59 | 60 | func (p *Parser) unread() { 61 | _ = p.in.UnreadRune() 62 | } 63 | 64 | func isWhitespace(ch rune) bool { 65 | return ch == ' ' || ch == '\t' || ch == '\n' 66 | } 67 | 68 | func isLetter(ch rune) bool { 69 | return (ch >= 'a' && ch <= 'z') || (ch >= 'A' && ch <= 'Z') 70 | } 71 | 72 | func isDigit(ch rune) bool { 73 | return '0' <= ch && ch <= '9' 74 | } 75 | 76 | func isNumeric(ch rune) bool { 77 | return isDigit(ch) || ch == '.' 78 | } 79 | 80 | func isIdentifierRune(ch rune) bool { 81 | return isLetter(ch) || isDigit(ch) || ch == '_' 82 | } 83 | 84 | func (p *Parser) scanWhitespace() (tkn Token, literal string) { 85 | var buf bytes.Buffer 86 | buf.WriteRune(p.chr) 87 | 88 | for { 89 | if p.read(); p.chr == eof { 90 | break 91 | } else if !isWhitespace(p.chr) { 92 | p.unread() 93 | break 94 | } else { 95 | buf.WriteRune(p.chr) 96 | } 97 | } 98 | 99 | tkn, literal = WHITESPACE, buf.String() 100 | return 101 | } 102 | 103 | func (p *Parser) scanIdentifier() (tkn Token, literal string) { 104 | var buf bytes.Buffer 105 | buf.WriteRune(p.chr) 106 | 107 | for { 108 | if p.read(); p.chr == eof { 109 | break 110 | } else if !isIdentifierRune(p.chr) { 111 | p.unread() 112 | break 113 | } else { 114 | buf.WriteRune(p.chr) 115 | } 116 | } 117 | 118 | tkn, literal = IDENTIFIER, buf.String() 119 | 120 | // is it a keyword 121 | tkn = checkKeyword(literal) 122 | 123 | return 124 | } 125 | 126 | func (p *Parser) scanNumber() (literal string) { 127 | var buf bytes.Buffer 128 | buf.WriteRune(p.chr) 129 | 130 | for { 131 | if p.read(); p.chr == eof { 132 | break 133 | } else if !isNumeric(p.chr) { 134 | p.unread() 135 | break 136 | } else { 137 | buf.WriteRune(p.chr) 138 | } 139 | } 140 | 141 | return buf.String() 142 | } 143 | 144 | func (p *Parser) scanString() string { 145 | var buf bytes.Buffer 146 | 147 | for { 148 | if p.read(); p.chr == eof || p.chr == '"' { 149 | break 150 | } else { 151 | buf.WriteRune(p.chr) 152 | } 153 | } 154 | 155 | return buf.String() 156 | } 157 | -------------------------------------------------------------------------------- /shaderBuilder/parser/parser_test.go: -------------------------------------------------------------------------------- 1 | package parser 2 | 3 | import ( 4 | "bytes" 5 | "os" 6 | "testing" 7 | 8 | "github.com/stretchr/testify/assert" 9 | "regexp" 10 | ) 11 | 12 | func fixNewLines(str string) string { 13 | re := regexp.MustCompile(`[\n|\r]+`) 14 | return re.ReplaceAllString(str, "\n") 15 | } 16 | 17 | func TestGenerator(t *testing.T) { 18 | expectedFragFd, err := os.Open("./test/expected.frag") 19 | assert.NoError(t, err) 20 | defer expectedFragFd.Close() 21 | 22 | expectedVertFd, err := os.Open("./test/expected.vert") 23 | assert.NoError(t, err) 24 | defer expectedVertFd.Close() 25 | 26 | shaderPath := "./test/test.glsl" 27 | frag := new(bytes.Buffer) 28 | vert := new(bytes.Buffer) 29 | err = ParseFile(shaderPath, vert, frag, nil) 30 | 31 | expectedFrag := new(bytes.Buffer) 32 | expectedFrag.ReadFrom(expectedFragFd) 33 | assert.Equal(t, fixNewLines(expectedFrag.String()), fixNewLines(frag.String())) 34 | 35 | expectedVert := new(bytes.Buffer) 36 | expectedVert.ReadFrom(expectedVertFd) 37 | assert.Equal(t, fixNewLines(expectedVert.String()), fixNewLines(vert.String())) 38 | } 39 | -------------------------------------------------------------------------------- /shaderBuilder/parser/test/expected.frag: -------------------------------------------------------------------------------- 1 | #version 330 2 | 3 | float import1() { 4 | return 1.0; 5 | } 6 | 7 | uniform float lookupTable[10] = float[] (0.01, 0.02, 0.03, 0.04, 0.05, 0.06, 0.07, 0.08, 0.09, 0.1); 8 | uniform float lookupTable2[10] = float[] (-0.01, -0.02, -0.03, -0.04, -0.05, -0.06, -0.07, -0.08, -0.09, -0.1); 9 | 10 | vec4 fragFunc() { 11 | output = vec4(0.5); 12 | for (int i = 0; i < 10; ++i) { 13 | output += vec4(lookupTable[i]); 14 | } 15 | return vec4(0.5); 16 | } 17 | 18 | void main() { 19 | gl_FragColor = fragFunc(); 20 | } 21 | -------------------------------------------------------------------------------- /shaderBuilder/parser/test/expected.vert: -------------------------------------------------------------------------------- 1 | #version 330 2 | 3 | float import1() { 4 | return 1.0; 5 | } 6 | 7 | vec4 vertFunc() { 8 | return vec4(1.0); 9 | } 10 | 11 | void main() { 12 | gl_Position = vertFunc(); 13 | } 14 | 15 | -------------------------------------------------------------------------------- /shaderBuilder/parser/test/includeTest.glsl: -------------------------------------------------------------------------------- 1 | #include "includeTest2.glsl" 2 | 3 | #frag 4 | uniform #lookup 10 lookupTable ((i + 1) / 100.0); 5 | uniform #lookup 10 lookupTable2 ((i + 1) / -100.0); 6 | 7 | vec4 fragFunc() { 8 | output = vec4(0.5); 9 | for (int i = 0; i < 10; ++i) { 10 | output += vec4(lookupTable[i]); 11 | } 12 | return vec4(0.5); 13 | } 14 | #endfrag 15 | 16 | #vert 17 | vec4 vertFunc() { 18 | return vec4(1.0); 19 | } 20 | #endvert -------------------------------------------------------------------------------- /shaderBuilder/parser/test/includeTest2.glsl: -------------------------------------------------------------------------------- 1 | float import1() { 2 | return 1.0; 3 | } -------------------------------------------------------------------------------- /shaderBuilder/parser/test/test.glsl: -------------------------------------------------------------------------------- 1 | #version 330 2 | 3 | #include "includeTest.glsl" 4 | #include "includeTest2.glsl" 5 | 6 | void main() { 7 | #vert 8 | gl_Position = vertFunc(); 9 | #endvert 10 | 11 | #frag 12 | gl_FragColor = fragFunc(); 13 | #endfrag 14 | } 15 | -------------------------------------------------------------------------------- /shaderBuilder/parser/token.go: -------------------------------------------------------------------------------- 1 | package parser 2 | 3 | type Token int 4 | 5 | const ( 6 | _ Token = iota 7 | 8 | EOF 9 | WHITESPACE 10 | IDENTIFIER 11 | STRING 12 | NUMBER 13 | 14 | LEFT_PARENTHESIS // ( 15 | RIGHT_PARENTHESIS // ) 16 | 17 | HASH // # 18 | PLUS // + 19 | MINUS // - 20 | MULTIPLY // * 21 | SLASH // / 22 | REMAINDER // % 23 | POWER // ^ 24 | UNKNOWN 25 | 26 | // keywords 27 | INCLUDE 28 | VERT 29 | FRAG 30 | GEO 31 | ENDVERT 32 | ENDFRAG 33 | ENDGEO 34 | LOOKUP 35 | ) 36 | 37 | func checkKeyword(literal string) Token { 38 | switch literal { 39 | case "include": 40 | return INCLUDE 41 | case "vert": 42 | return VERT 43 | case "frag": 44 | return FRAG 45 | case "geo": 46 | return GEO 47 | case "endvert": 48 | return ENDVERT 49 | case "endfrag": 50 | return ENDFRAG 51 | case "endgeo": 52 | return ENDGEO 53 | case "lookup": 54 | return LOOKUP 55 | } 56 | return IDENTIFIER 57 | } 58 | -------------------------------------------------------------------------------- /shaders/basic.glsl: -------------------------------------------------------------------------------- 1 | #version 400 2 | 3 | #include "./lib/base.glsl" 4 | #include "./lib/worldTransform.glsl" 5 | #include "./lib/textures.glsl" 6 | #include "./lib/ambientLight.glsl" 7 | #include "./lib/pointLights.glsl" 8 | #include "./lib/directionalLights.glsl" 9 | 10 | void main() { 11 | textures(); 12 | 13 | #vert 14 | worldTransform(); 15 | gl_Position = projection * camera * model * vec4(vert, 1); 16 | #endvert 17 | 18 | #frag 19 | if (unlit) { 20 | outputColor = diffuse; 21 | } else { 22 | vec3 finalColor = ambientLight(diffuse) + pointLights(diffuse, specular, normalValue) + directionalLights(diffuse, specular, normalValue); 23 | outputColor = vec4(finalColor, diffuse.a); 24 | } 25 | #endfrag 26 | } -------------------------------------------------------------------------------- /shaders/build/basic.frag: -------------------------------------------------------------------------------- 1 | #version 400 2 | 3 | uniform mat4 projection; 4 | uniform mat4 camera; 5 | uniform mat4 model; 6 | uniform mat4 modelNormal; 7 | uniform vec3 cameraTranslation; 8 | 9 | uniform bool unlit; 10 | uniform bool useTextures; 11 | 12 | layout(location = 0) out vec4 outputColor; 13 | 14 | in vec3 worldVertex; 15 | in vec3 worldNormal; 16 | in vec3 eyeDirection; 17 | in mat3 TBNMatrix; 18 | in mat3 inverseTBNMatrix; 19 | 20 | uniform sampler2D normalMap; 21 | uniform sampler2D diffuseMap; 22 | uniform sampler2D specularMap; 23 | uniform sampler2D aoMap; 24 | 25 | in vec2 fragTexCoord; 26 | in vec4 fragColor; 27 | 28 | vec4 normalValue; 29 | vec4 diffuse; 30 | vec4 specular; 31 | vec4 ao; 32 | 33 | vec2 repeatTextCoord() { 34 | float textureX = fragTexCoord.x - int(fragTexCoord.x); 35 | float textureY = fragTexCoord.y - int(fragTexCoord.y); 36 | if (fragTexCoord.x < 0) {textureX = textureX + 1.0;} 37 | if (fragTexCoord.y < 0) {textureY = textureY + 1.0;} 38 | return vec2(textureX, textureY); 39 | } 40 | 41 | void textures() { 42 | vec2 overflowTextCoord = repeatTextCoord(); 43 | 44 | // multiply color by diffuse map. use only color if no map is provided 45 | if (useTextures) { 46 | diffuse = fragColor * texture(diffuseMap, overflowTextCoord); 47 | specular = texture(specularMap, overflowTextCoord); 48 | normalValue = texture(normalMap, overflowTextCoord); 49 | ao = texture(aoMap, overflowTextCoord); 50 | } else { 51 | diffuse = fragColor; 52 | specular = vec4(0); 53 | normalValue = vec4(0); 54 | ao = vec4(1); 55 | } 56 | } 57 | 58 | uniform vec3 ambientLightValue; 59 | 60 | vec3 ambientLight(vec4 diffuse) { 61 | return ambientLightValue * diffuse.rgb; 62 | } 63 | 64 | float pow2(float x) { 65 | return x*x; 66 | } 67 | 68 | float pow3(float x) { 69 | return x*x*x; 70 | } 71 | 72 | 73 | vec3 directLight( vec3 light, vec3 direction, vec4 diffuse, vec4 specular, vec4 normalValue ) { 74 | vec3 normal_tangentSpace = (normalValue.xyz*2) - 1; 75 | vec3 direction_tangentSpace = direction * TBNMatrix; 76 | vec3 eyeDirection_tangentSpace = eyeDirection * TBNMatrix; 77 | vec3 reflectedEye_tangentSpace = reflect( eyeDirection_tangentSpace, normal_tangentSpace ); 78 | 79 | float diffuseMultiplier = max(0.0, dot(normal_tangentSpace, -direction_tangentSpace)); 80 | 81 | float specularMultiplier = pow2(max(0.0, dot(reflectedEye_tangentSpace, -direction_tangentSpace))); 82 | 83 | vec3 color = (diffuseMultiplier * diffuse.rgb) + (specularMultiplier * specular.rgb); 84 | 85 | return color * light; 86 | } 87 | 88 | 89 | 90 | #define MAX_POINT_LIGHTS 4 91 | 92 | uniform int nbPointLights; 93 | uniform vec4 pointLightPositions[ MAX_POINT_LIGHTS ]; 94 | uniform vec4 pointLightValues[ MAX_POINT_LIGHTS ]; 95 | 96 | vec3 pointLights(vec4 diffuse, vec4 specular, vec4 normalValue) { 97 | vec3 totalLight = vec3(0.0, 0.0, 0.0); 98 | for (int i=0; i < nbPointLights; i++) { 99 | vec3 LightPos = pointLightPositions[i].rgb; 100 | vec3 LightValue = pointLightValues[i].rgb; 101 | 102 | vec3 v = worldVertex - LightPos; 103 | float lightDistance = dot(v, v); 104 | float brightness = 1.0 / lightDistance; 105 | 106 | vec3 worldLightDir = normalize(v); 107 | vec3 light = brightness*LightValue; 108 | 109 | totalLight += directLight(light, worldLightDir, diffuse, specular, normalValue); 110 | } 111 | return totalLight; 112 | } 113 | 114 | #define MAX_DIRECTIONAL_LIGHTS 4 115 | 116 | uniform int nbDirectionalLights; 117 | uniform vec4 directionalLightVectors[ MAX_DIRECTIONAL_LIGHTS ]; 118 | uniform vec4 directionalLightValues[ MAX_DIRECTIONAL_LIGHTS ]; 119 | 120 | vec3 directionalLights(vec4 diffuse, vec4 specular, vec4 normalValue) { 121 | vec3 totalLight = vec3(0.0, 0.0, 0.0); 122 | for (int i=0; i < nbDirectionalLights; i++) { 123 | vec3 LightDirection = directionalLightVectors[i].rgb; 124 | vec3 LightValue = directionalLightValues[i].rgb; 125 | 126 | totalLight += directLight(LightValue, LightDirection, diffuse, specular, normalValue); 127 | } 128 | return totalLight; 129 | } 130 | 131 | 132 | void main() { 133 | textures(); 134 | 135 | if (unlit) { 136 | outputColor = diffuse; 137 | } else { 138 | vec3 finalColor = ambientLight(diffuse) + pointLights(diffuse, specular, normalValue) + directionalLights(diffuse, specular, normalValue); 139 | outputColor = vec4(finalColor, diffuse.a); 140 | } 141 | 142 | } 143 | -------------------------------------------------------------------------------- /shaders/build/basic.vert: -------------------------------------------------------------------------------- 1 | #version 400 2 | 3 | uniform mat4 projection; 4 | uniform mat4 camera; 5 | uniform mat4 model; 6 | uniform mat4 modelNormal; 7 | uniform vec3 cameraTranslation; 8 | 9 | uniform bool unlit; 10 | uniform bool useTextures; 11 | 12 | 13 | in vec3 vert; 14 | in vec3 normal; 15 | in vec2 texCoord; 16 | in vec4 color; 17 | 18 | out vec3 worldVertex; 19 | out vec3 worldNormal; 20 | out vec3 eyeDirection; 21 | out mat3 TBNMatrix; 22 | out mat3 inverseTBNMatrix; 23 | 24 | void worldTransform() { 25 | worldVertex = (model * vec4(vert,1)).xyz; 26 | worldNormal = (modelNormal * vec4(normal,1)).xyz; 27 | worldNormal = normalize(worldNormal); 28 | eyeDirection = normalize(worldVertex - cameraTranslation); 29 | 30 | // generate arbitrary tangent and bitangent to the normal 31 | vec3 tangent = cross(normal, normal + vec3(-1)); 32 | vec3 bitangent = cross(normal, tangent); 33 | vec3 worldTangent = normalize((modelNormal * vec4(tangent,1)).xyz); 34 | vec3 worldBitangent = normalize((modelNormal * vec4(bitangent,1)).xyz); 35 | 36 | //tangent space conversion - worldToTangent 37 | TBNMatrix = mat3(worldTangent, worldBitangent, worldNormal); 38 | inverseTBNMatrix = inverse(TBNMatrix); 39 | } 40 | 41 | out vec2 fragTexCoord; 42 | out vec4 fragColor; 43 | 44 | void textures() { 45 | fragTexCoord = texCoord; 46 | fragColor = color; 47 | } 48 | 49 | float pow2(float x) { 50 | return x*x; 51 | } 52 | 53 | float pow3(float x) { 54 | return x*x*x; 55 | } 56 | 57 | 58 | 59 | void main() { 60 | textures(); 61 | 62 | 63 | worldTransform(); 64 | gl_Position = projection * camera * model * vec4(vert, 1); 65 | 66 | } 67 | -------------------------------------------------------------------------------- /shaders/build/diffuseSpecular.vert: -------------------------------------------------------------------------------- 1 | #version 400 2 | 3 | uniform mat4 projection; 4 | uniform mat4 camera; 5 | uniform mat4 model; 6 | uniform mat4 modelNormal; 7 | uniform vec3 cameraTranslation; 8 | 9 | uniform bool unlit; 10 | uniform bool useTextures; 11 | 12 | 13 | in vec3 vert; 14 | in vec3 normal; 15 | in vec2 texCoord; 16 | in vec4 color; 17 | 18 | out vec3 worldVertex; 19 | out vec3 worldNormal; 20 | out vec3 eyeDirection; 21 | out mat3 TBNMatrix; 22 | out mat3 inverseTBNMatrix; 23 | 24 | void worldTransform() { 25 | worldVertex = (model * vec4(vert,1)).xyz; 26 | worldNormal = (modelNormal * vec4(normal,1)).xyz; 27 | worldNormal = normalize(worldNormal); 28 | eyeDirection = normalize(worldVertex - cameraTranslation); 29 | 30 | // generate arbitrary tangent and bitangent to the normal 31 | vec3 tangent = cross(normal, normal + vec3(-1)); 32 | vec3 bitangent = cross(normal, tangent); 33 | vec3 worldTangent = normalize((modelNormal * vec4(tangent,1)).xyz); 34 | vec3 worldBitangent = normalize((modelNormal * vec4(bitangent,1)).xyz); 35 | 36 | //tangent space conversion - worldToTangent 37 | TBNMatrix = mat3(worldTangent, worldBitangent, worldNormal); 38 | inverseTBNMatrix = inverse(TBNMatrix); 39 | } 40 | 41 | out vec2 fragTexCoord; 42 | out vec4 fragColor; 43 | 44 | void textures() { 45 | fragTexCoord = texCoord; 46 | fragColor = color; 47 | } 48 | 49 | float pow2(float x) { 50 | return x*x; 51 | } 52 | 53 | float pow3(float x) { 54 | return x*x*x; 55 | } 56 | 57 | 58 | 59 | void roughnessTexture() {} 60 | 61 | void glowOutput() {} 62 | 63 | void main() { 64 | textures(); 65 | roughnessTexture(); 66 | glowOutput(); 67 | 68 | 69 | worldTransform(); 70 | gl_Position = projection * camera * model * vec4(vert, 1); 71 | 72 | } 73 | -------------------------------------------------------------------------------- /shaders/build/pbr.vert: -------------------------------------------------------------------------------- 1 | #version 400 2 | 3 | uniform mat4 projection; 4 | uniform mat4 camera; 5 | uniform mat4 model; 6 | uniform mat4 modelNormal; 7 | uniform vec3 cameraTranslation; 8 | 9 | uniform bool unlit; 10 | uniform bool useTextures; 11 | 12 | 13 | in vec3 vert; 14 | in vec3 normal; 15 | in vec2 texCoord; 16 | in vec4 color; 17 | 18 | out vec3 worldVertex; 19 | out vec3 worldNormal; 20 | out vec3 eyeDirection; 21 | out mat3 TBNMatrix; 22 | out mat3 inverseTBNMatrix; 23 | 24 | void worldTransform() { 25 | worldVertex = (model * vec4(vert,1)).xyz; 26 | worldNormal = (modelNormal * vec4(normal,1)).xyz; 27 | worldNormal = normalize(worldNormal); 28 | eyeDirection = normalize(worldVertex - cameraTranslation); 29 | 30 | // generate arbitrary tangent and bitangent to the normal 31 | vec3 tangent = cross(normal, normal + vec3(-1)); 32 | vec3 bitangent = cross(normal, tangent); 33 | vec3 worldTangent = normalize((modelNormal * vec4(tangent,1)).xyz); 34 | vec3 worldBitangent = normalize((modelNormal * vec4(bitangent,1)).xyz); 35 | 36 | //tangent space conversion - worldToTangent 37 | TBNMatrix = mat3(worldTangent, worldBitangent, worldNormal); 38 | inverseTBNMatrix = inverse(TBNMatrix); 39 | } 40 | 41 | out vec2 fragTexCoord; 42 | out vec4 fragColor; 43 | 44 | void textures() { 45 | fragTexCoord = texCoord; 46 | fragColor = color; 47 | } 48 | 49 | void metalnessTexture() {} 50 | 51 | void roughnessTexture() {} 52 | 53 | float pow2(float x) { 54 | return x*x; 55 | } 56 | 57 | float pow3(float x) { 58 | return x*x*x; 59 | } 60 | 61 | void glowOutput() {} 62 | 63 | void main() { 64 | textures(); 65 | metalnessTexture(); 66 | roughnessTexture(); 67 | glowOutput(); 68 | 69 | 70 | worldTransform(); 71 | gl_Position = projection * camera * model * vec4(vert, 1); 72 | 73 | } 74 | -------------------------------------------------------------------------------- /shaders/build/pbrComposite.vert: -------------------------------------------------------------------------------- 1 | #version 400 2 | 3 | uniform mat4 projection; 4 | uniform mat4 camera; 5 | uniform mat4 model; 6 | uniform mat4 modelNormal; 7 | uniform vec3 cameraTranslation; 8 | 9 | uniform bool unlit; 10 | uniform bool useTextures; 11 | 12 | 13 | in vec3 vert; 14 | in vec3 normal; 15 | in vec2 texCoord; 16 | in vec4 color; 17 | 18 | out vec3 worldVertex; 19 | out vec3 worldNormal; 20 | out vec3 eyeDirection; 21 | out mat3 TBNMatrix; 22 | out mat3 inverseTBNMatrix; 23 | 24 | void worldTransform() { 25 | worldVertex = (model * vec4(vert,1)).xyz; 26 | worldNormal = (modelNormal * vec4(normal,1)).xyz; 27 | worldNormal = normalize(worldNormal); 28 | eyeDirection = normalize(worldVertex - cameraTranslation); 29 | 30 | // generate arbitrary tangent and bitangent to the normal 31 | vec3 tangent = cross(normal, normal + vec3(-1)); 32 | vec3 bitangent = cross(normal, tangent); 33 | vec3 worldTangent = normalize((modelNormal * vec4(tangent,1)).xyz); 34 | vec3 worldBitangent = normalize((modelNormal * vec4(bitangent,1)).xyz); 35 | 36 | //tangent space conversion - worldToTangent 37 | TBNMatrix = mat3(worldTangent, worldBitangent, worldNormal); 38 | inverseTBNMatrix = inverse(TBNMatrix); 39 | } 40 | 41 | out vec2 fragTexCoord; 42 | out vec4 fragColor; 43 | 44 | void textures() { 45 | fragTexCoord = texCoord; 46 | fragColor = color; 47 | } 48 | 49 | void pbrCompositeTextures() {} 50 | 51 | float pow2(float x) { 52 | return x*x; 53 | } 54 | 55 | float pow3(float x) { 56 | return x*x*x; 57 | } 58 | 59 | void glowOutput() {} 60 | 61 | void main() { 62 | textures(); 63 | pbrCompositeTextures(); 64 | glowOutput(); 65 | 66 | 67 | worldTransform(); 68 | gl_Position = projection * camera * model * vec4(vert, 1); 69 | 70 | } 71 | -------------------------------------------------------------------------------- /shaders/build/postEffects/cell.frag: -------------------------------------------------------------------------------- 1 | #version 330 2 | 3 | uniform sampler2D tex0; 4 | in vec2 fragTexCoord; 5 | out vec4 outputColor; 6 | 7 | float cell( float source ) { 8 | if (source < 0.3) { 9 | return 0; 10 | } else if (source < 0.6) { 11 | return 0.3; 12 | } else if (source < 0.9) { 13 | return 0.6; 14 | } 15 | return 1; 16 | } 17 | 18 | 19 | void main() { 20 | 21 | vec4 finalColor = vec4(0,0,0,1); 22 | vec4 source = texture(tex0, fragTexCoord); 23 | finalColor = vec4( cell(source.r), cell(source.g), cell(source.b), cell(source.a) ); 24 | outputColor = finalColor; 25 | 26 | } 27 | -------------------------------------------------------------------------------- /shaders/build/postEffects/cell.vert: -------------------------------------------------------------------------------- 1 | #version 330 2 | 3 | 4 | in vec2 vert; 5 | in vec2 texCoord; 6 | out vec2 fragTexCoord; 7 | 8 | void main() { 9 | 10 | gl_Position = vec4(vert, 0.0, 1.0); 11 | fragTexCoord = texCoord; 12 | 13 | } 14 | -------------------------------------------------------------------------------- /shaders/build/postEffects/glow.frag: -------------------------------------------------------------------------------- 1 | #version 330 2 | 3 | uniform sampler2D tex0; 4 | uniform sampler2D tex1; 5 | in vec2 fragTexCoord; 6 | out vec4 outputColor; 7 | 8 | uniform float sample = 2.0; 9 | uniform float iterations = 8; 10 | uniform float gaussian[100] = float[] (0.23696313590506837, 0.22576837913554718, 0.2149304965314546, 0.20444927242920974, 0.19432366696918377, 0.1845518595211794, 0.17513129301958733, 0.1660587189558863, 0.15733024278516244, 0.14894136951338266, 0.14088704924316767, 0.13316172246762595, 0.12575936491431866, 0.1186735317544968, 0.11189740100626834, 0.10542381597419066, 0.09924532658183352, 0.09335422946800571, 0.08774260673148353, 0.08240236322311853, 0.0773252622980482, 0.07250295995429872, 0.06792703729727498, 0.06358903128241358, 0.0594804637005589, 0.05559286838236543, 0.05191781660917574, 0.04844694072833519, 0.045171955980752694, 0.042084680557670666, 0.03917705391205346, 0.0364411533577275, 0.033869208996403666, 0.031453617018985, 0.029186951433115504, 0.027061974273770033, 0.025071644357838672, 0.023209124647140075, 0.0214677882871318, 0.01984122339079921, 0.018323236638827463, 0.016907855768226104, 0.015589331022117408, 0.014362135633453003, 0.013220965415025607, 0.012160737527331282, 0.011176588494649939, 0.010263871538186153, 0.009418153293284751, 0.008635209975644624, 0.007911023059133089, 0.007241774525289354, 0.006623841741928975, 0.006053792025457812, 0.005528376938598487, 0.005044526372259342, 0.004599342457255244, 0.004190093348552506, 0.003814206921672727, 0.00346926441787937, 0.0031529940717994523, 0.002863264752221866, 0.0025980796439755197, 0.0023555699960388587, 0.0021339889583780157, 0.0019317055274632945, 0.0017471986179802869, 0.0015790512759391155, 0.0014259450461971356, 0.0012866545053506402, 0.0011600419690211364, 0.0010450523807626958, 0.0009407083881481699, 0.0008461056100525355, 0.0007604080977391869, 0.0006828439910664701, 0.0006127013699641679, 0.0005493243002780956, 0.0004921090721417389, 0.0004405006282012008, 0.00039398917828905374, 0.00035210699650789463, 0.00031442539614041443, 0.0002805518773432896, 0.0002501274422019469, 0.00022282407141596062, 0.0001983423566452461, 0.00017640928236931793, 0.00015677615099052658, 0.00013921664484182239, 0.00012352501873527194, 0.00010951441670426968, 0.00009701530664553113, 0.000085874026652078, 0.00007595143694136696, 0.00006712167141956476, 0.00005927098308008699, 0.00005229667760850829, 0.00004610612975370932, 0.00004061587722377405); 11 | 12 | void main() { 13 | 14 | vec2 tex_offset = sample / textureSize(tex1, 0); 15 | vec3 result = texture(tex0, fragTexCoord).rgb; 16 | result += texture(tex1, fragTexCoord).rgb * gaussian[0]; 17 | for (int i = 1; i < iterations; ++i) { 18 | result += texture(tex1, fragTexCoord + vec2(tex_offset.x * i, 0.0)).rgb * gaussian[int((100*i)/iterations)]*sample; 19 | result += texture(tex1, fragTexCoord - vec2(tex_offset.x * i, 0.0)).rgb * gaussian[int((100*i)/iterations)]*sample; 20 | } 21 | for (int i = 1; i < iterations; ++i) { 22 | result += texture(tex1, fragTexCoord + vec2(0.0, tex_offset.y * i)).rgb * gaussian[int((100*i)/iterations)]*sample; 23 | result += texture(tex1, fragTexCoord - vec2(0.0, tex_offset.y * i)).rgb * gaussian[int((100*i)/iterations)]*sample; 24 | } 25 | outputColor = vec4(result, 1.0); 26 | 27 | } 28 | -------------------------------------------------------------------------------- /shaders/build/postEffects/glow.vert: -------------------------------------------------------------------------------- 1 | #version 330 2 | 3 | in vec2 vert; 4 | in vec2 texCoord; 5 | out vec2 fragTexCoord; 6 | 7 | void main() { 8 | 9 | gl_Position = vec4(vert, 0.0, 1.0); 10 | fragTexCoord = texCoord; 11 | 12 | } 13 | -------------------------------------------------------------------------------- /shaders/diffuseSpecular.glsl: -------------------------------------------------------------------------------- 1 | #version 400 2 | 3 | #include "./lib/base.glsl" 4 | #include "./lib/worldTransform.glsl" 5 | #include "./lib/textures.glsl" 6 | #include "./lib/fresnelEffect.glsl" 7 | #include "./lib/roughnessTexture.glsl" 8 | #include "./lib/ambientLight.glsl" 9 | #include "./lib/pointLights.glsl" 10 | #include "./lib/directionalLights.glsl" 11 | #include "./lib/indirectLight.glsl" 12 | #include "./lib/glowOutput.glsl" 13 | 14 | void main() { 15 | textures(); 16 | roughnessTexture(); 17 | glowOutput(); 18 | 19 | #vert 20 | worldTransform(); 21 | gl_Position = projection * camera * model * vec4(vert, 1); 22 | #endvert 23 | 24 | #frag 25 | if (unlit) { 26 | outputColor = diffuse; 27 | } else { 28 | vec4 aoDiffuse = ao * metalDiffuse; 29 | vec4 feSpecular = fresnelEffect(metalSpecular, normalValue); 30 | vec3 dLight = ambientLight(aoDiffuse) + pointLights(aoDiffuse, feSpecular, normalValue) + directionalLights(aoDiffuse, feSpecular, normalValue); 31 | vec3 iLight = indirectLight(aoDiffuse, feSpecular, normalValue); 32 | outputColor = vec4(dLight + iLight, diffuse.a); 33 | } 34 | #endfrag 35 | } -------------------------------------------------------------------------------- /shaders/lib/ambientLight.glsl: -------------------------------------------------------------------------------- 1 | #frag 2 | #include "./base.glsl" 3 | 4 | uniform vec3 ambientLightValue; 5 | 6 | vec3 ambientLight(vec4 diffuse) { 7 | return ambientLightValue * diffuse.rgb; 8 | } 9 | #endfrag -------------------------------------------------------------------------------- /shaders/lib/base.glsl: -------------------------------------------------------------------------------- 1 | uniform mat4 projection; 2 | uniform mat4 camera; 3 | uniform mat4 model; 4 | uniform mat4 modelNormal; 5 | uniform vec3 cameraTranslation; 6 | 7 | uniform bool unlit; 8 | uniform bool useTextures; 9 | 10 | #vert 11 | in vec3 vert; 12 | in vec3 normal; 13 | in vec2 texCoord; 14 | in vec4 color; 15 | #endvert 16 | 17 | #frag 18 | layout(location = 0) out vec4 outputColor; 19 | #endfrag 20 | 21 | -------------------------------------------------------------------------------- /shaders/lib/common.glsl: -------------------------------------------------------------------------------- 1 | 2 | float pow2(float x) { 3 | return x*x; 4 | } 5 | 6 | float pow3(float x) { 7 | return x*x*x; 8 | } -------------------------------------------------------------------------------- /shaders/lib/directLight.glsl: -------------------------------------------------------------------------------- 1 | #frag 2 | #include "./common.glsl" 3 | #include "./worldTransform.glsl" 4 | 5 | vec3 directLight( vec3 light, vec3 direction, vec4 diffuse, vec4 specular, vec4 normalValue ) { 6 | vec3 normal_tangentSpace = (normalValue.xyz*2) - 1; 7 | vec3 direction_tangentSpace = direction * TBNMatrix; 8 | vec3 eyeDirection_tangentSpace = eyeDirection * TBNMatrix; 9 | vec3 reflectedEye_tangentSpace = reflect( eyeDirection_tangentSpace, normal_tangentSpace ); 10 | 11 | float diffuseMultiplier = max(0.0, dot(normal_tangentSpace, -direction_tangentSpace)); 12 | 13 | float specularMultiplier = pow2(max(0.0, dot(reflectedEye_tangentSpace, -direction_tangentSpace))); 14 | 15 | vec3 color = (diffuseMultiplier * diffuse.rgb) + (specularMultiplier * specular.rgb); 16 | 17 | return color * light; 18 | } 19 | #endfrag 20 | -------------------------------------------------------------------------------- /shaders/lib/directionalLights.glsl: -------------------------------------------------------------------------------- 1 | #frag 2 | #include "./base.glsl" 3 | #include "./worldTransform.glsl" 4 | #include "./directLight.glsl" 5 | 6 | #define MAX_DIRECTIONAL_LIGHTS 4 7 | 8 | uniform int nbDirectionalLights; 9 | uniform vec4 directionalLightVectors[ MAX_DIRECTIONAL_LIGHTS ]; 10 | uniform vec4 directionalLightValues[ MAX_DIRECTIONAL_LIGHTS ]; 11 | 12 | vec3 directionalLights(vec4 diffuse, vec4 specular, vec4 normalValue) { 13 | vec3 totalLight = vec3(0.0, 0.0, 0.0); 14 | for (int i=0; i < nbDirectionalLights; i++) { 15 | vec3 LightDirection = directionalLightVectors[i].rgb; 16 | vec3 LightValue = directionalLightValues[i].rgb; 17 | 18 | totalLight += directLight(LightValue, LightDirection, diffuse, specular, normalValue); 19 | } 20 | return totalLight; 21 | } 22 | #endfrag -------------------------------------------------------------------------------- /shaders/lib/fresnelEffect.glsl: -------------------------------------------------------------------------------- 1 | #frag 2 | #include "./common.glsl" 3 | #include "./worldTransform.glsl" 4 | 5 | vec4 fresnelEffect(vec4 baseSpecular, vec4 normalValue) { 6 | vec3 normal_tangentSpace = (normalValue.xyz*2) - 1; 7 | vec3 normal_worldSpace = normal_tangentSpace * inverseTBNMatrix; 8 | float NdV = abs(dot(normal_worldSpace, eyeDirection)); 9 | 10 | return mix(baseSpecular, vec4(1.0), pow(1.0 - NdV, 5.0)); 11 | } 12 | #endfrag -------------------------------------------------------------------------------- /shaders/lib/glowOutput.glsl: -------------------------------------------------------------------------------- 1 | #include "./base.glsl" 2 | #include "./textures.glsl" 3 | 4 | #frag 5 | uniform sampler2D glowMap; 6 | 7 | layout(location = 1) out vec4 brightColor; 8 | #endfrag 9 | 10 | #vert 11 | void glowOutput() {} 12 | #endvert 13 | 14 | #frag 15 | void glowOutput() { 16 | vec2 overflowTextCoord = repeatTextCoord(); 17 | 18 | brightColor = fragColor * texture(glowMap, overflowTextCoord); 19 | } 20 | #endfrag -------------------------------------------------------------------------------- /shaders/lib/indirectLight.glsl: -------------------------------------------------------------------------------- 1 | #frag 2 | #include "./worldTransform.glsl" 3 | 4 | uniform samplerCube environmentMap; 5 | 6 | vec3 indirectLight(vec4 diffuse, vec4 specular, vec4 normalValue) { 7 | vec3 normal_tangentSpace = (normalValue.xyz*2) - 1; 8 | vec3 normal_worldSpace = normal_tangentSpace * inverseTBNMatrix; 9 | vec3 reflectedEye_worldSpace = reflect( eyeDirection, normal_worldSpace ); 10 | 11 | vec3 diffuseValue = textureLod(environmentMap, normal_worldSpace, 10).rgb; 12 | vec3 specularValue = textureLod(environmentMap, reflectedEye_worldSpace, roughness.r * 10).rgb; 13 | 14 | return (diffuse.rgb * diffuseValue) + (specular.rgb * specularValue); 15 | } 16 | #endfrag -------------------------------------------------------------------------------- /shaders/lib/metalnessTexture.glsl: -------------------------------------------------------------------------------- 1 | #include "./textures.glsl" 2 | 3 | #vert 4 | void metalnessTexture() {} 5 | #endvert 6 | 7 | #frag 8 | uniform sampler2D metalnessMap; 9 | 10 | vec4 metalness; 11 | vec4 metalSpecular; 12 | vec4 metalDiffuse; 13 | 14 | void metalnessTexture() { 15 | vec2 overflowTextCoord = repeatTextCoord(); 16 | 17 | metalness = texture(metalnessMap, overflowTextCoord); 18 | metalSpecular = mix(vec4(0.04), diffuse, metalness.r); 19 | metalDiffuse = mix(diffuse, vec4(0), metalness.r); 20 | } 21 | #endfrag -------------------------------------------------------------------------------- /shaders/lib/pbrCompositeTextures.glsl: -------------------------------------------------------------------------------- 1 | #include "./textures.glsl" 2 | 3 | #vert 4 | void pbrCompositeTextures() {} 5 | #endvert 6 | 7 | #frag 8 | uniform sampler2D compositeMap; 9 | 10 | vec4 roughness; 11 | vec4 metalness; 12 | vec4 metalSpecular; 13 | vec4 metalDiffuse; 14 | 15 | void pbrCompositeTextures() { 16 | vec2 overflowTextCoord = repeatTextCoord(); 17 | 18 | vec4 composite = texture(compositeMap, overflowTextCoord); 19 | 20 | ao = vec4(composite.r); 21 | roughness = vec4(composite.g); 22 | metalness = vec4(composite.b); 23 | 24 | metalSpecular = mix(vec4(0.04), diffuse, metalness.r); 25 | metalDiffuse = mix(diffuse, vec4(0), metalness.r); 26 | } 27 | #endfrag -------------------------------------------------------------------------------- /shaders/lib/pointLights.glsl: -------------------------------------------------------------------------------- 1 | #frag 2 | #include "./base.glsl" 3 | #include "./worldTransform.glsl" 4 | #include "./directLight.glsl" 5 | 6 | #define MAX_POINT_LIGHTS 4 7 | 8 | uniform int nbPointLights; 9 | uniform vec4 pointLightPositions[ MAX_POINT_LIGHTS ]; 10 | uniform vec4 pointLightValues[ MAX_POINT_LIGHTS ]; 11 | 12 | vec3 pointLights(vec4 diffuse, vec4 specular, vec4 normalValue) { 13 | vec3 totalLight = vec3(0.0, 0.0, 0.0); 14 | for (int i=0; i < nbPointLights; i++) { 15 | vec3 LightPos = pointLightPositions[i].rgb; 16 | vec3 LightValue = pointLightValues[i].rgb; 17 | 18 | vec3 v = worldVertex - LightPos; 19 | float lightDistance = dot(v, v); 20 | float brightness = 1.0 / lightDistance; 21 | 22 | vec3 worldLightDir = normalize(v); 23 | vec3 light = brightness*LightValue; 24 | 25 | totalLight += directLight(light, worldLightDir, diffuse, specular, normalValue); 26 | } 27 | return totalLight; 28 | } 29 | #endfrag -------------------------------------------------------------------------------- /shaders/lib/roughnessTexture.glsl: -------------------------------------------------------------------------------- 1 | #include "./textures.glsl" 2 | 3 | #vert 4 | void roughnessTexture() {} 5 | #endvert 6 | 7 | #frag 8 | uniform sampler2D roughnessMap; 9 | 10 | vec4 roughness; 11 | 12 | void roughnessTexture() { 13 | vec2 overflowTextCoord = repeatTextCoord(); 14 | 15 | roughness = texture(roughnessMap, overflowTextCoord); 16 | } 17 | #endfrag -------------------------------------------------------------------------------- /shaders/lib/textures.glsl: -------------------------------------------------------------------------------- 1 | #include "./base.glsl" 2 | 3 | #vert 4 | out vec2 fragTexCoord; 5 | out vec4 fragColor; 6 | 7 | void textures() { 8 | fragTexCoord = texCoord; 9 | fragColor = color; 10 | } 11 | #endvert 12 | 13 | #frag 14 | uniform sampler2D normalMap; 15 | uniform sampler2D diffuseMap; 16 | uniform sampler2D specularMap; 17 | uniform sampler2D aoMap; 18 | 19 | in vec2 fragTexCoord; 20 | in vec4 fragColor; 21 | 22 | vec4 normalValue; 23 | vec4 diffuse; 24 | vec4 specular; 25 | vec4 ao; 26 | 27 | vec2 repeatTextCoord() { 28 | float textureX = fragTexCoord.x - int(fragTexCoord.x); 29 | float textureY = fragTexCoord.y - int(fragTexCoord.y); 30 | if (fragTexCoord.x < 0) {textureX = textureX + 1.0;} 31 | if (fragTexCoord.y < 0) {textureY = textureY + 1.0;} 32 | return vec2(textureX, textureY); 33 | } 34 | 35 | void textures() { 36 | vec2 overflowTextCoord = repeatTextCoord(); 37 | 38 | // multiply color by diffuse map. use only color if no map is provided 39 | if (useTextures) { 40 | diffuse = fragColor * texture(diffuseMap, overflowTextCoord); 41 | specular = texture(specularMap, overflowTextCoord); 42 | normalValue = texture(normalMap, overflowTextCoord); 43 | ao = texture(aoMap, overflowTextCoord); 44 | } else { 45 | diffuse = fragColor; 46 | specular = vec4(0); 47 | normalValue = vec4(0); 48 | ao = vec4(1); 49 | } 50 | } 51 | #endfrag -------------------------------------------------------------------------------- /shaders/lib/worldTransform.glsl: -------------------------------------------------------------------------------- 1 | #include "./base.glsl" 2 | 3 | #frag 4 | in vec3 worldVertex; 5 | in vec3 worldNormal; 6 | in vec3 eyeDirection; 7 | in mat3 TBNMatrix; 8 | in mat3 inverseTBNMatrix; 9 | #endfrag 10 | 11 | #vert 12 | out vec3 worldVertex; 13 | out vec3 worldNormal; 14 | out vec3 eyeDirection; 15 | out mat3 TBNMatrix; 16 | out mat3 inverseTBNMatrix; 17 | 18 | void worldTransform() { 19 | worldVertex = (model * vec4(vert,1)).xyz; 20 | worldNormal = (modelNormal * vec4(normal,1)).xyz; 21 | worldNormal = normalize(worldNormal); 22 | eyeDirection = normalize(worldVertex - cameraTranslation); 23 | 24 | // generate arbitrary tangent and bitangent to the normal 25 | vec3 tangent = cross(normal, normal + vec3(-1)); 26 | vec3 bitangent = cross(normal, tangent); 27 | vec3 worldTangent = normalize((modelNormal * vec4(tangent,1)).xyz); 28 | vec3 worldBitangent = normalize((modelNormal * vec4(bitangent,1)).xyz); 29 | 30 | //tangent space conversion - worldToTangent 31 | TBNMatrix = mat3(worldTangent, worldBitangent, worldNormal); 32 | inverseTBNMatrix = inverse(TBNMatrix); 33 | } 34 | #endvert -------------------------------------------------------------------------------- /shaders/pbr.glsl: -------------------------------------------------------------------------------- 1 | #version 400 2 | 3 | #include "./lib/base.glsl" 4 | #include "./lib/worldTransform.glsl" 5 | #include "./lib/textures.glsl" 6 | #include "./lib/metalnessTexture.glsl" 7 | #include "./lib/roughnessTexture.glsl" 8 | #include "./lib/fresnelEffect.glsl" 9 | #include "./lib/ambientLight.glsl" 10 | #include "./lib/pointLights.glsl" 11 | #include "./lib/directionalLights.glsl" 12 | #include "./lib/indirectLight.glsl" 13 | #include "./lib/glowOutput.glsl" 14 | 15 | void main() { 16 | textures(); 17 | metalnessTexture(); 18 | roughnessTexture(); 19 | glowOutput(); 20 | 21 | #vert 22 | worldTransform(); 23 | gl_Position = projection * camera * model * vec4(vert, 1); 24 | #endvert 25 | 26 | #frag 27 | if (unlit) { 28 | outputColor = diffuse; 29 | } else { 30 | vec4 aoDiffuse = ao * metalDiffuse; 31 | vec4 feSpecular = fresnelEffect(metalSpecular, normalValue); 32 | vec3 dLight = ambientLight(aoDiffuse) + pointLights(aoDiffuse, feSpecular, normalValue) + directionalLights(aoDiffuse, feSpecular, normalValue); 33 | vec3 iLight = indirectLight(aoDiffuse, feSpecular, normalValue); 34 | outputColor = vec4(dLight + iLight, diffuse.a); 35 | } 36 | #endfrag 37 | } -------------------------------------------------------------------------------- /shaders/pbrComposite.glsl: -------------------------------------------------------------------------------- 1 | #version 400 2 | 3 | #include "./lib/base.glsl" 4 | #include "./lib/worldTransform.glsl" 5 | #include "./lib/textures.glsl" 6 | #include "./lib/pbrCompositeTextures.glsl" 7 | #include "./lib/fresnelEffect.glsl" 8 | #include "./lib/ambientLight.glsl" 9 | #include "./lib/pointLights.glsl" 10 | #include "./lib/directionalLights.glsl" 11 | #include "./lib/indirectLight.glsl" 12 | #include "./lib/glowOutput.glsl" 13 | 14 | void main() { 15 | textures(); 16 | pbrCompositeTextures(); 17 | glowOutput(); 18 | 19 | #vert 20 | worldTransform(); 21 | gl_Position = projection * camera * model * vec4(vert, 1); 22 | #endvert 23 | 24 | #frag 25 | if (unlit) { 26 | outputColor = diffuse; 27 | } else { 28 | vec4 aoDiffuse = ao * metalDiffuse; 29 | vec4 feSpecular = fresnelEffect(metalSpecular, normalValue); 30 | vec3 dLight = ambientLight(aoDiffuse) + pointLights(aoDiffuse, feSpecular, normalValue) + directionalLights(aoDiffuse, feSpecular, normalValue); 31 | vec3 iLight = indirectLight(aoDiffuse, feSpecular, normalValue); 32 | outputColor = vec4(dLight + iLight, diffuse.a); 33 | } 34 | #endfrag 35 | } -------------------------------------------------------------------------------- /shaders/postEffects/cell.glsl: -------------------------------------------------------------------------------- 1 | #version 330 2 | 3 | #vert 4 | in vec2 vert; 5 | in vec2 texCoord; 6 | out vec2 fragTexCoord; 7 | #endvert 8 | 9 | #frag 10 | uniform sampler2D tex0; 11 | in vec2 fragTexCoord; 12 | out vec4 outputColor; 13 | 14 | float cell( float source ) { 15 | if (source < 0.3) { 16 | return 0; 17 | } else if (source < 0.6) { 18 | return 0.3; 19 | } else if (source < 0.9) { 20 | return 0.6; 21 | } 22 | return 1; 23 | } 24 | #endfrag 25 | 26 | void main() { 27 | #vert 28 | gl_Position = vec4(vert, 0.0, 1.0); 29 | fragTexCoord = texCoord; 30 | #endvert 31 | 32 | #frag 33 | vec4 finalColor = vec4(0,0,0,1); 34 | vec4 source = texture(tex0, fragTexCoord); 35 | finalColor = vec4( cell(source.r), cell(source.g), cell(source.b), cell(source.a) ); 36 | outputColor = finalColor; 37 | #endfrag 38 | } -------------------------------------------------------------------------------- /shaders/postEffects/glow.glsl: -------------------------------------------------------------------------------- 1 | #version 330 2 | 3 | #vert 4 | in vec2 vert; 5 | in vec2 texCoord; 6 | out vec2 fragTexCoord; 7 | #endvert 8 | 9 | #frag 10 | uniform sampler2D tex0; 11 | uniform sampler2D tex1; 12 | in vec2 fragTexCoord; 13 | out vec4 outputColor; 14 | 15 | uniform float sample = 2.0; 16 | uniform float iterations = 8; 17 | uniform #lookup 100 gaussian (2.718 ^ ( -(i*0.02 + 1.2) ^ 2.0 )); 18 | #endfrag 19 | 20 | void main() { 21 | #vert 22 | gl_Position = vec4(vert, 0.0, 1.0); 23 | fragTexCoord = texCoord; 24 | #endvert 25 | 26 | #frag 27 | vec2 tex_offset = sample / textureSize(tex1, 0); 28 | vec3 result = texture(tex0, fragTexCoord).rgb; 29 | result += texture(tex1, fragTexCoord).rgb * gaussian[0]; 30 | for (int i = 1; i < iterations; ++i) { 31 | result += texture(tex1, fragTexCoord + vec2(tex_offset.x * i, 0.0)).rgb * gaussian[int((100*i)/iterations)]*sample; 32 | result += texture(tex1, fragTexCoord - vec2(tex_offset.x * i, 0.0)).rgb * gaussian[int((100*i)/iterations)]*sample; 33 | } 34 | for (int i = 1; i < iterations; ++i) { 35 | result += texture(tex1, fragTexCoord + vec2(0.0, tex_offset.y * i)).rgb * gaussian[int((100*i)/iterations)]*sample; 36 | result += texture(tex1, fragTexCoord - vec2(0.0, tex_offset.y * i)).rgb * gaussian[int((100*i)/iterations)]*sample; 37 | } 38 | outputColor = vec4(result, 1.0); 39 | #endfrag 40 | } -------------------------------------------------------------------------------- /ui/children.go: -------------------------------------------------------------------------------- 1 | package ui 2 | 3 | type Children []Element 4 | 5 | func (c Children) GetChild(index int) Element { 6 | if index >= len(c) { 7 | return nil 8 | } 9 | return c[index] 10 | } 11 | 12 | func (c Children) GetChildById(id string) Element { 13 | for _, child := range c { 14 | if child.GetId() == id { 15 | return child 16 | } 17 | if elem := child.GetChildren().GetChildById(id); elem != nil { 18 | return elem 19 | } 20 | } 21 | return nil 22 | } 23 | 24 | func (c Children) TextElementById(id string) *TextElement { 25 | elem := c.GetChildById(id) 26 | if elem != nil && len(elem.GetChildren()) > 0 { 27 | if textElement, ok := elem.GetChildren().GetChild(0).(*TextElement); ok { 28 | return textElement 29 | } 30 | if textField, ok := elem.GetChildren().GetChild(0).(*TextField); ok { 31 | return textField.text 32 | } 33 | } 34 | return nil 35 | } 36 | -------------------------------------------------------------------------------- /ui/element.go: -------------------------------------------------------------------------------- 1 | package ui 2 | 3 | import ( 4 | "github.com/go-gl/mathgl/mgl32" 5 | "github.com/walesey/go-engine/renderer" 6 | ) 7 | 8 | type Element interface { 9 | Render(size, offset mgl32.Vec2) mgl32.Vec2 10 | ReRender() 11 | Spatial() (spatial renderer.Spatial) 12 | GlobalOrthoOrder() int 13 | GetId() string 14 | SetId(id string) 15 | GetChildren() Children 16 | mouseMove(position mgl32.Vec2) bool 17 | mouseClick(button int, release bool, position mgl32.Vec2) bool 18 | keyClick(key string, release bool) 19 | } 20 | 21 | // Sort elements 22 | type byGlobalOrthoOrder []Element 23 | 24 | func (slice byGlobalOrthoOrder) Len() int { 25 | return len(slice) 26 | } 27 | 28 | func (slice byGlobalOrthoOrder) Less(i, j int) bool { 29 | return slice[i].GlobalOrthoOrder() > slice[j].GlobalOrthoOrder() 30 | } 31 | 32 | func (slice byGlobalOrthoOrder) Swap(i, j int) { 33 | slice[i], slice[j] = slice[j], slice[i] 34 | } 35 | -------------------------------------------------------------------------------- /ui/hitbox.go: -------------------------------------------------------------------------------- 1 | package ui 2 | 3 | import ( 4 | "github.com/go-gl/mathgl/mgl32" 5 | "github.com/walesey/go-engine/emitter" 6 | "github.com/walesey/go-engine/util" 7 | ) 8 | 9 | type Hitbox interface { 10 | AddOnClick(handler func(button int, release bool, position mgl32.Vec2)) 11 | AddOnHover(handler func()) 12 | AddOnUnHover(handler func()) 13 | AddOnMouseMove(handler func(position mgl32.Vec2)) 14 | MouseMove(position mgl32.Vec2) bool 15 | MouseClick(button int, release bool, position mgl32.Vec2) bool 16 | SetSize(size mgl32.Vec2) 17 | } 18 | 19 | type HitboxImpl struct { 20 | size mgl32.Vec2 21 | events emitter.EventEmitter 22 | hoverState bool 23 | } 24 | 25 | type clickEvent struct { 26 | button int 27 | release bool 28 | position mgl32.Vec2 29 | } 30 | 31 | func (hb *HitboxImpl) AddOnClick(handler func(button int, release bool, position mgl32.Vec2)) { 32 | hb.events.On("click", func(e emitter.Event) { 33 | if ce, ok := e.(clickEvent); ok { 34 | handler(ce.button, ce.release, ce.position) 35 | } 36 | }) 37 | } 38 | 39 | func (hb *HitboxImpl) AddOnHover(handler func()) { 40 | hb.events.On("hover", func(e emitter.Event) { 41 | handler() 42 | }) 43 | } 44 | 45 | func (hb *HitboxImpl) AddOnUnHover(handler func()) { 46 | hb.events.On("unHover", func(e emitter.Event) { 47 | handler() 48 | }) 49 | } 50 | 51 | func (hb *HitboxImpl) AddOnMouseMove(handler func(position mgl32.Vec2)) { 52 | hb.events.On("mouseMove", func(e emitter.Event) { 53 | if pos, ok := e.(mgl32.Vec2); ok { 54 | handler(pos) 55 | } 56 | }) 57 | } 58 | 59 | func (hb *HitboxImpl) MouseMove(position mgl32.Vec2) bool { 60 | if util.PointLiesInsideAABB(mgl32.Vec2{}, hb.size, position) { 61 | if !hb.hoverState { 62 | hb.hoverState = true 63 | hb.events.Do("hover", position) 64 | } 65 | hb.events.Do("mouseMove", position) 66 | return true 67 | } else if hb.hoverState { 68 | hb.hoverState = false 69 | hb.events.Do("unHover", position) 70 | } 71 | return false 72 | } 73 | 74 | func (hb *HitboxImpl) MouseClick(button int, release bool, position mgl32.Vec2) bool { 75 | if util.PointLiesInsideAABB(mgl32.Vec2{}, hb.size, position) { 76 | hb.events.Do("click", clickEvent{button, release, position}) 77 | return true 78 | } 79 | return false 80 | } 81 | 82 | func (hb *HitboxImpl) SetSize(size mgl32.Vec2) { 83 | hb.size = size 84 | } 85 | 86 | func NewHitbox() Hitbox { 87 | return &HitboxImpl{ 88 | events: emitter.New(1), 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /ui/htmlAssets.go: -------------------------------------------------------------------------------- 1 | package ui 2 | 3 | import ( 4 | "image" 5 | 6 | "github.com/walesey/go-engine/libs/freetype/truetype" 7 | ) 8 | 9 | type HtmlAssets struct { 10 | fontMap map[string]*truetype.Font 11 | callbackMap map[string]func(element Element, args ...interface{}) 12 | imageMap map[string]image.Image 13 | } 14 | 15 | func (assets HtmlAssets) AddFont(key string, font *truetype.Font) { 16 | assets.fontMap[key] = font 17 | } 18 | 19 | func (assets HtmlAssets) AddCallback(key string, callback func(element Element, args ...interface{})) { 20 | assets.callbackMap[key] = callback 21 | } 22 | 23 | func (assets HtmlAssets) AddImage(key string, img image.Image) { 24 | assets.imageMap[key] = img 25 | } 26 | 27 | func NewHtmlAssets() HtmlAssets { 28 | assets := HtmlAssets{ 29 | fontMap: make(map[string]*truetype.Font), 30 | callbackMap: make(map[string]func(element Element, args ...interface{})), 31 | imageMap: make(map[string]image.Image), 32 | } 33 | defaultFont, err := LoadFont(getDefaultFont()) 34 | if err == nil { 35 | assets.AddFont("default", defaultFont) 36 | } 37 | return assets 38 | } 39 | -------------------------------------------------------------------------------- /ui/image.go: -------------------------------------------------------------------------------- 1 | package ui 2 | 3 | import ( 4 | "image" 5 | 6 | "github.com/go-gl/mathgl/mgl32" 7 | "github.com/walesey/go-engine/renderer" 8 | ) 9 | 10 | type ImageElement struct { 11 | id string 12 | Hitbox Hitbox 13 | percentWidth bool 14 | percentHeight bool 15 | width, height float32 16 | rotation float32 17 | size, offset mgl32.Vec2 18 | node *renderer.Node 19 | img image.Image 20 | } 21 | 22 | func (ie *ImageElement) Render(size, offset mgl32.Vec2) mgl32.Vec2 { 23 | ie.size, ie.offset = size, offset 24 | width, height := ie.getWidth(size.X()), ie.getHeight(size.X()) 25 | if ie.img != nil { 26 | if width <= 0 && height <= 0 { 27 | width = float32(ie.img.Bounds().Size().X) 28 | height = float32(ie.img.Bounds().Size().Y) 29 | } else if width <= 0 { 30 | width = height * float32(ie.img.Bounds().Size().X) / float32(ie.img.Bounds().Size().Y) 31 | } else if height <= 0 { 32 | height = width * float32(ie.img.Bounds().Size().Y) / float32(ie.img.Bounds().Size().X) 33 | } 34 | } 35 | imgSize := mgl32.Vec2{width, height} 36 | ie.node.SetScale(imgSize.Vec3(0)) 37 | ie.node.SetTranslation(offset.Vec3(0)) 38 | ie.offset = offset 39 | ie.Hitbox.SetSize(imgSize) 40 | return imgSize 41 | } 42 | 43 | func (ie *ImageElement) ReRender() { 44 | ie.Render(ie.size, ie.offset) 45 | } 46 | 47 | func (ie *ImageElement) Spatial() renderer.Spatial { 48 | return ie.node 49 | } 50 | 51 | func (ie *ImageElement) GlobalOrthoOrder() int { 52 | return 0 53 | } 54 | 55 | func (ie *ImageElement) GetId() string { 56 | return ie.id 57 | } 58 | 59 | func (ie *ImageElement) SetId(id string) { 60 | ie.id = id 61 | } 62 | 63 | func (ie *ImageElement) GetChildren() Children { 64 | return []Element{} 65 | } 66 | 67 | func (ie *ImageElement) SetWidth(width float32) { 68 | ie.width = width 69 | } 70 | 71 | func (ie *ImageElement) UsePercentWidth(usePercent bool) { 72 | ie.percentWidth = usePercent 73 | } 74 | 75 | func (ie *ImageElement) SetHeight(height float32) { 76 | ie.height = height 77 | } 78 | 79 | func (ie *ImageElement) UsePercentHeight(usePercent bool) { 80 | ie.percentHeight = usePercent 81 | } 82 | 83 | func (ie *ImageElement) getWidth(parentWidth float32) float32 { 84 | if ie.percentWidth { 85 | return parentWidth * ie.width / 100.0 86 | } 87 | return ie.width 88 | } 89 | 90 | func (ie *ImageElement) getHeight(parentWidth float32) float32 { 91 | if ie.percentHeight { 92 | return parentWidth * ie.height / 100.0 93 | } 94 | return ie.height 95 | } 96 | 97 | func (ie *ImageElement) SetRotation(rotation float32) { 98 | ie.rotation = rotation 99 | } 100 | 101 | func (ie *ImageElement) SetImage(img image.Image) { 102 | mat := renderer.NewMaterial(renderer.NewTexture("diffuseMap", img, false)) 103 | ie.node.Material = mat 104 | ie.img = img 105 | } 106 | 107 | func (ie *ImageElement) mouseMove(position mgl32.Vec2) bool { 108 | offsetPos := position.Sub(ie.offset) 109 | return ie.Hitbox.MouseMove(offsetPos) 110 | } 111 | 112 | func (ie *ImageElement) mouseClick(button int, release bool, position mgl32.Vec2) bool { 113 | offsetPos := position.Sub(ie.offset) 114 | return ie.Hitbox.MouseClick(button, release, offsetPos) 115 | } 116 | 117 | func (ie *ImageElement) keyClick(key string, release bool) {} 118 | 119 | func NewImageElement(img image.Image) *ImageElement { 120 | imageElement := &ImageElement{ 121 | rotation: 0, 122 | Hitbox: NewHitbox(), 123 | node: renderer.NewNode(), 124 | } 125 | box := renderer.CreateBoxWithOffset(1, 1, 0, 0) 126 | imageElement.node.Add(box) 127 | imageElement.SetImage(img) 128 | return imageElement 129 | } 130 | -------------------------------------------------------------------------------- /ui/progressBar.go: -------------------------------------------------------------------------------- 1 | package ui 2 | 3 | import ( 4 | "fmt" 5 | 6 | "image/color" 7 | 8 | "github.com/go-gl/mathgl/mgl32" 9 | ) 10 | 11 | const nbBars = 20 12 | 13 | func NewProgressBar(label string) *Window { 14 | window := NewWindow() 15 | window.SetTranslation(mgl32.Vec3{500, 200, 0}) 16 | window.SetScale(mgl32.Vec3{325, 0, 1}) 17 | 18 | container := NewContainer() 19 | window.SetElement(container) 20 | container.SetBackgroundColor(200, 200, 200, 255) 21 | container.SetWidth(325) 22 | 23 | labelElem := NewTextElement(label, color.RGBA{10, 10, 20, 255}, 12, nil) 24 | labelContainer := NewContainer() 25 | labelContainer.SetWidth(100) 26 | labelContainer.UsePercentWidth(true) 27 | labelContainer.SetMargin(NewMargin(5)) 28 | labelContainer.AddChildren(labelElem) 29 | 30 | progressBar := NewContainer() 31 | progressBar.SetBackgroundColor(50, 50, 50, 255) 32 | progressBar.SetMargin(NewMargin(5)) 33 | progressBar.SetPadding(Margin{0, 0, 0, 5}) 34 | progressBar.SetHeight(40) 35 | progressBar.SetWidth(315) 36 | 37 | container.AddChildren(labelContainer, progressBar) 38 | 39 | for i := 1; i <= nbBars; i++ { 40 | box := NewContainer() 41 | progressBar.AddChildren(box) 42 | box.SetId(fmt.Sprintf("progress%v", i)) 43 | box.SetBackgroundColor(50, 220, 80, 255) 44 | box.SetMargin(Margin{10, 0, 10, 5}) 45 | box.SetHeight(20) 46 | box.SetWidth(10) 47 | } 48 | 49 | window.Render() 50 | 51 | return window 52 | } 53 | 54 | func SetProgressBar(pb *Window, progress int) { 55 | for i := 1; i <= nbBars; i++ { 56 | container, ok := pb.ElementById(fmt.Sprintf("progress%v", i)).(*Container) 57 | if ok { 58 | if i > progress { 59 | container.SetBackgroundColor(0, 0, 0, 0) 60 | } else { 61 | container.SetBackgroundColor(0, 255, 0, 255) 62 | } 63 | } 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /ui/window.go: -------------------------------------------------------------------------------- 1 | package ui 2 | 3 | import ( 4 | "image/color" 5 | 6 | "github.com/go-gl/mathgl/mgl32" 7 | "github.com/walesey/go-engine/renderer" 8 | ) 9 | 10 | var activeWindow *Window 11 | 12 | type Activatable interface { 13 | Active() bool 14 | Activate() 15 | Deactivate() 16 | } 17 | 18 | type Window struct { 19 | node, elementNode, background *renderer.Node 20 | backgroundBox *renderer.Geometry 21 | element Element 22 | size, position mgl32.Vec2 23 | mousePos mgl32.Vec2 24 | Tabs []Activatable 25 | } 26 | 27 | func (w *Window) Draw(renderer renderer.Renderer, transform mgl32.Mat4) { 28 | w.node.Draw(renderer, transform) 29 | } 30 | 31 | func (w *Window) Destroy(renderer renderer.Renderer) { 32 | w.node.Destroy(renderer) 33 | } 34 | 35 | func (w *Window) Center() mgl32.Vec3 { 36 | return w.node.Center() 37 | } 38 | 39 | func (w *Window) SetParent(parent *renderer.Node) { 40 | w.node.SetParent(parent) 41 | } 42 | 43 | func (w *Window) Optimize(geometry *renderer.Geometry, transform mgl32.Mat4) { 44 | w.node.Optimize(geometry, transform) 45 | } 46 | 47 | func (w *Window) BoundingRadius() float32 { 48 | return w.node.BoundingRadius() 49 | } 50 | 51 | func (w *Window) OrthoOrder() int { 52 | return w.node.OrthoOrder() 53 | } 54 | 55 | func (w *Window) SetScale(scale mgl32.Vec3) { 56 | w.background.SetScale(scale) 57 | w.size = scale.Vec2() 58 | w.Render() 59 | } 60 | 61 | func (w *Window) SetTranslation(translation mgl32.Vec3) { 62 | w.node.SetTranslation(translation) 63 | w.position = translation.Vec2() 64 | } 65 | 66 | func (w *Window) SetOrientation(orientation mgl32.Quat) { 67 | w.node.SetOrientation(orientation) 68 | } 69 | 70 | func (w *Window) SetBackgroundColor(r, g, b, a uint8) { 71 | w.backgroundBox.SetColor(color.NRGBA{r, g, b, a}) 72 | } 73 | 74 | func (w *Window) SetElement(element Element) { 75 | if w.element != nil { 76 | w.elementNode.Remove(w.element.Spatial(), true) 77 | } 78 | w.element = element 79 | w.elementNode.Add(element.Spatial()) 80 | w.Render() 81 | } 82 | 83 | func (w *Window) Render() { 84 | if w.element != nil { 85 | size := w.element.Render(w.size, mgl32.Vec2{0, 0}) 86 | width, height := w.size.X(), w.size.Y() 87 | if size.X() > width { 88 | width = size.X() 89 | } 90 | if size.Y() > height { 91 | height = size.Y() 92 | } 93 | w.background.SetScale(mgl32.Vec2{width, height}.Vec3(0)) 94 | } 95 | } 96 | 97 | func (w *Window) ElementById(id string) Element { 98 | if w.element != nil { 99 | if w.element.GetId() == id { 100 | return w.element 101 | } 102 | return w.element.GetChildren().GetChildById(id) 103 | } 104 | return nil 105 | } 106 | 107 | func (w *Window) TextElementById(id string) *TextElement { 108 | if w.element != nil { 109 | return w.element.GetChildren().TextElementById(id) 110 | } 111 | return nil 112 | } 113 | 114 | func (w *Window) mouseMove(position mgl32.Vec2) { 115 | w.mousePos = position.Sub(w.position) 116 | if w.element != nil { 117 | w.element.mouseMove(w.mousePos) 118 | } 119 | } 120 | 121 | func (w *Window) mouseClick(button int, release bool) { 122 | if w.element != nil { 123 | // Deselect all fields 124 | if !release { 125 | deactivateAllFields(w.element) 126 | } 127 | // Process the click 128 | if w.element.mouseClick(button, release, w.mousePos) { 129 | // set this to the active window 130 | if !release { 131 | if activeWindow != nil { 132 | activeWindow.node.OrthoOrderValue = 10 133 | } 134 | w.node.OrthoOrderValue = 100 135 | activeWindow = w 136 | } 137 | } 138 | } 139 | } 140 | 141 | func (w *Window) keyClick(key string, release bool) { 142 | if w.element != nil { 143 | w.element.keyClick(key, release) 144 | } 145 | } 146 | 147 | func deactivateAllFields(elem Element) { 148 | for _, child := range elem.GetChildren() { 149 | deactivateAllFields(child) 150 | switch t := child.(type) { 151 | case *TextField: 152 | t.Deactivate() 153 | case *Dropdown: 154 | t.deactivate_setFlag() 155 | } 156 | } 157 | } 158 | 159 | func NewWindow() *Window { 160 | node := renderer.NewNode() 161 | elementNode := renderer.NewNode() 162 | background := renderer.NewNode() 163 | background.Material = renderer.NewMaterial() 164 | box := renderer.CreateBoxWithOffset(1, 1, 0, 0) 165 | box.SetColor(color.NRGBA{255, 255, 255, 255}) 166 | background.Add(box) 167 | node.Add(background) 168 | node.Add(elementNode) 169 | node.OrthoOrderValue = 10 170 | return &Window{ 171 | node: node, 172 | backgroundBox: box, 173 | background: background, 174 | elementNode: elementNode, 175 | size: mgl32.Vec2{500, 1}, 176 | Tabs: []Activatable{}, 177 | } 178 | } 179 | -------------------------------------------------------------------------------- /ui/windowAddons.go: -------------------------------------------------------------------------------- 1 | package ui 2 | 3 | import ( 4 | "github.com/go-gl/mathgl/mgl32" 5 | "github.com/walesey/go-engine/controller" 6 | ) 7 | 8 | func ClickAndDragWindow(window *Window, hitbox Hitbox, c controller.Controller) { 9 | grabbed := false 10 | grabOffset := mgl32.Vec2{} 11 | hitbox.AddOnClick(func(button int, release bool, position mgl32.Vec2) { 12 | grabOffset = position 13 | grabbed = !release 14 | }) 15 | c.BindMouseAction(func() { 16 | grabbed = false 17 | }, controller.MouseButton1, controller.Release) 18 | c.BindAxisAction(func(xpos, ypos float32) { 19 | if grabbed { 20 | position := mgl32.Vec2{xpos, ypos} 21 | window.SetTranslation(position.Sub(grabOffset).Vec3(0)) 22 | } 23 | }) 24 | } 25 | -------------------------------------------------------------------------------- /util/image.go: -------------------------------------------------------------------------------- 1 | package util 2 | 3 | import "image" 4 | 5 | // ImageColor - returns an image with a single pixel 6 | func ImageColor(r, g, b, a uint8) image.Image { 7 | return &image.RGBA{ 8 | Pix: []uint8{r, g, b, a}, 9 | Stride: 4, 10 | Rect: image.Rectangle{Min: image.Point{0, 0}, Max: image.Point{1, 1}}, 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /util/serialize_test.go: -------------------------------------------------------------------------------- 1 | package util 2 | 3 | import ( 4 | "testing" 5 | 6 | "bytes" 7 | 8 | "github.com/stretchr/testify/assert" 9 | ) 10 | 11 | func TestUint64(t *testing.T) { 12 | var i uint64 = 50 13 | data, err := SerializeArgs(i) 14 | assert.NoError(t, err) 15 | result, err := UInt64frombytes(bytes.NewBuffer(data)) 16 | assert.NoError(t, err) 17 | assert.EqualValues(t, i, result) 18 | } 19 | 20 | func TestAbc(t *testing.T) { 21 | a := []string{"a", "b"} 22 | data, err := SerializeArgs(len(a)) 23 | assert.NoError(t, err) 24 | result, err := UInt32frombytes(bytes.NewBuffer(data)) 25 | assert.NoError(t, err) 26 | assert.EqualValues(t, 2, result) 27 | } 28 | -------------------------------------------------------------------------------- /util/util.go: -------------------------------------------------------------------------------- 1 | package util 2 | 3 | import ( 4 | "encoding/base64" 5 | "fmt" 6 | "io/ioutil" 7 | "strings" 8 | "time" 9 | ) 10 | 11 | func Base64ToBytes(base64String string) []byte { 12 | decoder := base64.NewDecoder(base64.URLEncoding, strings.NewReader(base64String)) 13 | data, err := ioutil.ReadAll(decoder) 14 | if err != nil { 15 | fmt.Printf("Error converting base64 string to bytes: %v\n", err) 16 | return []byte{} 17 | } 18 | return data 19 | } 20 | 21 | func SetInterval(fn func(), interval time.Duration) func() { 22 | ticker := time.NewTicker(interval) 23 | quit := make(chan struct{}) 24 | go func() { 25 | for { 26 | select { 27 | case <-ticker.C: 28 | fn() 29 | case <-quit: 30 | ticker.Stop() 31 | return 32 | } 33 | } 34 | }() 35 | return func() { 36 | close(quit) 37 | } 38 | } 39 | --------------------------------------------------------------------------------