├── .circleci └── config.yml ├── .gitignore ├── LICENSE ├── README.md ├── entity ├── Id.go ├── base.go ├── base_test.go ├── camera.go ├── camera_test.go ├── generic.go ├── generic_test.go ├── ientity.go ├── transform.go └── transform_test.go ├── event ├── id.go ├── imessage.go ├── manager.go ├── manager_test.go ├── message.go └── message_test.go ├── filesystem └── constants.go ├── game ├── IGame.go ├── README.md ├── counterstrike-source.go └── entity │ ├── common │ ├── prop_door_rotating.go │ ├── prop_door_rotating_test.go │ ├── prop_dynamic.go │ ├── prop_dynamic_ornament.go │ ├── prop_dynamic_ornament_test.go │ ├── prop_dynamic_override.go │ ├── prop_dynamic_override_test.go │ ├── prop_dynamic_test.go │ ├── prop_physics.go │ ├── prop_physics_multiplayer.go │ ├── prop_physics_multiplayer_test.go │ ├── prop_physics_override.go │ ├── prop_physics_override_test.go │ ├── prop_physics_test.go │ ├── prop_ragdoll.go │ └── prop_ragdoll_test.go │ ├── iprop.go │ └── iprop_test.go ├── go.mod ├── lib ├── gameinfo │ ├── load.go │ └── reader.go ├── math │ └── shape │ │ └── rectangle.go ├── stringtable │ └── stringtable.go ├── studiomodel │ └── studiomodel.go ├── util │ ├── logger.go │ ├── strings.go │ └── strings_test.go └── vpk │ └── open.go ├── loader ├── bsp.go ├── entity │ ├── classmap │ │ └── classmap.go │ ├── create.go │ └── prop.go ├── keyvalues │ └── keyvalues.go ├── material │ ├── common.go │ ├── materials.go │ ├── vmt.go │ └── vtf.go ├── prop │ └── prop.go ├── props.go ├── sky.go └── vgui │ └── vgui.go ├── material ├── imaterial.go ├── material.go └── material_test.go ├── mesh ├── face.go ├── face_test.go ├── imesh.go ├── mesh.go ├── mesh_test.go ├── primitive │ ├── cube.go │ └── cube_test.go └── util │ └── tangents.go ├── model ├── bsp.go ├── imodel.go ├── leaf.go ├── model.go └── staticprop.go ├── renovate.json ├── resource ├── iresource.go ├── manager.go ├── manager_test.go └── message │ ├── map.go │ ├── material.go │ ├── model.go │ └── texture.go ├── scene ├── iscene.go ├── scene.go └── scene_test.go ├── texture ├── atlas.go ├── colour2d.go ├── colour2d_test.go ├── cubemap.go ├── cubemap_test.go ├── itexture.go ├── lightmap.go ├── texture2d.go └── texture2d_test.go └── vgui ├── Element.go ├── button.go ├── masterPanel.go └── panel.go /.circleci/config.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | jobs: 3 | build: 4 | docker: 5 | - image: circleci/golang:1.17 6 | steps: 7 | - checkout 8 | - run: 9 | name: Install Go Dependencies 10 | command: | 11 | go get -u 12 | go get -v github.com/golangci/golangci-lint/cmd/golangci-lint 13 | - run: 14 | name: Lint 15 | command: golangci-lint run --deadline=2m 16 | - run: 17 | name: Test 18 | command: go test -v -race -coverprofile=coverage.txt -covermode=atomic ./... 19 | - run: 20 | name: Upload Codecov Results 21 | command: bash <(curl -s https://codecov.io/bash) 22 | when: on_success -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | \.DS_Store 2 | vendor 3 | \.idea/ 4 | 5 | config\.json 6 | example_game/ 7 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | This is free and unencumbered software released into the public domain. 2 | 3 | Anyone is free to copy, modify, publish, use, compile, sell, or 4 | distribute this software, either in source code form or as a compiled 5 | binary, for any purpose, commercial or non-commercial, and by any 6 | means. 7 | 8 | In jurisdictions that recognize copyright laws, the author or authors 9 | of this software dedicate any and all copyright interest in the 10 | software to the public domain. We make this dedication for the benefit 11 | of the public at large and to the detriment of our heirs and 12 | successors. We intend this dedication to be an overt act of 13 | relinquishment in perpetuity of all present and future rights to this 14 | software under copyright law. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 17 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 18 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 19 | IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR 20 | OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, 21 | ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 22 | OTHER DEALINGS IN THE SOFTWARE. 23 | 24 | For more information, please refer to 25 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![GoDoc](https://godoc.org/github.com/galaco/lambda-core?status.svg)](https://godoc.org/github.com/galaco/lambda-core) 2 | [![Go report card](https://goreportcard.com/badge/github.com/galaco/lambda-core)](https://goreportcard.com/badge/github.com/galaco/lambda-core) 3 | [![GolangCI](https://golangci.com/badges/github.com/galaco/lambda-core.svg)](https://golangci.com) 4 | [![codecov](https://codecov.io/gh/Galaco/lambda-core/branch/master/graph/badge.svg)](https://codecov.io/gh/Galaco/lambda-core) 5 | [![CircleCI](https://circleci.com/gh/Galaco/lambda-core.svg?style=svg)](https://circleci.com/gh/Galaco/lambda-core) 6 | 7 | # Lambda Core 8 | Lambda Core provides a semi-comprehensive set of tools to build practically any Source Engine tool from. Any module can be used 9 | in isolation, but its recommended to utilise at least the FileSystem and ResourceManager modules if any loader is used. 10 | 11 | ##### See [https://github.com/galaco/Lambda-Client](https://github.com/galaco/Lambda-Client) for a working BSP renderer built on top of this library. 12 | 13 | ### Current features 14 | * GameInfo.txt parser for existing games 15 | * Full filesystem loader and searcher for all GameInfo defined paths, including pakfile and vpks 16 | * Vmt and Vtf parsing 17 | * Basic .mdl parsing (useable, incomplete) 18 | * Full bsp loading utilities 19 | 20 | ## Contributing 21 | There is loads to do! Right now there are a few core issues that need fixing, and loads of fundamental features to add. Here 22 | are just a few! 23 | * StudioModel library needs finishing before props can be properly added. There are some issues around multiple stripgroups per mesh, multiple 24 | materials per prop, mdl data not fully loaded, and likely more 25 | * Implement physics (probably bullet physics? Accurate VPhysics is probably not worthwhile, but needs investigation) 26 | * Displacement support incomplete - generation is buggy, and visibility checks cull displacements always (visible when outside of world only) 27 | * Additional game support/testing in BSP library 28 | 29 | 30 | 31 | #### Some documentation 32 | 33 | ##### Entity 34 | Provides an interface, and generic implementation of a game entity, as well as 35 | 3d transform struct and camera implementation. 36 | 37 | ##### Event 38 | Event provides a very simple emitter/subscriber manager to allow engine event 39 | processing. It can be used for handling internal engine events, or for game logic, 40 | although that isn't recommended. 41 | 42 | ##### Filesystem 43 | Source Engine is a little annoying in that there are potentially unlimited possible 44 | locations that engine resources can be located. Filesystem provides a way to register 45 | and organise any potential resource path or filesystem, while preserving filesystem type 46 | search priority. 47 | 48 | ##### Loader 49 | Loader generates data structures for Source formats from file streams. Materials, textures, 50 | meshes etc. 51 | 52 | ##### Logger 53 | Logger is a simple module to abstract out different print() priorities, and (eventually) locations 54 | than just stdout. 55 | 56 | ##### Material 57 | Material provides a more GPU friendly vmt material implementation 58 | 59 | ##### Mesh 60 | Provides a set of common data formats for vertex data in the bsp or compiled 61 | .mdl props. 62 | 63 | ##### Model 64 | Models are combinations of simple data structures that represent a single higher 65 | level visual object. e.g. []Mesh is a studio model, []Face is bsp data etc. 66 | 67 | ##### Resource 68 | Resource provides a management struct for tracking what game resources have been 69 | loaded from the filesystem. When a map is loaded, all found materials, textures, models 70 | should be added to the ResourceManager, so they can be loaded only once, and cleaned up 71 | correctly when no longer needed. 72 | 73 | ##### Scene 74 | Provides a simple scene struct that contains bsp face and staticprop information 75 | 76 | ##### Texture 77 | Provides a set of GPU friendly texture formats. For now OpenGL usage is enforced, but 78 | abstracting that out should be doable. -------------------------------------------------------------------------------- /entity/Id.go: -------------------------------------------------------------------------------- 1 | package entity 2 | 3 | // EntityId 4 | type EntityId uint 5 | 6 | var entityHandleCounter EntityId 7 | 8 | func newEntityHandle() EntityId { 9 | entityHandleCounter++ 10 | return entityHandleCounter 11 | } 12 | -------------------------------------------------------------------------------- /entity/base.go: -------------------------------------------------------------------------------- 1 | package entity 2 | 3 | import ( 4 | entity2 "github.com/galaco/source-tools-common/entity" 5 | ) 6 | 7 | // Base is an object in the game world. 8 | // By itself entity is nothing more than an identifiable object located at a point in space 9 | type Base struct { 10 | keyValues *entity2.Entity 11 | transform Transform 12 | 13 | handle EntityId 14 | } 15 | 16 | // SetKeyValues set this entity's keyvalues 17 | func (entity *Base) SetKeyValues(keyValues *entity2.Entity) { 18 | entity.keyValues = keyValues 19 | } 20 | 21 | // KeyValues returns this entitys keyvalues 22 | func (entity *Base) KeyValues() *entity2.Entity { 23 | return entity.keyValues 24 | } 25 | 26 | // Classname gets this entitiy's classname 27 | func (entity *Base) Classname() string { 28 | if entity.keyValues == nil { 29 | return "generic" 30 | } 31 | return entity.keyValues.ValueForKey("classname") 32 | } 33 | 34 | // Transform Returns this entity's transform component 35 | func (entity *Base) Transform() *Transform { 36 | return &entity.transform 37 | } 38 | 39 | // New entity 40 | func (entity *Base) New() IEntity { 41 | return &Base{} 42 | } 43 | -------------------------------------------------------------------------------- /entity/base_test.go: -------------------------------------------------------------------------------- 1 | package entity 2 | 3 | import ( 4 | "reflect" 5 | "testing" 6 | ) 7 | 8 | func TestBase_Classname(t *testing.T) { 9 | sut := &Base{} 10 | if sut.Classname() != "generic" { 11 | t.Errorf("unexpected classname. Received %s, but expected %s", sut.Classname(), "generic") 12 | } 13 | } 14 | 15 | func TestBase_New(t *testing.T) { 16 | sut := &Base{} 17 | 18 | actual := sut.New().(*Base) 19 | 20 | if reflect.TypeOf(actual) != reflect.TypeOf(sut) { 21 | t.Errorf("Expected: %d, but got: %s", reflect.TypeOf(sut), reflect.TypeOf(actual)) 22 | } 23 | } 24 | 25 | func TestBase_KeyValues(t *testing.T) { 26 | t.Skip() 27 | } 28 | 29 | func TestBase_SetKeyValues(t *testing.T) { 30 | t.Skip() 31 | } 32 | 33 | func TestBase_Transform(t *testing.T) { 34 | t.Skip() 35 | } 36 | -------------------------------------------------------------------------------- /entity/camera.go: -------------------------------------------------------------------------------- 1 | package entity 2 | 3 | import ( 4 | "github.com/go-gl/mathgl/mgl32" 5 | "math" 6 | ) 7 | 8 | const cameraSpeed = float64(320) 9 | const sensitivity = float32(0.03) 10 | 11 | var minVerticalRotation = mgl32.DegToRad(90) 12 | var maxVerticalRotation = mgl32.DegToRad(270) 13 | 14 | // Camera 15 | type Camera struct { 16 | *Base 17 | fov float32 18 | aspectRatio float32 19 | up mgl32.Vec3 20 | right mgl32.Vec3 21 | direction mgl32.Vec3 22 | worldUp mgl32.Vec3 23 | } 24 | 25 | // Forwards 26 | func (camera *Camera) Forwards(dt float64) { 27 | camera.Transform().Position = camera.Transform().Position.Add(camera.direction.Mul(float32(cameraSpeed * dt))) 28 | } 29 | 30 | // Backwards 31 | func (camera *Camera) Backwards(dt float64) { 32 | camera.Transform().Position = camera.Transform().Position.Sub(camera.direction.Mul(float32(cameraSpeed * dt))) 33 | } 34 | 35 | // Left 36 | func (camera *Camera) Left(dt float64) { 37 | camera.Transform().Position = camera.Transform().Position.Sub(camera.right.Mul(float32(cameraSpeed * dt))) 38 | } 39 | 40 | // Right 41 | func (camera *Camera) Right(dt float64) { 42 | camera.Transform().Position = camera.Transform().Position.Add(camera.right.Mul(float32(cameraSpeed * dt))) 43 | } 44 | 45 | // Rotate 46 | func (camera *Camera) Rotate(x, y, z float32) { 47 | camera.Transform().Rotation[0] -= float32(x * sensitivity) 48 | camera.Transform().Rotation[1] -= float32(y * sensitivity) 49 | camera.Transform().Rotation[2] -= float32(z * sensitivity) 50 | 51 | // Lock vertical rotation 52 | if camera.Transform().Rotation[2] > maxVerticalRotation { 53 | camera.Transform().Rotation[2] = maxVerticalRotation 54 | } 55 | if camera.Transform().Rotation[2] < minVerticalRotation { 56 | camera.Transform().Rotation[2] = minVerticalRotation 57 | } 58 | } 59 | 60 | // Update updates the camera position 61 | func (camera *Camera) Update(dt float64) { 62 | camera.updateVectors() 63 | } 64 | 65 | // updateVectors Updates the camera directional properties with any changes 66 | func (camera *Camera) updateVectors() { 67 | rot := camera.Transform().Rotation 68 | 69 | // Calculate the new Front vector 70 | camera.direction = mgl32.Vec3{ 71 | float32(math.Cos(float64(rot[2])) * math.Sin(float64(rot[0]))), 72 | float32(math.Cos(float64(rot[2])) * math.Cos(float64(rot[0]))), 73 | float32(math.Sin(float64(rot[2]))), 74 | } 75 | // Also re-calculate the right and up vector 76 | camera.right = mgl32.Vec3{ 77 | float32(math.Sin(float64(rot[0]) - math.Pi/2)), 78 | float32(math.Cos(float64(rot[0]) - math.Pi/2)), 79 | 0, 80 | } 81 | camera.up = camera.right.Cross(camera.direction) 82 | } 83 | 84 | // ModelMatrix returns identity matrix (camera model is our position!) 85 | func (camera *Camera) ModelMatrix() mgl32.Mat4 { 86 | return mgl32.Ident4() 87 | } 88 | 89 | // ViewMatrix calculates the cameras View matrix 90 | func (camera *Camera) ViewMatrix() mgl32.Mat4 { 91 | return mgl32.LookAtV( 92 | camera.Transform().Position, 93 | camera.Transform().Position.Add(camera.direction), 94 | camera.up) 95 | } 96 | 97 | // ProjectionMatrix calculates projection matrix. 98 | // This is unlikely to change throughout program lifetime, but could do 99 | func (camera *Camera) ProjectionMatrix() mgl32.Mat4 { 100 | return mgl32.Perspective(camera.fov, camera.aspectRatio, 0.1, 16384) 101 | } 102 | 103 | // NewCamera returns a new camera 104 | // fov should be provided in radians 105 | func NewCamera(fov float32, aspectRatio float32) *Camera { 106 | return &Camera{ 107 | Base: &Base{}, 108 | fov: fov, 109 | aspectRatio: aspectRatio, 110 | up: mgl32.Vec3{0, 1, 0}, 111 | worldUp: mgl32.Vec3{0, 1, 0}, 112 | direction: mgl32.Vec3{0, 0, -1}, 113 | } 114 | } 115 | -------------------------------------------------------------------------------- /entity/camera_test.go: -------------------------------------------------------------------------------- 1 | package entity 2 | 3 | import "testing" 4 | 5 | func TestCamera_ModelMatrix(t *testing.T) { 6 | t.Skip() 7 | } 8 | 9 | func TestCamera_ViewMatrix(t *testing.T) { 10 | t.Skip() 11 | } 12 | 13 | func TestCamera_ProjectionMatrix(t *testing.T) { 14 | t.Skip() 15 | } 16 | 17 | func TestCamera_Update(t *testing.T) { 18 | t.Skip() 19 | } 20 | 21 | func TestCamera_ReceiveMessage(t *testing.T) { 22 | t.Skip() 23 | } 24 | -------------------------------------------------------------------------------- /entity/generic.go: -------------------------------------------------------------------------------- 1 | package entity 2 | 3 | import ( 4 | entity2 "github.com/galaco/source-tools-common/entity" 5 | ) 6 | 7 | // GenericEntity can be used to substitute out an entity 8 | // that doesn't have a known implementation 9 | type GenericEntity struct { 10 | Base 11 | } 12 | 13 | // NewGenericEntity returns new Entity 14 | func NewGenericEntity(definition *entity2.Entity) *GenericEntity { 15 | ent := &GenericEntity{ 16 | Base: Base{ 17 | keyValues: definition, 18 | handle: newEntityHandle(), 19 | }, 20 | } 21 | 22 | return ent 23 | } 24 | -------------------------------------------------------------------------------- /entity/generic_test.go: -------------------------------------------------------------------------------- 1 | package entity 2 | 3 | import ( 4 | "reflect" 5 | "testing" 6 | ) 7 | 8 | func TestNewGenericEntity(t *testing.T) { 9 | sut := NewGenericEntity(nil) 10 | 11 | if reflect.TypeOf(sut) != reflect.TypeOf(&GenericEntity{}) { 12 | t.Errorf("Expected: %s, but received: %s", reflect.TypeOf(&GenericEntity{}), reflect.TypeOf(sut)) 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /entity/ientity.go: -------------------------------------------------------------------------------- 1 | package entity 2 | 3 | import ( 4 | "github.com/galaco/source-tools-common/entity" 5 | ) 6 | 7 | // IEntity Base interface 8 | // All game entities need to implement this 9 | type IEntity interface { 10 | // KeyValues 11 | KeyValues() *entity.Entity 12 | // SetKeyValues 13 | SetKeyValues(*entity.Entity) 14 | // Classname 15 | Classname() string 16 | // Transform 17 | Transform() *Transform 18 | // New 19 | New() IEntity 20 | } 21 | 22 | // IClassname all valid game entities should have a classname, 23 | // but there may be temporary non-game entities that have classnames 24 | type IClassname interface { 25 | // Classname 26 | Classname() string 27 | } 28 | -------------------------------------------------------------------------------- /entity/transform.go: -------------------------------------------------------------------------------- 1 | package entity 2 | 3 | import ( 4 | "github.com/go-gl/mathgl/mgl32" 5 | ) 6 | 7 | // Transform Represents the transformation of an entity in 8 | // a 3-dimensional space: position, rotation and scale. 9 | // Note: Rotation is measured in degrees 10 | type Transform struct { 11 | Position mgl32.Vec3 12 | Rotation mgl32.Vec3 13 | Scale mgl32.Vec3 14 | 15 | prevPosition mgl32.Vec3 16 | prevRotation mgl32.Vec3 17 | prevScale mgl32.Vec3 18 | matrix mgl32.Mat4 19 | quat mgl32.Quat 20 | } 21 | 22 | // TransformationMatrix computes object transformation matrix 23 | func (transform *Transform) TransformationMatrix() mgl32.Mat4 { 24 | if !transform.Position.ApproxEqual(transform.prevPosition) || 25 | !transform.Rotation.ApproxEqual(transform.prevRotation) || 26 | !transform.Scale.ApproxEqual(transform.prevScale) { 27 | 28 | transform.quat = mgl32.QuatIdent() 29 | 30 | // Scale of 0 is invalid 31 | if transform.Scale.X() == 0 || 32 | transform.Scale.Y() == 0 || 33 | transform.Scale.Z() == 0 { 34 | transform.Scale = mgl32.Vec3{1, 1, 1} 35 | } 36 | 37 | //Translate 38 | translation := mgl32.Translate3D(transform.Position.X(), transform.Position.Y(), transform.Position.Z()) 39 | 40 | // rotate 41 | // IMPORTANT. Source engine has Y and Z axis switched 42 | rotation := mgl32.Ident4() 43 | rotation = transform.rotateAroundAxis(rotation, mgl32.Vec3{1, 0, 0}, mgl32.DegToRad(transform.Rotation.X())) 44 | rotation = transform.rotateAroundAxis(rotation, mgl32.Vec3{0, 1, 0}, mgl32.DegToRad(transform.Rotation.Z())) 45 | rotation = transform.rotateAroundAxis(rotation, mgl32.Vec3{0, 0, 1}, mgl32.DegToRad(transform.Rotation.Y())) 46 | 47 | //@TODO ROTATIONS 48 | 49 | // scale 50 | scale := mgl32.Scale3D(transform.Scale.X(), transform.Scale.Y(), transform.Scale.Z()) 51 | 52 | transform.prevPosition = transform.Position 53 | transform.prevRotation = transform.Rotation 54 | transform.prevScale = transform.Scale 55 | 56 | transform.matrix = translation.Mul4(rotation).Mul4(scale) 57 | } 58 | 59 | return transform.matrix 60 | } 61 | 62 | // rotateAroundAxis rotates a matrix around a given axis 63 | func (transform *Transform) rotateAroundAxis(matrix mgl32.Mat4, axis mgl32.Vec3, angle float32) mgl32.Mat4 { 64 | q1 := mgl32.QuatRotate(angle, axis) 65 | transform.quat = transform.quat.Mul(q1) 66 | 67 | return matrix.Mul4(q1.Mat4()) 68 | 69 | } 70 | -------------------------------------------------------------------------------- /entity/transform_test.go: -------------------------------------------------------------------------------- 1 | package entity 2 | 3 | import "testing" 4 | 5 | func TestTransform_TransformationMatrix(t *testing.T) { 6 | t.Skip() 7 | } 8 | -------------------------------------------------------------------------------- /event/id.go: -------------------------------------------------------------------------------- 1 | package event 2 | 3 | // MessageType 4 | type MessageType string 5 | 6 | // EventHandle 7 | type EventHandle uint 8 | 9 | var eventHandleCounter EventHandle 10 | 11 | // newEventHandle 12 | func newEventHandle() EventHandle { 13 | eventHandleCounter++ 14 | return eventHandleCounter 15 | } 16 | -------------------------------------------------------------------------------- /event/imessage.go: -------------------------------------------------------------------------------- 1 | package event 2 | 3 | // IMessage Generic event manager message interface 4 | // All messages need to implement this 5 | type IMessage interface { 6 | Type() MessageType 7 | } 8 | -------------------------------------------------------------------------------- /event/manager.go: -------------------------------------------------------------------------------- 1 | package event 2 | 3 | import ( 4 | "sync" 5 | ) 6 | 7 | // manager Event manager 8 | // Handles sending and receiving events for immediate handling 9 | // Generally used for engine functionality, such as user input events, window 10 | // management etc. 11 | // Game entities should use their own event queue, and should not hook into this queue. 12 | type manager struct { 13 | listenerMap map[MessageType]map[EventHandle]func(IMessage) 14 | mu sync.Mutex 15 | eventQueue []*queueItem 16 | } 17 | 18 | // Listen Register a new listener to listen to an event 19 | func (manager *manager) Listen(eventName MessageType, callback func(IMessage)) EventHandle { 20 | handle := newEventHandle() 21 | manager.mu.Lock() 22 | if _, ok := manager.listenerMap[eventName]; !ok { 23 | manager.listenerMap[eventName] = make(map[EventHandle]func(IMessage)) 24 | } 25 | manager.listenerMap[eventName][handle] = callback 26 | manager.mu.Unlock() 27 | 28 | return handle 29 | } 30 | 31 | // ProcessQueue Runs the event queue in its own go routine 32 | func (manager *manager) ProcessQueue() { 33 | manager.mu.Lock() 34 | queue := manager.eventQueue 35 | manager.mu.Unlock() 36 | 37 | if len(queue) > 0 { 38 | // FIFO - ensure dispatch order, and concurrency integrity 39 | item := queue[0] 40 | manager.mu.Lock() 41 | manager.eventQueue = manager.eventQueue[1:] 42 | 43 | // Fire event 44 | listeners := manager.listenerMap[item.EventName] 45 | manager.mu.Unlock() 46 | for _, listener := range listeners { 47 | listener(item.Message) 48 | } 49 | } 50 | } 51 | 52 | // Unlisten Remove a listener from listening for an event 53 | func (manager *manager) Unlisten(eventName MessageType, handle EventHandle) { 54 | manager.mu.Lock() 55 | delete(manager.listenerMap[eventName], handle) 56 | manager.mu.Unlock() 57 | } 58 | 59 | // Dispatch Fires an event to all listening objects 60 | func (manager *manager) Dispatch(message IMessage) { 61 | queueItem := &queueItem{ 62 | EventName: message.Type(), 63 | Message: message, 64 | } 65 | manager.mu.Lock() 66 | manager.eventQueue = append(manager.eventQueue, queueItem) 67 | manager.mu.Unlock() 68 | 69 | manager.ProcessQueue() 70 | } 71 | 72 | var eventManager manager 73 | 74 | // Manager return static eventmanager 75 | func Manager() *manager { 76 | if eventManager.listenerMap == nil { 77 | eventManager.listenerMap = make(map[MessageType]map[EventHandle]func(IMessage), 512) 78 | } 79 | return &eventManager 80 | } 81 | 82 | // queueItem Event Queue item. 83 | // Contains the event name, and a message 84 | type queueItem struct { 85 | EventName MessageType 86 | Message IMessage 87 | } 88 | -------------------------------------------------------------------------------- /event/manager_test.go: -------------------------------------------------------------------------------- 1 | package event 2 | 3 | import ( 4 | "reflect" 5 | "testing" 6 | ) 7 | 8 | func TestGetEventManager(t *testing.T) { 9 | if reflect.TypeOf(Manager()) != reflect.TypeOf(&manager{}) || Manager() == nil { 10 | t.Error("Unexpected value for event manager") 11 | } 12 | } 13 | 14 | func TestManager_Dispatch(t *testing.T) { 15 | 16 | } 17 | 18 | func TestManager_Listen(t *testing.T) { 19 | 20 | } 21 | 22 | func TestManager_Unlisten(t *testing.T) { 23 | 24 | } 25 | -------------------------------------------------------------------------------- /event/message.go: -------------------------------------------------------------------------------- 1 | package event 2 | 3 | // Message is a generic event message 4 | // Contains the type of event 5 | type Message struct { 6 | id MessageType 7 | } 8 | 9 | // Type Gets type of event 10 | func (message Message) Type() MessageType { 11 | return message.id 12 | } 13 | -------------------------------------------------------------------------------- /event/message_test.go: -------------------------------------------------------------------------------- 1 | package event 2 | 3 | import "testing" 4 | 5 | func TestMessage_GetType(t *testing.T) { 6 | msg := Message{ 7 | id: MessageType("foo"), 8 | } 9 | if msg.Type() != MessageType("foo") { 10 | t.Errorf("Unexpected message type. Got %s, but expected %s", MessageType("foo"), msg.Type()) 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /filesystem/constants.go: -------------------------------------------------------------------------------- 1 | package filesystem 2 | 3 | // File Extensions 4 | const ( 5 | // ExtensionVmt Material file extension 6 | ExtensionVmt = ".vmt" 7 | // ExtensionVtf Texture file extension 8 | ExtensionVtf = ".vtf" 9 | ) 10 | 11 | // FilePath prefixes 12 | const ( 13 | // BasePathMaterial is path prefix for all materials/textures 14 | BasePathMaterial = "materials/" 15 | // BasePathModels is path prefix for all models/props 16 | BasePathModels = "models/" 17 | ) 18 | -------------------------------------------------------------------------------- /game/IGame.go: -------------------------------------------------------------------------------- 1 | package game 2 | 3 | // IGame interface represents a game 4 | type IGame interface { 5 | // RegisterEntityClasses should setup any game entity classes 6 | // for use with the engine when loading entdata 7 | RegisterEntityClasses() 8 | } 9 | -------------------------------------------------------------------------------- /game/README.md: -------------------------------------------------------------------------------- 1 | ### Game 2 | 3 | Game contains game specific definitions. Each game would normally have its own unique 4 | implementation of any combination of entities. 5 | 6 | All source engine games have different available entities, as well as plenty 7 | of their own game specific code. Providing entity definitions for the game 8 | to the main engine code is managed here. -------------------------------------------------------------------------------- /game/counterstrike-source.go: -------------------------------------------------------------------------------- 1 | package game 2 | 3 | import ( 4 | "github.com/galaco/lambda-core/game/entity/common" 5 | "github.com/galaco/lambda-core/loader/entity/classmap" 6 | ) 7 | 8 | // CounterstrikeSource 9 | type CounterstrikeSource struct{} 10 | 11 | // RegisterEntityClasses loads all Game entity classes into the engine. 12 | func (target *CounterstrikeSource) RegisterEntityClasses() { 13 | loader.RegisterClass(&common.PropDoorRotating{}) 14 | loader.RegisterClass(&common.PropDynamic{}) 15 | loader.RegisterClass(&common.PropDynamicOrnament{}) 16 | loader.RegisterClass(&common.PropDynamicOverride{}) 17 | loader.RegisterClass(&common.PropPhysics{}) 18 | loader.RegisterClass(&common.PropPhysicsMultiplayer{}) 19 | loader.RegisterClass(&common.PropPhysicsOverride{}) 20 | loader.RegisterClass(&common.PropRagdoll{}) 21 | } 22 | -------------------------------------------------------------------------------- /game/entity/common/prop_door_rotating.go: -------------------------------------------------------------------------------- 1 | package common 2 | 3 | import ( 4 | entity2 "github.com/galaco/lambda-core/entity" 5 | "github.com/galaco/lambda-core/game/entity" 6 | ) 7 | 8 | type PropDoorRotating struct { 9 | entity2.Base 10 | entity.PropBase 11 | } 12 | 13 | func (entity *PropDoorRotating) New() entity2.IEntity { 14 | return &PropDoorRotating{} 15 | } 16 | 17 | func (entity PropDoorRotating) Classname() string { 18 | return "prop_door_rotating" 19 | } 20 | -------------------------------------------------------------------------------- /game/entity/common/prop_door_rotating_test.go: -------------------------------------------------------------------------------- 1 | package common 2 | 3 | import ( 4 | "reflect" 5 | "testing" 6 | ) 7 | 8 | func TestPropDoorRotating_Classname(t *testing.T) { 9 | sut := PropDoorRotating{} 10 | if sut.Classname() != "prop_door_rotating" { 11 | t.Errorf("expected classname: prop_door_rotating, but got: %s", sut.Classname()) 12 | } 13 | } 14 | 15 | func TestPropDoorRotating_New(t *testing.T) { 16 | sut := &PropDoorRotating{} 17 | 18 | actual := sut.New() 19 | if reflect.TypeOf(actual) != reflect.TypeOf(sut) { 20 | t.Errorf("unexpected type returned from New. Expected: %s, but received: %s", reflect.TypeOf(sut), reflect.TypeOf(actual)) 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /game/entity/common/prop_dynamic.go: -------------------------------------------------------------------------------- 1 | package common 2 | 3 | import ( 4 | entity2 "github.com/galaco/lambda-core/entity" 5 | "github.com/galaco/lambda-core/game/entity" 6 | ) 7 | 8 | // PropDynamic 9 | type PropDynamic struct { 10 | entity2.Base 11 | entity.PropBase 12 | } 13 | 14 | // New 15 | func (entity *PropDynamic) New() entity2.IEntity { 16 | return &PropDynamic{} 17 | } 18 | 19 | // Classname 20 | func (entity PropDynamic) Classname() string { 21 | return "prop_dynamic" 22 | } 23 | -------------------------------------------------------------------------------- /game/entity/common/prop_dynamic_ornament.go: -------------------------------------------------------------------------------- 1 | package common 2 | 3 | import ( 4 | "github.com/galaco/lambda-core/entity" 5 | entity2 "github.com/galaco/lambda-core/game/entity" 6 | ) 7 | 8 | //PropDynamicOrnament 9 | type PropDynamicOrnament struct { 10 | entity.Base 11 | entity2.PropBase 12 | } 13 | 14 | // New 15 | func (entity *PropDynamicOrnament) New() entity.IEntity { 16 | return &PropDynamicOrnament{} 17 | } 18 | 19 | // Classname 20 | func (entity PropDynamicOrnament) Classname() string { 21 | return "prop_dynamic_ornament" 22 | } 23 | -------------------------------------------------------------------------------- /game/entity/common/prop_dynamic_ornament_test.go: -------------------------------------------------------------------------------- 1 | package common 2 | 3 | import ( 4 | "reflect" 5 | "testing" 6 | ) 7 | 8 | func TestPropDynamicOrnament_Classname(t *testing.T) { 9 | sut := PropDynamicOrnament{} 10 | if sut.Classname() != "prop_dynamic_ornament" { 11 | t.Errorf("expected classname: prop_dynamic_ornament, but got: %s", sut.Classname()) 12 | } 13 | } 14 | 15 | func TestPropDynamicOrnament_New(t *testing.T) { 16 | sut := &PropDynamicOrnament{} 17 | 18 | actual := sut.New() 19 | if reflect.TypeOf(actual) != reflect.TypeOf(sut) { 20 | t.Errorf("unexpected type returned from New. Expected: %s, but received: %s", reflect.TypeOf(sut), reflect.TypeOf(actual)) 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /game/entity/common/prop_dynamic_override.go: -------------------------------------------------------------------------------- 1 | package common 2 | 3 | import ( 4 | "github.com/galaco/lambda-core/entity" 5 | entity2 "github.com/galaco/lambda-core/game/entity" 6 | ) 7 | 8 | // PropDynamicOverride 9 | type PropDynamicOverride struct { 10 | entity.Base 11 | entity2.PropBase 12 | } 13 | 14 | // New 15 | func (entity *PropDynamicOverride) New() entity.IEntity { 16 | return &PropDynamicOverride{} 17 | } 18 | 19 | // Classname 20 | func (entity PropDynamicOverride) Classname() string { 21 | return "prop_dynamic_override" 22 | } 23 | -------------------------------------------------------------------------------- /game/entity/common/prop_dynamic_override_test.go: -------------------------------------------------------------------------------- 1 | package common 2 | 3 | import ( 4 | "reflect" 5 | "testing" 6 | ) 7 | 8 | func TestPropDynamicOverride_Classname(t *testing.T) { 9 | sut := PropDynamicOverride{} 10 | if sut.Classname() != "prop_dynamic_override" { 11 | t.Errorf("expected classname: prop_dynamic_override, but got: %s", sut.Classname()) 12 | } 13 | } 14 | 15 | func TestPropDynamicOverride_New(t *testing.T) { 16 | sut := &PropDynamicOverride{} 17 | 18 | actual := sut.New() 19 | if reflect.TypeOf(actual) != reflect.TypeOf(sut) { 20 | t.Errorf("unexpected type returned from New. Expected: %s, but received: %s", reflect.TypeOf(sut), reflect.TypeOf(actual)) 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /game/entity/common/prop_dynamic_test.go: -------------------------------------------------------------------------------- 1 | package common 2 | 3 | import ( 4 | "reflect" 5 | "testing" 6 | ) 7 | 8 | func TestPropDynamic_Classname(t *testing.T) { 9 | sut := PropDynamic{} 10 | if sut.Classname() != "prop_dynamic" { 11 | t.Errorf("expected classname: prop_dynamic, but got: %s", sut.Classname()) 12 | } 13 | } 14 | 15 | func TestPropDynamic_New(t *testing.T) { 16 | sut := &PropDynamic{} 17 | 18 | actual := sut.New() 19 | if reflect.TypeOf(actual) != reflect.TypeOf(sut) { 20 | t.Errorf("unexpected type returned from New. Expected: %s, but received: %s", reflect.TypeOf(sut), reflect.TypeOf(actual)) 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /game/entity/common/prop_physics.go: -------------------------------------------------------------------------------- 1 | package common 2 | 3 | import ( 4 | entity2 "github.com/galaco/lambda-core/entity" 5 | "github.com/galaco/lambda-core/game/entity" 6 | ) 7 | 8 | // PropPhysics 9 | type PropPhysics struct { 10 | entity2.Base 11 | entity.PropBase 12 | } 13 | 14 | // New 15 | func (entity *PropPhysics) New() entity2.IEntity { 16 | return &PropPhysics{} 17 | } 18 | 19 | // Classname 20 | func (entity PropPhysics) Classname() string { 21 | return "prop_physics" 22 | } 23 | -------------------------------------------------------------------------------- /game/entity/common/prop_physics_multiplayer.go: -------------------------------------------------------------------------------- 1 | package common 2 | 3 | import ( 4 | "github.com/galaco/lambda-core/entity" 5 | entity2 "github.com/galaco/lambda-core/game/entity" 6 | ) 7 | 8 | // PropPhysicsMultiplayer 9 | type PropPhysicsMultiplayer struct { 10 | entity.Base 11 | entity2.PropBase 12 | } 13 | 14 | // New 15 | func (entity *PropPhysicsMultiplayer) New() entity.IEntity { 16 | return &PropPhysicsMultiplayer{} 17 | } 18 | 19 | // Classname 20 | func (entity PropPhysicsMultiplayer) Classname() string { 21 | return "prop_physics_multiplayer" 22 | } 23 | -------------------------------------------------------------------------------- /game/entity/common/prop_physics_multiplayer_test.go: -------------------------------------------------------------------------------- 1 | package common 2 | 3 | import ( 4 | "reflect" 5 | "testing" 6 | ) 7 | 8 | func TestPropPhysicsMultiplayer_Classname(t *testing.T) { 9 | sut := PropPhysicsMultiplayer{} 10 | if sut.Classname() != "prop_physics_multiplayer" { 11 | t.Errorf("expected classname: prop_physics_multiplayer, but got: %s", sut.Classname()) 12 | } 13 | } 14 | 15 | func TestPropPhysicsMultiplayer_New(t *testing.T) { 16 | sut := &PropPhysicsMultiplayer{} 17 | 18 | actual := sut.New() 19 | if reflect.TypeOf(actual) != reflect.TypeOf(sut) { 20 | t.Errorf("unexpected type returned from New. Expected: %s, but received: %s", reflect.TypeOf(sut), reflect.TypeOf(actual)) 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /game/entity/common/prop_physics_override.go: -------------------------------------------------------------------------------- 1 | package common 2 | 3 | import ( 4 | "github.com/galaco/lambda-core/entity" 5 | entity2 "github.com/galaco/lambda-core/game/entity" 6 | ) 7 | 8 | // PropPhysicsOverride 9 | type PropPhysicsOverride struct { 10 | entity.Base 11 | entity2.PropBase 12 | } 13 | 14 | //New 15 | func (entity *PropPhysicsOverride) New() entity.IEntity { 16 | return &PropPhysicsOverride{} 17 | } 18 | 19 | // Classname 20 | func (entity PropPhysicsOverride) Classname() string { 21 | return "prop_physics_override" 22 | } 23 | -------------------------------------------------------------------------------- /game/entity/common/prop_physics_override_test.go: -------------------------------------------------------------------------------- 1 | package common 2 | 3 | import ( 4 | "reflect" 5 | "testing" 6 | ) 7 | 8 | func TestPropPhysicsOverride_Classname(t *testing.T) { 9 | sut := PropPhysicsOverride{} 10 | if sut.Classname() != "prop_physics_override" { 11 | t.Errorf("expected classname: prop_physics_override, but got: %s", sut.Classname()) 12 | } 13 | } 14 | 15 | func TestPropPhysicsOverride_New(t *testing.T) { 16 | sut := &PropPhysicsOverride{} 17 | 18 | actual := sut.New() 19 | if reflect.TypeOf(actual) != reflect.TypeOf(sut) { 20 | t.Errorf("unexpected type returned from New. Expected: %s, but received: %s", reflect.TypeOf(sut), reflect.TypeOf(actual)) 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /game/entity/common/prop_physics_test.go: -------------------------------------------------------------------------------- 1 | package common 2 | 3 | import ( 4 | "reflect" 5 | "testing" 6 | ) 7 | 8 | func TestPropPhysics_Classname(t *testing.T) { 9 | sut := PropPhysics{} 10 | if sut.Classname() != "prop_physics" { 11 | t.Errorf("expected classname: prop_physics, but got: %s", sut.Classname()) 12 | } 13 | } 14 | 15 | func TestPropPhysics_New(t *testing.T) { 16 | sut := &PropPhysics{} 17 | 18 | actual := sut.New() 19 | if reflect.TypeOf(actual) != reflect.TypeOf(sut) { 20 | t.Errorf("unexpected type returned from New. Expected: %s, but received: %s", reflect.TypeOf(sut), reflect.TypeOf(actual)) 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /game/entity/common/prop_ragdoll.go: -------------------------------------------------------------------------------- 1 | package common 2 | 3 | import ( 4 | entity2 "github.com/galaco/lambda-core/entity" 5 | "github.com/galaco/lambda-core/game/entity" 6 | ) 7 | 8 | // PropRagdoll 9 | type PropRagdoll struct { 10 | entity2.Base 11 | entity.PropBase 12 | } 13 | 14 | // New 15 | func (entity *PropRagdoll) New() entity2.IEntity { 16 | return &PropRagdoll{} 17 | } 18 | 19 | // Classname 20 | func (entity PropRagdoll) Classname() string { 21 | return "prop_ragdoll" 22 | } 23 | -------------------------------------------------------------------------------- /game/entity/common/prop_ragdoll_test.go: -------------------------------------------------------------------------------- 1 | package common 2 | 3 | import ( 4 | "reflect" 5 | "testing" 6 | ) 7 | 8 | func TestPropRagdoll_Classname(t *testing.T) { 9 | sut := PropRagdoll{} 10 | if sut.Classname() != "prop_ragdoll" { 11 | t.Errorf("expected classname: prop_ragdoll, but got: %s", sut.Classname()) 12 | } 13 | } 14 | 15 | func TestPropRagdoll_New(t *testing.T) { 16 | sut := &PropRagdoll{} 17 | 18 | actual := sut.New() 19 | if reflect.TypeOf(actual) != reflect.TypeOf(sut) { 20 | t.Errorf("unexpected type returned from New. Expected: %s, but received: %s", reflect.TypeOf(sut), reflect.TypeOf(actual)) 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /game/entity/iprop.go: -------------------------------------------------------------------------------- 1 | package entity 2 | 3 | import "github.com/galaco/lambda-core/model" 4 | 5 | // IProp Base renderable prop interface 6 | type IProp interface { 7 | Model() *model.Model 8 | SetModel(model *model.Model) 9 | } 10 | 11 | // PropBase is a minimal renderable prop entity 12 | type PropBase struct { 13 | model *model.Model 14 | } 15 | 16 | // SetModel 17 | func (prop *PropBase) SetModel(model *model.Model) { 18 | prop.model = model 19 | } 20 | 21 | // Model 22 | func (prop *PropBase) Model() *model.Model { 23 | return prop.model 24 | } 25 | -------------------------------------------------------------------------------- /game/entity/iprop_test.go: -------------------------------------------------------------------------------- 1 | package entity 2 | 3 | import ( 4 | "github.com/galaco/lambda-core/model" 5 | "testing" 6 | ) 7 | 8 | func TestPropBase_Model(t *testing.T) { 9 | sut := PropBase{} 10 | if sut.Model() != nil { 11 | t.Error("model was set, but should not be") 12 | } 13 | 14 | mod := &model.Model{} 15 | sut.SetModel(mod) 16 | 17 | if sut.Model() != mod { 18 | t.Errorf("set mode l does not match expected") 19 | } 20 | } 21 | 22 | func TestPropBase_SetModel(t *testing.T) { 23 | sut := PropBase{} 24 | if sut.Model() != nil { 25 | t.Error("model was set, but should not be") 26 | } 27 | 28 | mod := &model.Model{} 29 | sut.SetModel(mod) 30 | 31 | if sut.Model() != mod { 32 | t.Errorf("set mode l does not match expected") 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/galaco/lambda-core 2 | 3 | go 1.13 4 | 5 | require ( 6 | github.com/galaco/KeyValues v1.4.1 7 | github.com/galaco/bsp v0.3.0 8 | github.com/galaco/loggy v0.0.0-20190629005848-af043014a903 9 | github.com/galaco/source-tools-common v0.1.0 10 | github.com/galaco/studiomodel v0.1.3 11 | github.com/galaco/vmf v1.0.0 12 | github.com/galaco/vpk2 v0.0.0-20181012095330-21e4d1f6c888 13 | github.com/galaco/vtf v1.1.1 14 | github.com/go-gl/mathgl v0.0.0-20190713194549-592312d8590a 15 | github.com/galaco/filesystem v0.1.3 16 | github.com/galaco/stringtable v0.1.1 17 | github.com/galaco/vmt v0.1.3 18 | ) 19 | -------------------------------------------------------------------------------- /lib/gameinfo/load.go: -------------------------------------------------------------------------------- 1 | package gameinfo 2 | 3 | import ( 4 | "github.com/galaco/KeyValues" 5 | "os" 6 | ) 7 | 8 | // LoadConfig loads a gameinfo.txt source engine file 9 | func LoadConfig(gameDirectory string) (*keyvalues.KeyValue, error) { 10 | // Load gameinfo.txt 11 | gameInfoFile, err := os.Open(gameDirectory + "/gameinfo.txt") 12 | if err != nil { 13 | return nil, err 14 | } 15 | return Load(gameInfoFile) 16 | } 17 | -------------------------------------------------------------------------------- /lib/gameinfo/reader.go: -------------------------------------------------------------------------------- 1 | package gameinfo 2 | 3 | import ( 4 | "github.com/galaco/KeyValues" 5 | "io" 6 | ) 7 | 8 | var gameInfo keyvalues.KeyValue 9 | 10 | // Get returns static gameinfo.txt keyvalues 11 | func Get() *keyvalues.KeyValue { 12 | return &gameInfo 13 | } 14 | 15 | // Load parses a gameinfo.txt stream to a KeyValues object 16 | func Load(stream io.Reader) (*keyvalues.KeyValue, error) { 17 | kvReader := keyvalues.NewReader(stream) 18 | 19 | kv, err := kvReader.Read() 20 | if err == nil { 21 | gameInfo = kv 22 | } 23 | 24 | return &gameInfo, err 25 | } 26 | -------------------------------------------------------------------------------- /lib/math/shape/rectangle.go: -------------------------------------------------------------------------------- 1 | package shape 2 | 3 | import "github.com/go-gl/mathgl/mgl32" 4 | 5 | // Rect 6 | type Rect struct { 7 | // Mins 8 | Mins mgl32.Vec2 9 | // Maxs 10 | Maxs mgl32.Vec2 11 | } 12 | 13 | // X 14 | func (rect *Rect) X() float32 { 15 | return rect.Mins.X() 16 | } 17 | 18 | // Y 19 | func (rect *Rect) Y() float32 { 20 | return rect.Mins.Y() 21 | } 22 | 23 | // Width 24 | func (rect *Rect) Width() float32 { 25 | return rect.Maxs.X() 26 | } 27 | 28 | // Height 29 | func (rect *Rect) Height() float32 { 30 | return rect.Maxs.Y() 31 | } 32 | 33 | // NewRect 34 | func NewRect(mins mgl32.Vec2, maxs mgl32.Vec2) *Rect { 35 | return &Rect{ 36 | Mins: mins, 37 | Maxs: maxs, 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /lib/stringtable/stringtable.go: -------------------------------------------------------------------------------- 1 | package stringtable 2 | 3 | import ( 4 | "github.com/galaco/bsp/lumps" 5 | "github.com/galaco/bsp/primitives/texinfo" 6 | "github.com/galaco/stringtable" 7 | ) 8 | 9 | // NewTable returns a new StringTable 10 | func NewTable(stringData *lumps.TexDataStringData, stringTable *lumps.TexDataStringTable) *stringtable.StringTable { 11 | // Prepare texture lookup table 12 | return stringtable.NewFromExistingStringTableData(stringData.GetData(), stringTable.GetData()) 13 | } 14 | 15 | // SortUnique builds a unique list of materials in a StringTable 16 | // referenced by BSP TexInfo lump data. 17 | func SortUnique(stringTable *stringtable.StringTable, texInfos *[]texinfo.TexInfo) []string { 18 | materialList := make([]string, 0) 19 | for _, ti := range *texInfos { 20 | target, _ := stringTable.FindString(int(ti.TexData)) 21 | found := false 22 | for _, cur := range materialList { 23 | if cur == target { 24 | found = true 25 | break 26 | } 27 | } 28 | if !found { 29 | materialList = append(materialList, target) 30 | } 31 | } 32 | 33 | return materialList 34 | } 35 | -------------------------------------------------------------------------------- /lib/studiomodel/studiomodel.go: -------------------------------------------------------------------------------- 1 | package studiomodel 2 | 3 | import ( 4 | "errors" 5 | "github.com/galaco/studiomodel" 6 | "github.com/galaco/studiomodel/vtx" 7 | "github.com/galaco/studiomodel/vvd" 8 | ) 9 | 10 | // VertexDataForModel loads model vertex data 11 | func VertexDataForModel(studioModel *studiomodel.StudioModel, lodIdx int) ([][]float32, [][]float32, [][]float32, error) { 12 | vertices := make([][]float32, 0) 13 | normals := make([][]float32, 0) 14 | textureCoordinates := make([][]float32, 0) 15 | for _, bodyPart := range studioModel.Vtx.BodyParts { 16 | for _, model := range bodyPart.Models { 17 | if len(model.LODS) < lodIdx { 18 | return nil, nil, nil, errors.New("invalid LOD index requested for model") 19 | } 20 | for _, mesh := range model.LODS[lodIdx].Meshes { 21 | indices := indicesForMesh(&mesh) 22 | if len(indices) == 0 { 23 | continue 24 | } 25 | 26 | v, n, uv, err := vertexDataForMesh(indices, studioModel.Vvd) 27 | if err != nil { 28 | return nil, nil, nil, err 29 | } 30 | vertices = append(vertices, v) 31 | normals = append(normals, n) 32 | textureCoordinates = append(textureCoordinates, uv) 33 | } 34 | } 35 | } 36 | 37 | return vertices, normals, textureCoordinates, nil 38 | } 39 | 40 | // indicesForMesh get indices for mesh 41 | func indicesForMesh(mesh *vtx.Mesh) []uint16 { 42 | if len(mesh.StripGroups) > 1 { 43 | return make([]uint16, 0) 44 | } 45 | // indexMap := make([]uint16, 0) 46 | meshIndices := make([]uint16, 0) 47 | 48 | stripGroup := mesh.StripGroups[0] 49 | 50 | //for i := 0; i < len(stripGroup.Vertexes); i++ { 51 | // indexMap = append(indexMap, stripGroup.Vertexes[i].OriginalMeshVertexID) 52 | //} 53 | 54 | for _, strip := range stripGroup.Strips { 55 | for j := int32(0); j < strip.NumIndices; j++ { 56 | index := stripGroup.Indices[strip.IndexOffset+j] 57 | vert := stripGroup.Vertexes[index] 58 | 59 | meshIndices = append(meshIndices, uint16(strip.VertOffset)+vert.OriginalMeshVertexID) 60 | } 61 | } 62 | 63 | return meshIndices 64 | } 65 | 66 | func vertexDataForMesh(indices []uint16, vvd *vvd.Vvd) ([]float32, []float32, []float32, error) { 67 | verts := make([]float32, 0) 68 | normals := make([]float32, 0) 69 | textureCoordinates := make([]float32, 0) 70 | 71 | for _, index := range indices { 72 | if int(index) > len(vvd.Vertices) { 73 | return nil, nil, nil, errors.New("vertex data bounds out of range") 74 | } 75 | vvdVert := &vvd.Vertices[index] 76 | 77 | verts = append(verts, vvdVert.Position.X(), vvdVert.Position.Y(), vvdVert.Position.Z()) 78 | normals = append(normals, vvdVert.Normal.X(), vvdVert.Normal.Y(), vvdVert.Normal.Z()) 79 | textureCoordinates = append(textureCoordinates, vvdVert.UVs.X(), vvdVert.UVs.Y()) 80 | } 81 | return verts, normals, textureCoordinates, nil 82 | } 83 | -------------------------------------------------------------------------------- /lib/util/logger.go: -------------------------------------------------------------------------------- 1 | package util 2 | 3 | import ( 4 | "github.com/galaco/loggy" 5 | ) 6 | 7 | var logger *loggy.Loggy 8 | 9 | func Logger() *loggy.Loggy { 10 | if logger == nil { 11 | logger = loggy.NewLoggy() 12 | } 13 | 14 | return logger 15 | } 16 | -------------------------------------------------------------------------------- /lib/util/strings.go: -------------------------------------------------------------------------------- 1 | package util 2 | 3 | // RemoveDuplicatesFromList 4 | func RemoveDuplicatesFromList(list []string) (uniqueList []string) { 5 | for _, entry := range list { 6 | found := false 7 | for _, unique := range uniqueList { 8 | if entry == unique { 9 | found = true 10 | break 11 | } 12 | } 13 | if !found { 14 | uniqueList = append(uniqueList, entry) 15 | } 16 | } 17 | 18 | return uniqueList 19 | } 20 | -------------------------------------------------------------------------------- /lib/util/strings_test.go: -------------------------------------------------------------------------------- 1 | package util 2 | 3 | import "testing" 4 | 5 | func TestRemoveDuplicatesFromList(t *testing.T) { 6 | samples := []string{ 7 | "foo", 8 | "bar", 9 | "foo", 10 | "bar", 11 | "baz", 12 | "foo", 13 | "bar", 14 | "bat", 15 | "bat", 16 | } 17 | expected := []string{ 18 | "foo", 19 | "bar", 20 | "baz", 21 | "bat", 22 | } 23 | actual := RemoveDuplicatesFromList(samples) 24 | for i := 0; i < len(expected); i++ { 25 | if expected[i] != actual[i] { 26 | t.Error("unexpected entry in list after duplicate removal") 27 | } 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /lib/vpk/open.go: -------------------------------------------------------------------------------- 1 | package vpk 2 | 3 | import ( 4 | "github.com/galaco/vpk2" 5 | ) 6 | 7 | // OpenVPK Basic wrapper around vpk library. 8 | // Just opens a multi-part vpk (ver 2 only) 9 | func OpenVPK(filepath string) (*vpk.VPK, error) { 10 | return vpk.Open(vpk.MultiVPK(filepath)) 11 | } 12 | -------------------------------------------------------------------------------- /loader/bsp.go: -------------------------------------------------------------------------------- 1 | package loader 2 | 3 | import ( 4 | "github.com/galaco/bsp" 5 | "github.com/galaco/bsp/lumps" 6 | "github.com/galaco/bsp/primitives/common" 7 | "github.com/galaco/bsp/primitives/dispinfo" 8 | "github.com/galaco/bsp/primitives/dispvert" 9 | "github.com/galaco/bsp/primitives/face" 10 | "github.com/galaco/bsp/primitives/plane" 11 | "github.com/galaco/bsp/primitives/texinfo" 12 | "github.com/galaco/lambda-core/event" 13 | matloader "github.com/galaco/lambda-core/loader/material" 14 | "github.com/galaco/lambda-core/material" 15 | "github.com/galaco/lambda-core/mesh" 16 | "github.com/galaco/lambda-core/model" 17 | "github.com/galaco/lambda-core/resource" 18 | "github.com/galaco/lambda-core/resource/message" 19 | "github.com/galaco/lambda-core/scene" 20 | "github.com/galaco/lambda-core/texture" 21 | "github.com/go-gl/mathgl/mgl32" 22 | "github.com/galaco/filesystem" 23 | "math" 24 | "strings" 25 | "unsafe" 26 | ) 27 | 28 | type bspstructs struct { 29 | faces []face.Face 30 | planes []plane.Plane 31 | vertexes []mgl32.Vec3 32 | surfEdges []int32 33 | edges [][2]uint16 34 | texInfos []texinfo.TexInfo 35 | dispInfos []dispinfo.DispInfo 36 | dispVerts []dispvert.DispVert 37 | game *lumps.Game 38 | lightmap []common.ColorRGBExponent32 39 | } 40 | 41 | // LoadMap is the gateway into loading the core static level. Entities are loaded 42 | // elsewhere 43 | // It loads in the following order: 44 | // BSP Geometry 45 | // BSP Materials 46 | // StaticProps (materials loaded as required) 47 | func LoadMap(fs *filesystem.FileSystem, file *bsp.Bsp) scene.IScene { 48 | ResourceManager := resource.Manager() 49 | bspStructure := bspstructs{ 50 | faces: file.Lump(bsp.LumpFaces).(*lumps.Face).GetData(), 51 | planes: file.Lump(bsp.LumpPlanes).(*lumps.Planes).GetData(), 52 | vertexes: file.Lump(bsp.LumpVertexes).(*lumps.Vertex).GetData(), 53 | surfEdges: file.Lump(bsp.LumpSurfEdges).(*lumps.Surfedge).GetData(), 54 | edges: file.Lump(bsp.LumpEdges).(*lumps.Edge).GetData(), 55 | texInfos: file.Lump(bsp.LumpTexInfo).(*lumps.TexInfo).GetData(), 56 | dispInfos: file.Lump(bsp.LumpDispInfo).(*lumps.DispInfo).GetData(), 57 | dispVerts: file.Lump(bsp.LumpDispVerts).(*lumps.DispVert).GetData(), 58 | game: file.Lump(bsp.LumpGame).(*lumps.Game), 59 | lightmap: file.Lump(bsp.LumpLighting).(*lumps.Lighting).GetData(), 60 | } 61 | 62 | //MATERIALS 63 | stringTable := matloader.LoadMaterials( 64 | fs, 65 | file.Lump(bsp.LumpTexDataStringData).(*lumps.TexDataStringData), 66 | file.Lump(bsp.LumpTexDataStringTable).(*lumps.TexDataStringTable), 67 | &bspStructure.texInfos) 68 | 69 | // BSP FACES 70 | bspMesh := mesh.NewMesh() 71 | bspObject := model.NewBsp(bspMesh) 72 | bspFaces := make([]mesh.Face, len(bspStructure.faces)) 73 | dispFaces := make([]int, 0) 74 | lightMaps := make([]texture.ITexture, len(bspFaces)) 75 | 76 | for idx, f := range bspStructure.faces { 77 | if f.DispInfo > -1 { 78 | // This face is a displacement 79 | bspFaces[idx] = generateDisplacementFace(&f, &bspStructure, bspMesh) 80 | dispFaces = append(dispFaces, idx) 81 | } else { 82 | bspFaces[idx] = generateBspFace(&f, &bspStructure, bspMesh) 83 | } 84 | 85 | // Prepare lightmaps for each face 86 | if len(bspStructure.lightmap) < 1 { 87 | continue 88 | } 89 | 90 | lightMaps[idx] = texture.LightmapFromColorRGBExp32( 91 | int(f.LightmapTextureSizeInLuxels[0]+1), 92 | int(f.LightmapTextureSizeInLuxels[1]+1), 93 | lightmapSamplesFromFace(&f, &bspStructure.lightmap)) 94 | 95 | //bspFaces[idx].AddLightmap(texture.LightmapFromColorRGBExp32( 96 | // int(f.LightmapTextureSizeInLuxels[0]+1), 97 | // int(f.LightmapTextureSizeInLuxels[1]+1), 98 | // lightmapSamplesFromFace(&f, &bspStructure.lightmap))) 99 | //bspFaces[idx].Lightmap().Finish() 100 | } 101 | 102 | // Add MATERIALS TO FACES 103 | for idx, bspFace := range bspFaces { 104 | faceVmt, _ := stringTable.FindString(int(bspStructure.texInfos[bspStructure.faces[idx].TexInfo].TexData)) 105 | var mat material.IMaterial 106 | if ResourceManager.HasMaterial(faceVmt) { 107 | mat = ResourceManager.Material(faceVmt).(material.IMaterial) 108 | } else { 109 | mat = ResourceManager.Material(resource.Manager().ErrorTextureName()).(material.IMaterial) 110 | } 111 | 112 | lightMat := bspFaces[idx].Lightmap() 113 | bspFaces[idx].AddMaterial(mat) 114 | // Generate texture coordinates 115 | bspMesh.AddUV( 116 | texCoordsForFaceFromTexInfo( 117 | bspMesh.Vertices()[bspFace.Offset()*3:(bspFace.Offset()*3)+(bspFace.Length()*3)], 118 | &bspStructure.texInfos[bspStructure.faces[idx].TexInfo], mat.Width(), mat.Height())...) 119 | if lightMat != nil { 120 | bspMesh.AddLightmapCoordinate( 121 | lightmapCoordsForFaceFromTexInfo( 122 | bspMesh.Vertices()[bspFace.Offset()*3:(bspFace.Offset()*3)+(bspFace.Length()*3)], 123 | &bspStructure.faces[idx], 124 | &bspStructure.texInfos[bspStructure.faces[idx].TexInfo], lightMat.Width(), lightMat.Height())...) 125 | } 126 | 127 | if strings.HasPrefix(faceVmt, "TOOLS/") { 128 | bspFaces[idx].AddMaterial(nil) 129 | } 130 | } 131 | 132 | //lightmapTexture := texture.NewAtlas(4096, 4096) 133 | //_,err := lightmapTexture.PackTextures(lightMaps, 1) 134 | //if err != nil { 135 | // logger.Error(err) 136 | //} 137 | 138 | // Finish the bsp object. 139 | event.Manager().Dispatch(message.LoadedMap(bspObject)) 140 | //bspMesh.Finish() 141 | 142 | cl := model.ClusterLeaf{ 143 | Id: 0, 144 | Faces: bspFaces, 145 | DispFaces: dispFaces, 146 | } 147 | bspObject.SetClusterLeafs([]model.ClusterLeaf{cl}) 148 | 149 | // Get static props 150 | staticProps := LoadStaticProps(bspStructure.game.GetStaticPropLump(), fs) 151 | 152 | return scene.NewScene(*bspObject, staticProps) 153 | } 154 | 155 | // generateBspFace Create primitives from face data in the bsp 156 | func generateBspFace(f *face.Face, bspStructure *bspstructs, bspMesh mesh.IMesh) mesh.Face { 157 | offset := int32(len(bspMesh.Vertices())) / 3 158 | length := int32(0) 159 | 160 | planeNormal := bspStructure.planes[f.Planenum].Normal 161 | // All surfedges associated with this face 162 | // surfEdges are basically indices into the edges lump 163 | faceSurfEdges := bspStructure.surfEdges[f.FirstEdge:(f.FirstEdge + int32(f.NumEdges))] 164 | rootIndex := uint16(0) 165 | for idx, surfEdge := range faceSurfEdges { 166 | edge := bspStructure.edges[int(math.Abs(float64(surfEdge)))] 167 | e1 := 0 168 | e2 := 1 169 | if surfEdge < 0 { 170 | e1 = 1 171 | e2 = 0 172 | } 173 | //Capture root indice 174 | if idx == 0 { 175 | rootIndex = edge[e1] 176 | } else { 177 | // Just create a triangle for every edge now 178 | bspMesh.AddVertex(bspStructure.vertexes[rootIndex].X(), bspStructure.vertexes[rootIndex].Y(), bspStructure.vertexes[rootIndex].Z()) 179 | bspMesh.AddNormal(planeNormal.X(), planeNormal.Y(), planeNormal.Z()) 180 | 181 | bspMesh.AddVertex(bspStructure.vertexes[edge[e1]].X(), bspStructure.vertexes[edge[e1]].Y(), bspStructure.vertexes[edge[e1]].Z()) 182 | bspMesh.AddNormal(planeNormal.X(), planeNormal.Y(), planeNormal.Z()) 183 | 184 | bspMesh.AddVertex(bspStructure.vertexes[edge[e2]].X(), bspStructure.vertexes[edge[e2]].Y(), bspStructure.vertexes[edge[e2]].Z()) 185 | bspMesh.AddNormal(planeNormal.X(), planeNormal.Y(), planeNormal.Z()) 186 | 187 | length += 3 // num verts (3 b/c face triangles 188 | } 189 | } 190 | 191 | return mesh.NewFace(offset, length, nil, nil) 192 | } 193 | 194 | // generateDisplacementFace Create Primitive from Displacement face 195 | // This is based on: 196 | // https://github.com/Metapyziks/VBspViewer/blob/master/Assets/VBspViewer/Scripts/Importing/VBsp/VBspFile.cs 197 | func generateDisplacementFace(f *face.Face, bspStructure *bspstructs, bspMesh mesh.IMesh) mesh.Face { 198 | corners := make([]mgl32.Vec3, 4) 199 | normal := bspStructure.planes[f.Planenum].Normal 200 | 201 | info := bspStructure.dispInfos[f.DispInfo] 202 | size := int(1 << uint32(info.Power)) 203 | firstCorner := int32(0) 204 | firstCornerDist2 := float32(math.MaxFloat32) 205 | 206 | offset := int32(len(bspMesh.Vertices())) / 3 207 | length := int32(0) 208 | 209 | for surfId := f.FirstEdge; surfId < f.FirstEdge+int32(f.NumEdges); surfId++ { 210 | surfEdge := bspStructure.surfEdges[surfId] 211 | edgeIndex := int32(math.Abs(float64(surfEdge))) 212 | edge := bspStructure.edges[edgeIndex] 213 | vert := bspStructure.vertexes[edge[0]] 214 | if surfEdge < 0 { 215 | vert = bspStructure.vertexes[edge[1]] 216 | } 217 | corners[surfId-f.FirstEdge] = vert 218 | 219 | dist2tmp := info.StartPosition.Sub(vert) 220 | dist2 := (dist2tmp.X() * dist2tmp.X()) + (dist2tmp.Y() * dist2tmp.Y()) + (dist2tmp.Z() * dist2tmp.Z()) 221 | if dist2 < firstCornerDist2 { 222 | firstCorner = surfId - f.FirstEdge 223 | firstCornerDist2 = dist2 224 | } 225 | } 226 | 227 | for x := 0; x < size; x++ { 228 | for y := 0; y < size; y++ { 229 | a := generateDispVert(int(info.DispVertStart), x, y, size, corners, firstCorner, &bspStructure.dispVerts) 230 | b := generateDispVert(int(info.DispVertStart), x, y+1, size, corners, firstCorner, &bspStructure.dispVerts) 231 | c := generateDispVert(int(info.DispVertStart), x+1, y+1, size, corners, firstCorner, &bspStructure.dispVerts) 232 | d := generateDispVert(int(info.DispVertStart), x+1, y, size, corners, firstCorner, &bspStructure.dispVerts) 233 | 234 | // Split into triangles 235 | bspMesh.AddVertex(a.X(), a.Y(), a.Z(), b.X(), b.Y(), b.Z(), c.X(), c.Y(), c.Z()) 236 | bspMesh.AddNormal(normal.X(), normal.Y(), normal.Z(), normal.X(), normal.Y(), normal.Z(), normal.X(), normal.Y(), normal.Z()) 237 | bspMesh.AddVertex(a.X(), a.Y(), a.Z(), c.X(), c.Y(), c.Z(), d.X(), d.Y(), d.Z()) 238 | bspMesh.AddNormal(normal.X(), normal.Y(), normal.Z(), normal.X(), normal.Y(), normal.Z(), normal.X(), normal.Y(), normal.Z()) 239 | 240 | length += 6 // 6 b/c quad = 2*triangle 241 | } 242 | } 243 | 244 | return mesh.NewFace(offset, length, nil, nil) 245 | } 246 | 247 | // generateDispVert Create a displacement vertex 248 | func generateDispVert(offset int, x int, y int, size int, corners []mgl32.Vec3, firstCorner int32, dispVerts *[]dispvert.DispVert) mgl32.Vec3 { 249 | vert := (*dispVerts)[offset+x+y*(size+1)] 250 | 251 | tx := float32(x) / float32(size) 252 | ty := float32(y) / float32(size) 253 | sx := 1.0 - tx 254 | sy := 1.0 - ty 255 | 256 | cornerA := corners[(0+firstCorner)&3] 257 | cornerB := corners[(1+firstCorner)&3] 258 | cornerC := corners[(2+firstCorner)&3] 259 | cornerD := corners[(3+firstCorner)&3] 260 | 261 | origin := ((cornerB.Mul(sx).Add(cornerC.Mul(tx))).Mul(ty)).Add((cornerA.Mul(sx).Add(cornerD.Mul(tx))).Mul(sy)) 262 | 263 | return origin.Add(vert.Vec.Mul(vert.Dist)) 264 | } 265 | 266 | // texCoordsForFaceFromTexInfo Generate texturecoordinates for face data 267 | func texCoordsForFaceFromTexInfo(vertexes []float32, tx *texinfo.TexInfo, width int, height int) (uvs []float32) { 268 | for idx := 0; idx < len(vertexes); idx += 3 { 269 | //u = tv0,0 * x + tv0,1 * y + tv0,2 * z + tv0,3 270 | u := ((tx.TextureVecsTexelsPerWorldUnits[0][0] * vertexes[idx]) + 271 | (tx.TextureVecsTexelsPerWorldUnits[0][1] * vertexes[idx+1]) + 272 | (tx.TextureVecsTexelsPerWorldUnits[0][2] * vertexes[idx+2]) + 273 | tx.TextureVecsTexelsPerWorldUnits[0][3]) / float32(width) 274 | 275 | //v = tv1,0 * x + tv1,1 * y + tv1,2 * z + tv1,3 276 | v := ((tx.TextureVecsTexelsPerWorldUnits[1][0] * vertexes[idx]) + 277 | (tx.TextureVecsTexelsPerWorldUnits[1][1] * vertexes[idx+1]) + 278 | (tx.TextureVecsTexelsPerWorldUnits[1][2] * vertexes[idx+2]) + 279 | tx.TextureVecsTexelsPerWorldUnits[1][3]) / float32(height) 280 | 281 | uvs = append(uvs, u, v) 282 | } 283 | 284 | return uvs 285 | } 286 | 287 | // lightmapCoordsForFaceFromTexInfo create lightmap coordinates from TexInfo 288 | func lightmapCoordsForFaceFromTexInfo(vertexes []float32, faceInfo *face.Face, tx *texinfo.TexInfo, width int, height int) (uvs []float32) { 289 | for idx := 0; idx < len(vertexes); idx += 3 { 290 | u := (mgl32.Vec3{vertexes[idx], vertexes[idx+1], vertexes[idx+2]}).Dot( 291 | mgl32.Vec3{ 292 | tx.LightmapVecsLuxelsPerWorldUnits[0][0], 293 | tx.LightmapVecsLuxelsPerWorldUnits[0][1], 294 | tx.LightmapVecsLuxelsPerWorldUnits[0][2], 295 | }) + tx.LightmapVecsLuxelsPerWorldUnits[0][3] 296 | v := (mgl32.Vec3{vertexes[idx], vertexes[idx+1], vertexes[idx+2]}).Dot( 297 | mgl32.Vec3{ 298 | tx.LightmapVecsLuxelsPerWorldUnits[1][0], 299 | tx.LightmapVecsLuxelsPerWorldUnits[1][1], 300 | tx.LightmapVecsLuxelsPerWorldUnits[1][2], 301 | }) + tx.LightmapVecsLuxelsPerWorldUnits[1][3] 302 | 303 | u -= float32(faceInfo.LightmapTextureMinsInLuxels[0]) - .5 304 | v -= float32(faceInfo.LightmapTextureMinsInLuxels[1]) - .5 305 | u /= float32(faceInfo.LightmapTextureSizeInLuxels[0]) + 1 306 | v /= float32(faceInfo.LightmapTextureSizeInLuxels[1]) + 1 307 | 308 | //u *= float32(width) // lightmapRect.width 309 | //v *= float32(height) //lightmapRect.height 310 | //u += lightmapRect.x 311 | //v += lightmapRect.y 312 | 313 | uvs = append(uvs, u, v) 314 | } 315 | 316 | return uvs 317 | } 318 | 319 | // lightmapSamplesFromFace create a lightmap rectangle for a face 320 | func lightmapSamplesFromFace(f *face.Face, samples *[]common.ColorRGBExponent32) []common.ColorRGBExponent32 { 321 | sampleSize := int32(unsafe.Sizeof((*samples)[0])) 322 | numLuxels := (f.LightmapTextureSizeInLuxels[0] + 1) * (f.LightmapTextureSizeInLuxels[1] + 1) 323 | firstSampleIdx := f.Lightofs / sampleSize 324 | 325 | return (*samples)[firstSampleIdx : firstSampleIdx+numLuxels] 326 | } 327 | -------------------------------------------------------------------------------- /loader/entity/classmap/classmap.go: -------------------------------------------------------------------------------- 1 | package loader 2 | 3 | import ( 4 | entity3 "github.com/galaco/lambda-core/entity" 5 | ) 6 | 7 | // entityClassMapper provides a reflection-like construct for creating 8 | // new entity objects of a known Classname. 9 | // The idea behind this was to remove the need to slow, difficult to read 10 | // reflection. Instead, it is up to defined entity types to provide a means 11 | // to create a new instance of its own type; this class being used to provide 12 | // a gateway to manage that mapping. 13 | // Note: this class is somewhat memory costly, as a single unmodified instance for every 14 | // mapped type is required for storage. Templated functions would probably solve this 15 | // problem better if they existed, and the plan was to avoid actual reflection 16 | // where possible. 17 | type entityClassMapper struct { 18 | entityMap map[string]entity3.IEntity 19 | } 20 | 21 | // find creates a new Entity of the specified 22 | // Classname. 23 | func (classMap *entityClassMapper) find(classname string) entity3.IEntity { 24 | if classMap.entityMap[classname] != nil { 25 | return classMap.entityMap[classname].New() 26 | } 27 | return nil 28 | } 29 | 30 | var classMap entityClassMapper 31 | 32 | // RegisterClass adds any type that implements a classname to 33 | // a saved mapping. From then on, new instances of that classname 34 | // can be created from just knowing the classname at runtime. 35 | func RegisterClass(entity entity3.IClassname) { 36 | if classMap.entityMap == nil { 37 | classMap.entityMap = map[string]entity3.IEntity{} 38 | } 39 | 40 | classMap.entityMap[entity.Classname()] = entity.(entity3.IEntity) 41 | } 42 | 43 | // New creates a new Entity of the specified 44 | // Classname. 45 | func New(classname string) entity3.IEntity { 46 | return classMap.find(classname) 47 | } 48 | -------------------------------------------------------------------------------- /loader/entity/create.go: -------------------------------------------------------------------------------- 1 | package entity 2 | 3 | import ( 4 | entity3 "github.com/galaco/lambda-core/entity" 5 | "github.com/galaco/lambda-core/loader/entity/classmap" 6 | "github.com/galaco/source-tools-common/entity" 7 | "github.com/galaco/vmf" 8 | "github.com/go-gl/mathgl/mgl32" 9 | "github.com/galaco/filesystem" 10 | "strings" 11 | ) 12 | 13 | // ParseEntities Parse Base block. 14 | // Vmf lib is actually capable of doing this; 15 | // contents are loaded into Vmf.Unclassified 16 | func ParseEntities(data string) (vmf.Vmf, error) { 17 | stringReader := strings.NewReader(data) 18 | reader := vmf.NewReader(stringReader) 19 | 20 | return reader.Read() 21 | } 22 | 23 | // CreateEntity creates a new entity with common properties 24 | // e.g. origin and angles 25 | func CreateEntity(ent *entity.Entity, fs *filesystem.FileSystem) entity3.IEntity { 26 | localEdict := loader.New(ent.ValueForKey("classname")) 27 | if localEdict == nil { 28 | localEdict = entity3.NewGenericEntity(ent) 29 | } else { 30 | localEdict.SetKeyValues(ent) 31 | } 32 | 33 | origin := ent.VectorForKey("origin") 34 | localEdict.Transform().Position = mgl32.Vec3{origin.X(), origin.Y(), origin.Z()} 35 | angles := ent.VectorForKey("angles") 36 | localEdict.Transform().Rotation = mgl32.Vec3{angles.X(), angles.Y(), angles.Z()} 37 | 38 | AssignProperties(localEdict, fs) 39 | 40 | return localEdict 41 | } 42 | 43 | // AssignProperties assigns type specific properties. 44 | // @TODO This is probably going to grow massively as more common types get implemented. 45 | // It should probably be refactored. 46 | func AssignProperties(ent entity3.IEntity, fs *filesystem.FileSystem) { 47 | if DoesEntityReferenceStudioModel(ent) { 48 | AssignStudioModelToEntity(ent, fs) 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /loader/entity/prop.go: -------------------------------------------------------------------------------- 1 | package entity 2 | 3 | import ( 4 | "github.com/galaco/lambda-core/entity" 5 | entity2 "github.com/galaco/lambda-core/game/entity" 6 | "github.com/galaco/lambda-core/loader/prop" 7 | "github.com/galaco/lambda-core/resource" 8 | "github.com/galaco/filesystem" 9 | "strings" 10 | ) 11 | 12 | // DoesEntityReferenceStudioModel tests if an entity is 13 | // tied to a model (normally prop_* classnames, but not exclusively) 14 | func DoesEntityReferenceStudioModel(ent entity.IEntity) bool { 15 | return strings.HasSuffix(ent.KeyValues().ValueForKey("model"), ".mdl") 16 | } 17 | 18 | // AssignStudioModelToEntity sets a renderable entity's model 19 | func AssignStudioModelToEntity(entity entity.IEntity, fs *filesystem.FileSystem) { 20 | modelName := entity.KeyValues().ValueForKey("model") 21 | if !resource.Manager().HasModel(modelName) { 22 | m, _ := prop.LoadProp(modelName, fs) 23 | entity.(entity2.IProp).SetModel(m) 24 | } else { 25 | entity.(entity2.IProp).SetModel(resource.Manager().Model(modelName)) 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /loader/keyvalues/keyvalues.go: -------------------------------------------------------------------------------- 1 | package keyvalues 2 | 3 | import ( 4 | "github.com/galaco/KeyValues" 5 | "github.com/galaco/filesystem" 6 | ) 7 | 8 | // ReadKeyValues loads a keyvalues file. 9 | // Its just a simple wrapper that combines the KeyValues library and 10 | // the filesystem module. 11 | func ReadKeyValues(filePath string, fs *filesystem.FileSystem) (*keyvalues.KeyValue, error) { 12 | stream, err := fs.GetFile(filePath) 13 | if err != nil { 14 | return nil, err 15 | } 16 | 17 | reader := keyvalues.NewReader(stream) 18 | kvs, err := reader.Read() 19 | 20 | return &kvs, err 21 | } 22 | -------------------------------------------------------------------------------- /loader/material/common.go: -------------------------------------------------------------------------------- 1 | package material 2 | 3 | import "io" 4 | 5 | type VirtualFilesystem interface { 6 | GetFile(string) (io.Reader, error) 7 | } 8 | -------------------------------------------------------------------------------- /loader/material/materials.go: -------------------------------------------------------------------------------- 1 | package material 2 | 3 | import ( 4 | "github.com/galaco/bsp/lumps" 5 | "github.com/galaco/bsp/primitives/texinfo" 6 | "github.com/galaco/lambda-core/lib/stringtable" 7 | "github.com/galaco/lambda-core/material" 8 | "github.com/galaco/lambda-core/resource" 9 | "github.com/galaco/lambda-core/texture" 10 | "github.com/galaco/filesystem" 11 | stringtableLib "github.com/galaco/stringtable" 12 | ) 13 | 14 | // LoadMaterials is the base bsp material loader function. 15 | // All bsp materials should be loaded by this function. 16 | // Note that this covers bsp referenced materials only, model & entity 17 | // materials are loaded mostly ad-hoc. 18 | func LoadMaterials(fs *filesystem.FileSystem, stringData *lumps.TexDataStringData, stringTable *lumps.TexDataStringTable, texInfos *[]texinfo.TexInfo) *stringtableLib.StringTable { 19 | materialStringTable := stringtable.NewTable(stringData, stringTable) 20 | LoadErrorMaterial() 21 | 22 | for _,path := range stringtable.SortUnique(materialStringTable, texInfos) { 23 | _,_ = LoadMaterialFromFilesystem(fs, path) 24 | } 25 | 26 | return materialStringTable 27 | } 28 | 29 | func LoadMaterialFromFilesystem(fs *filesystem.FileSystem, filePath string) (material.IMaterial, error) { 30 | if resource.Manager().HasMaterial(filePath) { 31 | return resource.Manager().Material(filePath), nil 32 | } 33 | props, err := LoadVmtFromFilesystem(fs, filePath) 34 | if err != nil { 35 | return resource.Manager().Material(resource.Manager().ErrorTextureName()), err 36 | } 37 | mat := material.NewMaterial(filePath) 38 | mat.BaseTextureName = props.BaseTexture 39 | mat.BumpMapName = props.Bumpmap 40 | 41 | if len(mat.BaseTextureName) > 0 { 42 | if resource.Manager().HasTexture(mat.BaseTextureName) { 43 | mat.Textures.Albedo = resource.Manager().Texture(mat.BaseTextureName) 44 | } else { 45 | albedo, err := LoadVtfFromFilesystem(fs, mat.BaseTextureName) 46 | if err != nil { 47 | mat.Textures.Albedo = resource.Manager().Texture(resource.Manager().ErrorTextureName()) 48 | return mat, err 49 | } 50 | resource.Manager().AddTexture(albedo) 51 | mat.Textures.Albedo = albedo 52 | } 53 | } else { 54 | mat.Textures.Albedo = resource.Manager().Texture(resource.Manager().ErrorTextureName()) 55 | } 56 | resource.Manager().AddMaterial(mat) 57 | 58 | return mat, nil 59 | } 60 | 61 | // LoadErrorMaterial ensures that the error material has been loaded 62 | func LoadErrorMaterial() { 63 | name := resource.Manager().ErrorTextureName() 64 | 65 | if resource.Manager().HasMaterial(name) { 66 | return 67 | } 68 | 69 | // Ensure that error texture is available 70 | resource.Manager().AddTexture(texture.NewError(name)) 71 | errorMat := material.NewMaterial(name) 72 | errorMat.Textures.Albedo = resource.Manager().Texture(name).(texture.ITexture) 73 | resource.Manager().AddMaterial(errorMat) 74 | } 75 | -------------------------------------------------------------------------------- /loader/material/vmt.go: -------------------------------------------------------------------------------- 1 | package material 2 | 3 | import ( 4 | "github.com/galaco/vmt" 5 | ) 6 | 7 | func LoadVmtFromFilesystem(fs VirtualFilesystem, filePath string) (*vmt.Properties, error) { 8 | mat,err := vmt.FromFilesystem(filePath, fs, vmt.NewProperties()) 9 | if err != nil { 10 | return nil, err 11 | } 12 | 13 | return mat.(*vmt.Properties), nil 14 | } -------------------------------------------------------------------------------- /loader/material/vtf.go: -------------------------------------------------------------------------------- 1 | package material 2 | 3 | import ( 4 | filesystem2 "github.com/galaco/lambda-core/filesystem" 5 | "github.com/galaco/lambda-core/resource" 6 | "github.com/galaco/lambda-core/texture" 7 | "github.com/galaco/vtf" 8 | "github.com/galaco/filesystem" 9 | "strings" 10 | ) 11 | 12 | // LoadVtfFromFilesystem 13 | func LoadVtfFromFilesystem(fs *filesystem.FileSystem, filePath string) (texture.ITexture, error) { 14 | if filePath == "" { 15 | return resource.Manager().Texture(resource.Manager().ErrorTextureName()).(texture.ITexture), nil 16 | } 17 | filePath = filesystem2.BasePathMaterial + filesystem.NormalisePath(filePath) 18 | if !strings.HasSuffix(filePath, filesystem2.ExtensionVtf) { 19 | filePath = filePath + filesystem2.ExtensionVtf 20 | } 21 | if resource.Manager().HasTexture(filePath) { 22 | return resource.Manager().Texture(filePath).(texture.ITexture), nil 23 | } 24 | mat, err := readVtf(filePath, fs) 25 | if err != nil { 26 | return resource.Manager().Texture(resource.Manager().ErrorTextureName()).(texture.ITexture), err 27 | } 28 | return mat, nil 29 | } 30 | 31 | // readVtf 32 | func readVtf(path string, fs *filesystem.FileSystem) (texture.ITexture, error) { 33 | ResourceManager := resource.Manager() 34 | stream, err := fs.GetFile(path) 35 | if err != nil { 36 | return nil, err 37 | } 38 | 39 | // Attempt to parse the vtf into color data we can use, 40 | // if this fails (it shouldn't) we can treat it like it was missing 41 | read, err := vtf.ReadFromStream(stream) 42 | if err != nil { 43 | return nil, err 44 | } 45 | // Store filesystem containing raw data in memory 46 | ResourceManager.AddTexture( 47 | texture.NewTexture2D( 48 | path, 49 | read, 50 | int(read.Header().Width), 51 | int(read.Header().Height))) 52 | 53 | // Finally generate the gpu buffer for the material 54 | return ResourceManager.Texture(path).(texture.ITexture), nil 55 | } 56 | -------------------------------------------------------------------------------- /loader/prop/prop.go: -------------------------------------------------------------------------------- 1 | package prop 2 | 3 | import ( 4 | filesystem2 "github.com/galaco/lambda-core/filesystem" 5 | studiomodellib "github.com/galaco/lambda-core/lib/studiomodel" 6 | material2 "github.com/galaco/lambda-core/loader/material" 7 | "github.com/galaco/lambda-core/material" 8 | "github.com/galaco/lambda-core/mesh" 9 | "github.com/galaco/lambda-core/model" 10 | "github.com/galaco/lambda-core/resource" 11 | "github.com/galaco/studiomodel" 12 | "github.com/galaco/studiomodel/mdl" 13 | "github.com/galaco/studiomodel/phy" 14 | "github.com/galaco/studiomodel/vtx" 15 | "github.com/galaco/studiomodel/vvd" 16 | "github.com/galaco/filesystem" 17 | "strings" 18 | ) 19 | 20 | // @TODO This is SUPER incomplete 21 | // right now it does the bare minimum, and many models seem to have 22 | // some corruption. 23 | 24 | // LoadProp loads a single prop/model of known filepath 25 | func LoadProp(path string, fs *filesystem.FileSystem) (*model.Model, error) { 26 | ResourceManager := resource.Manager() 27 | if ResourceManager.HasModel(path) { 28 | return ResourceManager.Model(path), nil 29 | } 30 | prop, err := loadProp(strings.Split(path, ".mdl")[0], fs) 31 | if prop != nil { 32 | m,err := modelFromStudioModel(path, prop, fs) 33 | if m != nil { 34 | ResourceManager.AddModel(m) 35 | } else { 36 | return ResourceManager.Model(ResourceManager.ErrorModelName()), err 37 | } 38 | } else { 39 | return ResourceManager.Model(ResourceManager.ErrorModelName()), err 40 | } 41 | 42 | return ResourceManager.Model(path), err 43 | } 44 | 45 | func loadProp(filePath string, fs *filesystem.FileSystem) (*studiomodel.StudioModel, error) { 46 | prop := studiomodel.NewStudioModel(filePath) 47 | 48 | // MDL 49 | f, err := fs.GetFile(filePath + ".mdl") 50 | if err != nil { 51 | return nil, err 52 | } 53 | mdlFile, err := mdl.ReadFromStream(f) 54 | if err != nil { 55 | return nil, err 56 | } 57 | prop.AddMdl(mdlFile) 58 | 59 | // VVD 60 | f, err = fs.GetFile(filePath + ".vvd") 61 | if err != nil { 62 | return nil, err 63 | } 64 | vvdFile, err := vvd.ReadFromStream(f) 65 | if err != nil { 66 | return nil, err 67 | } 68 | prop.AddVvd(vvdFile) 69 | 70 | // VTX 71 | f, err = fs.GetFile(filePath + ".dx90.vtx") 72 | if err != nil { 73 | return nil, err 74 | } 75 | vtxFile, err := vtx.ReadFromStream(f) 76 | 77 | if err != nil { 78 | return nil, err 79 | } 80 | prop.AddVtx(vtxFile) 81 | 82 | // PHY 83 | f, err = fs.GetFile(filePath + ".phy") 84 | if err != nil { 85 | return prop, err 86 | } 87 | 88 | phyFile, err := phy.ReadFromStream(f) 89 | if err != nil { 90 | return prop, err 91 | } 92 | prop.AddPhy(phyFile) 93 | 94 | return prop, nil 95 | } 96 | 97 | func modelFromStudioModel(filename string, studioModel *studiomodel.StudioModel, fs *filesystem.FileSystem) (*model.Model, error) { 98 | verts, normals, textureCoordinates, err := studiomodellib.VertexDataForModel(studioModel, 0) 99 | if err != nil { 100 | return nil, err 101 | } 102 | outModel := model.NewModel(filename) 103 | mats := materialsForStudioModel(studioModel.Mdl, fs) 104 | for i := 0; i < len(verts); i++ { //verts is a slice of slices, (ie vertex data per mesh) 105 | smMesh := mesh.NewMesh() 106 | smMesh.AddVertex(verts[i]...) 107 | smMesh.AddNormal(normals[i]...) 108 | smMesh.AddUV(textureCoordinates[i]...) 109 | //smMesh.Finish() 110 | 111 | //@TODO Map ALL materials to mesh data 112 | smMesh.SetMaterial(mats[0]) 113 | 114 | outModel.AddMesh(smMesh) 115 | } 116 | 117 | return outModel, nil 118 | } 119 | 120 | func materialsForStudioModel(mdlData *mdl.Mdl, fs *filesystem.FileSystem) []material.IMaterial { 121 | materials := make([]material.IMaterial, 0) 122 | for _, dir := range mdlData.TextureDirs { 123 | for _, name := range mdlData.TextureNames { 124 | path := strings.Replace(dir, "\\", "/", -1) + name + filesystem2.ExtensionVmt 125 | mat,_ := material2.LoadMaterialFromFilesystem(fs, path) 126 | materials = append(materials, mat) 127 | } 128 | } 129 | return materials 130 | } 131 | -------------------------------------------------------------------------------- /loader/props.go: -------------------------------------------------------------------------------- 1 | package loader 2 | 3 | import ( 4 | "github.com/galaco/bsp/primitives/game" 5 | "github.com/galaco/lambda-core/lib/util" 6 | "github.com/galaco/lambda-core/loader/prop" 7 | "github.com/galaco/lambda-core/model" 8 | "github.com/galaco/lambda-core/resource" 9 | "github.com/galaco/filesystem" 10 | "strings" 11 | ) 12 | 13 | // LoadStaticProps GetFile all staticprops referenced in a 14 | // bsp's game lump 15 | func LoadStaticProps(propLump *game.StaticPropLump, fs *filesystem.FileSystem) []model.StaticProp { 16 | ResourceManager := resource.Manager() 17 | errorProp, err := prop.LoadProp(ResourceManager.ErrorModelName(), fs) 18 | // If we have no error model, expect this to be fatal issue 19 | if errorProp == nil && err != nil { 20 | util.Logger().Panic(err) 21 | } 22 | 23 | propPaths := make([]string, 0) 24 | for _, propEntry := range propLump.PropLumps { 25 | propPaths = append(propPaths, propLump.DictLump.Name[propEntry.GetPropType()]) 26 | } 27 | 28 | propPaths = util.RemoveDuplicatesFromList(propPaths) 29 | util.Logger().Notice("Found %d staticprops", len(propPaths)) 30 | 31 | numLoaded := 0 32 | for _, path := range propPaths { 33 | if !strings.HasSuffix(path, ".mdl") { 34 | path += ".mdl" 35 | } 36 | _, err := prop.LoadProp(path, fs) 37 | if err != nil { 38 | continue 39 | } 40 | numLoaded++ 41 | } 42 | 43 | util.Logger().Notice("Loaded %d props, failed to load %d props", numLoaded, len(propPaths)-numLoaded) 44 | 45 | staticPropList := make([]model.StaticProp, 0) 46 | 47 | for _, propEntry := range propLump.PropLumps { 48 | modelName := propLump.DictLump.Name[propEntry.GetPropType()] 49 | m := ResourceManager.Model(modelName) 50 | if m != nil { 51 | staticPropList = append(staticPropList, *model.NewStaticProp(propEntry, &propLump.LeafLump, m)) 52 | continue 53 | } 54 | // Model missing, use error model 55 | m = ResourceManager.Model(ResourceManager.ErrorModelName()) 56 | staticPropList = append(staticPropList, *model.NewStaticProp(propEntry, &propLump.LeafLump, m)) 57 | } 58 | 59 | return staticPropList 60 | } 61 | -------------------------------------------------------------------------------- /loader/sky.go: -------------------------------------------------------------------------------- 1 | package loader 2 | 3 | import ( 4 | material2 "github.com/galaco/lambda-core/loader/material" 5 | "github.com/galaco/lambda-core/material" 6 | "github.com/galaco/lambda-core/mesh/primitive" 7 | "github.com/galaco/lambda-core/model" 8 | "github.com/galaco/lambda-core/texture" 9 | "github.com/galaco/filesystem" 10 | ) 11 | 12 | const skyboxRootDir = "skybox/" 13 | 14 | // LoadSky loads the skymaterial cubemap. 15 | // The materialname is normally obtained from the worldspawn entity 16 | func LoadSky(materialName string, fs *filesystem.FileSystem) *model.Model { 17 | sky := model.NewModel(materialName) 18 | 19 | mats := make([]material.IMaterial, 6) 20 | 21 | mats[0],_ = material2.LoadMaterialFromFilesystem(fs, skyboxRootDir+materialName+"up.vmt") 22 | mats[1],_ = material2.LoadMaterialFromFilesystem(fs, skyboxRootDir+materialName+"dn.vmt") 23 | mats[2],_ = material2.LoadMaterialFromFilesystem(fs, skyboxRootDir+materialName+"lf.vmt") 24 | mats[3],_ = material2.LoadMaterialFromFilesystem(fs, skyboxRootDir+materialName+"rt.vmt") 25 | mats[4],_ = material2.LoadMaterialFromFilesystem(fs, skyboxRootDir+materialName+"ft.vmt") 26 | mats[5],_ = material2.LoadMaterialFromFilesystem(fs, skyboxRootDir+materialName+"bk.vmt") 27 | 28 | texs := make([]texture.ITexture, 6) 29 | for i := 0; i < 6; i++ { 30 | texs[i] = mats[i].(*material.Material).Textures.Albedo 31 | } 32 | 33 | sky.AddMesh(primitive.NewCube()) 34 | 35 | sky.Meshes()[0].SetMaterial(texture.NewCubemap(texs)) 36 | 37 | return sky 38 | } 39 | -------------------------------------------------------------------------------- /loader/vgui/vgui.go: -------------------------------------------------------------------------------- 1 | package vgui 2 | 3 | import ( 4 | "errors" 5 | "fmt" 6 | keyvalues "github.com/galaco/KeyValues" 7 | "github.com/galaco/lambda-core/vgui" 8 | "github.com/galaco/filesystem" 9 | "log" 10 | ) 11 | 12 | // LoadVGUI 13 | func LoadVGUI(fs *filesystem.FileSystem, resourceName string) (*vgui.Panel, error) { 14 | stream, err := fs.GetFile(fmt.Sprintf("/resource/%s.res", resourceName)) 15 | if err != nil { 16 | return nil, err 17 | } 18 | kvReader := keyvalues.NewReader(stream) 19 | kv, err := kvReader.Read() 20 | if err != nil { 21 | return nil, err 22 | } 23 | log.Println(kv.Type()) 24 | if !kv.HasChildren() { 25 | return nil, errors.New("empty vgui resource keyvalues") 26 | } 27 | 28 | children, err := kv.Children() 29 | if err != nil { 30 | return nil, err 31 | } 32 | panel := recursiveBuildVGUITree(children[0], nil) 33 | 34 | return panel, nil 35 | } 36 | 37 | func recursiveBuildVGUITree(node *keyvalues.KeyValue, parent *vgui.Panel) *vgui.Panel { 38 | if node.HasChildren() { 39 | children, _ := node.Children() 40 | var isPanel bool 41 | // If a child also has children, then it should be a panel 42 | for _, c := range children { 43 | if c.HasChildren() { 44 | isPanel = true 45 | } 46 | } 47 | 48 | if isPanel { 49 | var p *vgui.Panel 50 | if parent == nil { 51 | // @TODO this needs proper construction rules 52 | parent = &vgui.Panel{} 53 | p = parent 54 | } else { 55 | p = parent.NewChildPanel(0, 0, 640, 480, true) 56 | } 57 | 58 | for _, c := range children { 59 | childPanel := recursiveBuildVGUITree(c, p) 60 | 61 | if childPanel != nil { 62 | p.AddChild(childPanel) 63 | } 64 | } 65 | } else { 66 | for _, c := range children { 67 | if c.Key() == "label" { 68 | if text, err := c.AsString(); err == nil { 69 | parent.AddElement(vgui.NewButton(text)) 70 | } 71 | } 72 | } 73 | return nil 74 | } 75 | } 76 | return parent 77 | } 78 | -------------------------------------------------------------------------------- /material/imaterial.go: -------------------------------------------------------------------------------- 1 | package material 2 | 3 | type IMaterial interface { 4 | //Bind() 5 | Width() int 6 | Height() int 7 | FilePath() string 8 | } 9 | -------------------------------------------------------------------------------- /material/material.go: -------------------------------------------------------------------------------- 1 | package material 2 | 3 | import "github.com/galaco/lambda-core/texture" 4 | 5 | // Material 6 | type Material struct { 7 | filePath string 8 | // ShaderName 9 | ShaderName string 10 | // Textures 11 | Textures struct { 12 | // Albedo 13 | Albedo texture.ITexture 14 | // Normal 15 | Normal texture.ITexture 16 | } 17 | // BaseTextureName 18 | BaseTextureName string 19 | // BumpMapName 20 | BumpMapName string 21 | // Properties 22 | Properties struct { 23 | } 24 | } 25 | 26 | // Width returns this materials width. Albedo is used to 27 | // determine material width where possible 28 | func (mat *Material) Width() int { 29 | return mat.Textures.Albedo.Width() 30 | } 31 | 32 | // Height returns this materials height. Albedo is used to 33 | // determine material height where possible 34 | func (mat *Material) Height() int { 35 | return mat.Textures.Albedo.Height() 36 | } 37 | 38 | // FilePath returns this materials location in whatever 39 | // filesystem it was found 40 | func (mat *Material) FilePath() string { 41 | return mat.filePath 42 | } 43 | 44 | func NewMaterial(filePath string) *Material { 45 | return &Material{ 46 | filePath: filePath, 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /material/material_test.go: -------------------------------------------------------------------------------- 1 | package material 2 | 3 | import ( 4 | "github.com/galaco/lambda-core/texture" 5 | "testing" 6 | ) 7 | 8 | func TestMaterial_FilePath(t *testing.T) { 9 | sut := Material{ 10 | filePath: "foo/bar.vmt", 11 | } 12 | 13 | if sut.FilePath() != "foo/bar.vmt" { 14 | t.Errorf("incorrect filepath returned. Expected %s, but received: %s", "foo/bar.vmt", sut.FilePath()) 15 | } 16 | } 17 | 18 | func TestMaterial_Height(t *testing.T) { 19 | sut := Material{ 20 | filePath: "foo/bar.vmt", 21 | } 22 | sut.Textures.Albedo = texture.NewError("error.vtf") 23 | 24 | if sut.Height() != sut.Textures.Albedo.Height() { 25 | t.Error("material height doesnt match basetextures height") 26 | } 27 | } 28 | 29 | func TestMaterial_Width(t *testing.T) { 30 | sut := Material{ 31 | filePath: "foo/bar.vmt", 32 | } 33 | sut.Textures.Albedo = texture.NewError("error.vtf") 34 | 35 | if sut.Width() != sut.Textures.Albedo.Width() { 36 | t.Error("material width doesnt match basetextures width") 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /mesh/face.go: -------------------------------------------------------------------------------- 1 | package mesh 2 | 3 | import ( 4 | "github.com/galaco/lambda-core/material" 5 | "github.com/galaco/lambda-core/texture" 6 | ) 7 | 8 | // Face 9 | type Face struct { 10 | offset int32 11 | length int32 12 | material material.IMaterial 13 | lightmap *texture.Lightmap 14 | } 15 | 16 | // Offset 17 | func (face *Face) Offset() int32 { 18 | return face.offset 19 | } 20 | 21 | // Length 22 | func (face *Face) Length() int32 { 23 | return face.length 24 | } 25 | 26 | // IsLightmapped 27 | func (face *Face) IsLightmapped() bool { 28 | return face.Lightmap() != nil 29 | } 30 | 31 | // AddMaterial 32 | func (face *Face) AddMaterial(mat material.IMaterial) { 33 | face.material = mat 34 | } 35 | 36 | // AddLightmap 37 | func (face *Face) AddLightmap(lightmap *texture.Lightmap) { 38 | face.lightmap = lightmap 39 | } 40 | 41 | // Material 42 | func (face *Face) Material() material.IMaterial { 43 | return face.material 44 | } 45 | 46 | // Lightmap 47 | func (face *Face) Lightmap() *texture.Lightmap { 48 | return face.lightmap 49 | } 50 | 51 | // NewFace 52 | func NewFace(offset int32, length int32, mat texture.ITexture, lightmap *texture.Lightmap) Face { 53 | return Face{ 54 | offset: offset, 55 | length: length, 56 | material: mat, 57 | lightmap: lightmap, 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /mesh/face_test.go: -------------------------------------------------------------------------------- 1 | package mesh 2 | 3 | import ( 4 | "github.com/galaco/lambda-core/material" 5 | "github.com/galaco/lambda-core/texture" 6 | "reflect" 7 | "testing" 8 | ) 9 | 10 | func TestFace_IsLightmapped(t *testing.T) { 11 | sut := Face{} 12 | sut.AddLightmap(&texture.Lightmap{}) 13 | 14 | if sut.IsLightmapped() != true { 15 | t.Error("face has lightmap, but isn't marked as being lightmapped") 16 | } 17 | } 18 | 19 | func TestFace_Length(t *testing.T) { 20 | sut := NewFace(32, 64, nil, nil) 21 | 22 | if sut.Length() != 64 { 23 | t.Error("unexpected length for face") 24 | } 25 | } 26 | 27 | func TestFace_AddMaterial(t *testing.T) { 28 | sut := Face{} 29 | expected := material.NewMaterial("foo.vmt") 30 | sut.AddMaterial(expected) 31 | 32 | if expected != sut.Material() { 33 | t.Error("unexpected material applied to face") 34 | } 35 | } 36 | 37 | func TestFace_AddLightmap(t *testing.T) { 38 | sut := Face{} 39 | expected := &texture.Lightmap{} 40 | sut.AddLightmap(expected) 41 | 42 | if expected != sut.Lightmap() { 43 | t.Error("unexpected lightmap applied to face") 44 | } 45 | } 46 | 47 | func TestFace_Lightmap(t *testing.T) { 48 | sut := Face{} 49 | expected := &texture.Lightmap{} 50 | sut.AddLightmap(expected) 51 | 52 | if expected != sut.Lightmap() { 53 | t.Error("unexpected lightmap applied to face") 54 | } 55 | } 56 | 57 | func TestFace_Material(t *testing.T) { 58 | sut := Face{} 59 | expected := material.NewMaterial("foo.vmt") 60 | sut.AddMaterial(expected) 61 | 62 | if expected != sut.Material() { 63 | t.Error("unexpected material applied to face") 64 | } 65 | } 66 | 67 | func TestFace_Offset(t *testing.T) { 68 | sut := NewFace(32, 64, nil, nil) 69 | 70 | if sut.Offset() != 32 { 71 | t.Error("unexpected offset for face") 72 | } 73 | } 74 | 75 | func TestNewFace(t *testing.T) { 76 | sut := NewFace(32, 64, nil, nil) 77 | if reflect.TypeOf(sut) != reflect.TypeOf(Face{}) { 78 | t.Errorf("unexpceted type returned. Expected %s, but received: %s", reflect.TypeOf(Face{}), reflect.TypeOf(sut)) 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /mesh/imesh.go: -------------------------------------------------------------------------------- 1 | package mesh 2 | 3 | import ( 4 | "github.com/galaco/lambda-core/material" 5 | "github.com/galaco/lambda-core/texture" 6 | ) 7 | 8 | // IMesh Generic Mesh interface 9 | // Most renderable objects should implement this, but there 10 | // are probably many custom cases that may not 11 | type IMesh interface { 12 | // AddVertex 13 | AddVertex(...float32) 14 | // AddNormal 15 | AddNormal(...float32) 16 | // AddUV 17 | AddUV(...float32) 18 | // AddLightmapCoordinate 19 | AddLightmapCoordinate(...float32) 20 | // GenerateTangents 21 | GenerateTangents() 22 | 23 | // Vertices 24 | Vertices() []float32 25 | // Normals 26 | Normals() []float32 27 | // UVs 28 | UVs() []float32 29 | // Tangents 30 | Tangents() []float32 31 | // LightmapCoordinates 32 | LightmapCoordinates() []float32 33 | 34 | // Material 35 | Material() material.IMaterial 36 | // SetMaterial 37 | SetMaterial(material.IMaterial) 38 | // Lightmap 39 | Lightmap() texture.ITexture 40 | // SetLightmap 41 | SetLightmap(texture.ITexture) 42 | } 43 | -------------------------------------------------------------------------------- /mesh/mesh.go: -------------------------------------------------------------------------------- 1 | package mesh 2 | 3 | import ( 4 | "github.com/galaco/lambda-core/material" 5 | "github.com/galaco/lambda-core/mesh/util" 6 | "github.com/galaco/lambda-core/texture" 7 | ) 8 | 9 | // Mesh 10 | type Mesh struct { 11 | vertices []float32 12 | normals []float32 13 | uvs []float32 14 | tangents []float32 15 | lightmapCoordinates []float32 16 | 17 | material material.IMaterial 18 | lightmap texture.ITexture 19 | } 20 | 21 | // AddVertex 22 | func (mesh *Mesh) AddVertex(vertex ...float32) { 23 | mesh.vertices = append(mesh.vertices, vertex...) 24 | } 25 | 26 | // AddNormal 27 | func (mesh *Mesh) AddNormal(normal ...float32) { 28 | mesh.normals = append(mesh.normals, normal...) 29 | } 30 | 31 | // AddUV 32 | func (mesh *Mesh) AddUV(uv ...float32) { 33 | mesh.uvs = append(mesh.uvs, uv...) 34 | } 35 | 36 | // AddTangent 37 | func (mesh *Mesh) AddTangent(tangent ...float32) { 38 | mesh.tangents = append(mesh.tangents, tangent...) 39 | } 40 | 41 | // AddLightmapCoordinate 42 | func (mesh *Mesh) AddLightmapCoordinate(uv ...float32) { 43 | mesh.lightmapCoordinates = append(mesh.lightmapCoordinates, uv...) 44 | } 45 | 46 | // Vertices 47 | func (mesh *Mesh) Vertices() []float32 { 48 | return mesh.vertices 49 | } 50 | 51 | // Normals 52 | func (mesh *Mesh) Normals() []float32 { 53 | return mesh.normals 54 | } 55 | 56 | // UVs 57 | func (mesh *Mesh) UVs() []float32 { 58 | return mesh.uvs 59 | } 60 | 61 | // Tangents 62 | func (mesh *Mesh) Tangents() []float32 { 63 | return mesh.tangents 64 | } 65 | 66 | // LightmapCoordinates 67 | func (mesh *Mesh) LightmapCoordinates() []float32 { 68 | // use standard uvs if there is no lightmap. Not ideal, 69 | // but there MUST be UVs, but they'll be ignored anyway if there is no 70 | // lightmap 71 | if len(mesh.lightmapCoordinates) == 0 { 72 | return mesh.UVs() 73 | } 74 | return mesh.lightmapCoordinates 75 | } 76 | 77 | // Material 78 | func (mesh *Mesh) Material() material.IMaterial { 79 | return mesh.material 80 | } 81 | 82 | // SetMaterial 83 | func (mesh *Mesh) SetMaterial(mat material.IMaterial) { 84 | mesh.material = mat 85 | } 86 | 87 | // Lightmap 88 | func (mesh *Mesh) Lightmap() texture.ITexture { 89 | return mesh.lightmap 90 | } 91 | 92 | //SetLightmap 93 | func (mesh *Mesh) SetLightmap(mat texture.ITexture) { 94 | mesh.lightmap = mat 95 | } 96 | 97 | // GenerateTangents 98 | func (mesh *Mesh) GenerateTangents() { 99 | mesh.tangents = util.GenerateTangents(mesh.Vertices(), mesh.Normals(), mesh.UVs()) 100 | } 101 | 102 | // NewMesh 103 | func NewMesh() *Mesh { 104 | return &Mesh{} 105 | } 106 | -------------------------------------------------------------------------------- /mesh/mesh_test.go: -------------------------------------------------------------------------------- 1 | package mesh 2 | 3 | import ( 4 | "github.com/galaco/lambda-core/material" 5 | "github.com/galaco/lambda-core/texture" 6 | "reflect" 7 | "testing" 8 | ) 9 | 10 | func TestNewMesh(t *testing.T) { 11 | if reflect.TypeOf(NewMesh()) != reflect.TypeOf(&Mesh{}) { 12 | t.Errorf("unexpected type returned for NewMesh. Expected: %s, but received: %s", reflect.TypeOf(&Mesh{}), reflect.TypeOf(NewMesh())) 13 | } 14 | } 15 | 16 | func TestMesh_AddLightmapCoordinate(t *testing.T) { 17 | sut := Mesh{} 18 | expected := []float32{ 19 | 1, 2, 3, 4, 20 | } 21 | sut.AddLightmapCoordinate(expected...) 22 | 23 | for i := 0; i < len(expected); i++ { 24 | if sut.LightmapCoordinates()[i] != expected[i] { 25 | t.Error("unexpected lightmap coordinate") 26 | } 27 | } 28 | } 29 | 30 | func TestMesh_AddNormal(t *testing.T) { 31 | sut := Mesh{} 32 | expected := []float32{ 33 | 1, 2, 3, 4, 34 | } 35 | sut.AddNormal(expected...) 36 | 37 | for i := 0; i < len(expected); i++ { 38 | if sut.Normals()[i] != expected[i] { 39 | t.Error("unexpected normal") 40 | } 41 | } 42 | } 43 | 44 | func TestMesh_AddTextureCoordinate(t *testing.T) { 45 | sut := Mesh{} 46 | expected := []float32{ 47 | 1, 2, 3, 4, 48 | } 49 | sut.AddUV(expected...) 50 | 51 | for i := 0; i < len(expected); i++ { 52 | if sut.UVs()[i] != expected[i] { 53 | t.Error("unexpected texture coordinate") 54 | } 55 | } 56 | } 57 | 58 | func TestMesh_AddVertex(t *testing.T) { 59 | sut := Mesh{} 60 | expected := []float32{ 61 | 1, 2, 3, 4, 62 | } 63 | sut.AddVertex(expected...) 64 | 65 | for i := 0; i < len(expected); i++ { 66 | if sut.Vertices()[i] != expected[i] { 67 | t.Error("unexpected vertex") 68 | } 69 | } 70 | } 71 | 72 | func TestMesh_Lightmap(t *testing.T) { 73 | sut := Mesh{} 74 | expected := &texture.Lightmap{} 75 | sut.SetLightmap(expected) 76 | 77 | if expected != sut.Lightmap() { 78 | t.Error("unexpected lightmap applied to mesh") 79 | } 80 | } 81 | 82 | func TestMesh_Material(t *testing.T) { 83 | sut := Mesh{} 84 | expected := material.NewMaterial("foo.vmt") 85 | sut.SetMaterial(expected) 86 | 87 | if expected != sut.Material() { 88 | t.Error("unexpected material applied to mesh") 89 | } 90 | } 91 | 92 | func TestMesh_LightmapCoordinates(t *testing.T) { 93 | sut := Mesh{} 94 | expected := []float32{ 95 | 1, 2, 3, 4, 96 | } 97 | sut.AddLightmapCoordinate(expected...) 98 | 99 | for i := 0; i < len(expected); i++ { 100 | if sut.LightmapCoordinates()[i] != expected[i] { 101 | t.Error("unexpected lightmap coordinate") 102 | } 103 | } 104 | } 105 | 106 | func TestMesh_Normals(t *testing.T) { 107 | sut := Mesh{} 108 | expected := []float32{ 109 | 1, 2, 3, 4, 110 | } 111 | sut.AddNormal(expected...) 112 | 113 | for i := 0; i < len(expected); i++ { 114 | if sut.Normals()[i] != expected[i] { 115 | t.Error("unexpected normal") 116 | } 117 | } 118 | } 119 | 120 | func TestMesh_SetLightmap(t *testing.T) { 121 | sut := Mesh{} 122 | expected := &texture.Lightmap{} 123 | sut.SetLightmap(expected) 124 | 125 | if expected != sut.Lightmap() { 126 | t.Error("unexpected lightmap applied to mesh") 127 | } 128 | } 129 | 130 | func TestMesh_SetMaterial(t *testing.T) { 131 | sut := Mesh{} 132 | expected := material.NewMaterial("foo.vmt") 133 | sut.SetMaterial(expected) 134 | 135 | if expected != sut.Material() { 136 | t.Error("unexpected material applied to mesh") 137 | } 138 | } 139 | 140 | func TestMesh_TextureCoordinates(t *testing.T) { 141 | sut := Mesh{} 142 | expected := []float32{ 143 | 1, 2, 3, 4, 144 | } 145 | sut.AddUV(expected...) 146 | 147 | for i := 0; i < len(expected); i++ { 148 | if sut.UVs()[i] != expected[i] { 149 | t.Error("unexpected texture coordinate") 150 | } 151 | } 152 | } 153 | 154 | func TestMesh_Vertices(t *testing.T) { 155 | sut := Mesh{} 156 | expected := []float32{ 157 | 1, 2, 3, 4, 158 | } 159 | sut.AddVertex(expected...) 160 | 161 | for i := 0; i < len(expected); i++ { 162 | if sut.Vertices()[i] != expected[i] { 163 | t.Error("unexpected vertex") 164 | } 165 | } 166 | } 167 | -------------------------------------------------------------------------------- /mesh/primitive/cube.go: -------------------------------------------------------------------------------- 1 | package primitive 2 | 3 | import ( 4 | "github.com/galaco/lambda-core/mesh" 5 | ) 6 | 7 | var cubeVerts = []float32{ 8 | -1.0, -1.0, -1.0, 9 | 1.0, -1.0, -1.0, 10 | -1.0, -1.0, 1.0, 11 | 1.0, -1.0, -1.0, 12 | 1.0, -1.0, 1.0, 13 | -1.0, -1.0, 1.0, 14 | -1.0, 1.0, -1.0, 15 | -1.0, 1.0, 1.0, 16 | 1.0, 1.0, -1.0, 17 | 1.0, 1.0, -1.0, 18 | -1.0, 1.0, 1.0, 19 | 1.0, 1.0, 1.0, 20 | -1.0, -1.0, 1.0, 21 | 1.0, -1.0, 1.0, 22 | -1.0, 1.0, 1.0, 23 | 1.0, -1.0, 1.0, 24 | 1.0, 1.0, 1.0, 25 | -1.0, 1.0, 1.0, 26 | -1.0, -1.0, -1.0, 27 | -1.0, 1.0, -1.0, 28 | 1.0, -1.0, -1.0, 29 | 1.0, -1.0, -1.0, 30 | -1.0, 1.0, -1.0, 31 | 1.0, 1.0, -1.0, 32 | -1.0, -1.0, 1.0, 33 | -1.0, 1.0, -1.0, 34 | -1.0, -1.0, -1.0, 35 | -1.0, -1.0, 1.0, 36 | -1.0, 1.0, 1.0, 37 | -1.0, 1.0, -1.0, 38 | 1.0, -1.0, 1.0, 39 | 1.0, -1.0, -1.0, 40 | 1.0, 1.0, -1.0, 41 | 1.0, -1.0, 1.0, 42 | 1.0, 1.0, -1.0, 43 | 1.0, 1.0, 1.0, 44 | } 45 | 46 | var cubeNormals = cubeVerts 47 | 48 | var cubeUVs = []float32{ 49 | 0, 0, 50 | 1, 0, 51 | 0, 1, 52 | 1, 0, 53 | 0, 1, 54 | 1, 1, 55 | 56 | 0, 0, 57 | 1, 0, 58 | 0, 1, 59 | 1, 0, 60 | 0, 1, 61 | 1, 1, 62 | 63 | 0, 0, 64 | 1, 0, 65 | 0, 1, 66 | 1, 0, 67 | 0, 1, 68 | 1, 1, 69 | 70 | 0, 0, 71 | 1, 0, 72 | 0, 1, 73 | 1, 0, 74 | 0, 1, 75 | 1, 1, 76 | 0, 0, 77 | 1, 0, 78 | 0, 1, 79 | 1, 0, 80 | 0, 1, 81 | 1, 1, 82 | 0, 0, 83 | 1, 0, 84 | 0, 1, 85 | 1, 0, 86 | 0, 1, 87 | 1, 1, 88 | } 89 | 90 | // Cube 91 | type Cube struct { 92 | mesh.Mesh 93 | } 94 | 95 | // NewCube 96 | func NewCube() *Cube { 97 | c := &Cube{ 98 | Mesh: *mesh.NewMesh(), 99 | } 100 | c.AddVertex(cubeVerts...) 101 | c.AddNormal(cubeNormals...) 102 | c.AddUV(cubeUVs...) 103 | //c.Finish() 104 | 105 | return c 106 | } 107 | -------------------------------------------------------------------------------- /mesh/primitive/cube_test.go: -------------------------------------------------------------------------------- 1 | package primitive 2 | 3 | import ( 4 | "reflect" 5 | "testing" 6 | ) 7 | 8 | func TestNewCube(t *testing.T) { 9 | sut := NewCube() 10 | if reflect.TypeOf(sut) != reflect.TypeOf(&Cube{}) { 11 | t.Error("unexpected value returned when creating Cube") 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /mesh/util/tangents.go: -------------------------------------------------------------------------------- 1 | package util 2 | 3 | import "github.com/go-gl/mathgl/mgl32" 4 | 5 | // GenerateTangents generates tangents for vertex data 6 | func GenerateTangents(points []float32, normals []float32, texCoords []float32) (tangents []float32) { 7 | //const vector & points, 8 | //const vector & normals, 9 | //const vector & faces, 10 | //const vector & texCoords, 11 | // vector & tangents) 12 | //{ 13 | //vector tan1Accum; 14 | tan1Accum := make([]float32, len(points)) 15 | //vector tan2Accum; 16 | tan2Accum := make([]float32, len(points)) 17 | tangents = make([]float32, len(points)+(len(points)/3)) 18 | 19 | //for( uint i = 0; i < points.size(); i++ ) { 20 | //tan1Accum.push_back(vec3(0.0f)); 21 | //tan2Accum.push_back(vec3(0.0f)); 22 | //tangents.push_back(vec4(0.0f)); 23 | //} 24 | 25 | // Compute the tangent vector 26 | for i := uint(0); i < uint(len(points))-9; i += 9 { 27 | rootIdx := i / 3 28 | p1 := mgl32.Vec3{points[i], points[i+1], points[i+2]} 29 | p2 := mgl32.Vec3{points[i+3], points[i+4], points[i+5]} 30 | p3 := mgl32.Vec3{points[i+6], points[i+7], points[i+8]} 31 | //const vec3 &p1 = points[faces[i]]; 32 | //const vec3 &p2 = points[faces[i+1]]; 33 | //const vec3 &p3 = points[faces[i+2]]; 34 | 35 | uvIdx := rootIdx * 2 36 | tc1 := mgl32.Vec2{texCoords[uvIdx], texCoords[uvIdx+1]} 37 | tc2 := mgl32.Vec2{texCoords[uvIdx+2], texCoords[uvIdx+3]} 38 | tc3 := mgl32.Vec2{texCoords[uvIdx+4], texCoords[uvIdx+5]} 39 | //const vec2 &tc1 = texCoords[faces[i]]; 40 | //const vec2 &tc2 = texCoords[faces[i+1]]; 41 | //const vec2 &tc3 = texCoords[faces[i+2]]; 42 | 43 | q1 := p2.Sub(p1) 44 | q2 := p3.Sub(p1) 45 | //vec3 q1 = p2 - p1; 46 | //vec3 q2 = p3 - p1; 47 | s1 := tc2.X() - tc1.X() 48 | s2 := tc3.X() - tc1.X() 49 | t1 := tc2.Y() - tc1.Y() 50 | t2 := tc3.Y() - tc1.Y() 51 | //float s1 = tc2.x - tc1.x, s2 = tc3.x - tc1.x; 52 | //float t1 = tc2.y - tc1.y, t2 = tc3.y - tc1.y; 53 | r := 1.0 / (s1*t2 - s2*t1) 54 | //float r = 1.0f / (s1 * t2 - s2 * t1); 55 | tan1 := mgl32.Vec3{ 56 | (t2*q1.X() - t1*q2.X()) * r, 57 | (t2*q1.Y() - t1*q2.Y()) * r, 58 | (t2*q1.Z() - t1*q2.Z()) * r, 59 | } 60 | //vec3 tan1( (t2*q1.x - t1*q2.x) * r, 61 | //(t2*q1.y - t1*q2.y) * r, 62 | //(t2*q1.z - t1*q2.z) * r); 63 | 64 | tan2 := mgl32.Vec3{ 65 | (s1*q2.X() - s2*q1.X()) * r, 66 | (s1*q2.Y() - s2*q1.Y()) * r, 67 | (s1*q2.Z() - s2*q1.Z()) * r, 68 | } 69 | //vec3 tan2( (s1*q2.x - s2*q1.x) * r, 70 | //(s1*q2.y - s2*q1.y) * r, 71 | //(s1*q2.z - s2*q1.z) * r); 72 | tan1Accum[i] += tan1.X() 73 | tan1Accum[i+1] += tan1.Y() 74 | tan1Accum[i+2] += tan1.Z() 75 | tan2Accum[i] += tan2.X() 76 | tan2Accum[i+1] += tan2.Y() 77 | tan2Accum[i+2] += tan2.Z() 78 | //tan1Accum[faces[i]] += tan1; 79 | //tan1Accum[faces[i+1]] += tan1; 80 | //tan1Accum[faces[i+2]] += tan1; 81 | //tan2Accum[faces[i]] += tan2; 82 | //tan2Accum[faces[i+1]] += tan2; 83 | //tan2Accum[faces[i+2]] += tan2; 84 | } 85 | 86 | for i := uint(0); i < uint(len(points))-2; i++ { 87 | n := mgl32.Vec3{ 88 | normals[i], 89 | normals[i+1], 90 | normals[i+2], 91 | } 92 | t1 := mgl32.Vec3{ 93 | tan1Accum[i], 94 | tan1Accum[i+1], 95 | tan1Accum[i+2], 96 | } 97 | t2 := mgl32.Vec3{ 98 | tan2Accum[i], 99 | tan2Accum[i+1], 100 | tan2Accum[i+2], 101 | } 102 | //const vec3 &n = normals[i]; 103 | //vec3 &t1 = tan1Accum[i]; 104 | //vec3 &t2 = tan2Accum[i]; 105 | 106 | // Gram-Schmidt orthogonalize 107 | //tangents[i] = vec4(glm::normalize( t1 - (glm::dot(n,t1) * n) ), 0.0f); 108 | res := t1.Sub(n.Mul(n.Dot(t1))).Normalize() 109 | tangents[i] = res.X() 110 | tangents[i+1] = res.Y() 111 | tangents[i+2] = res.Z() 112 | // Store handedness in w 113 | w := float32(1.0) 114 | if n.Cross(t1).Dot(t2) < 0 { 115 | w = -1.0 116 | } 117 | tangents[i+3] = w 118 | //tangents[i] = (glm::dot( glm::cross(n,t1), t2 ) < 0.0f) ? -1.0f : 1.0f; 119 | } 120 | 121 | //tan1Accum.clear(); 122 | //tan2Accum.clear(); 123 | 124 | return tangents 125 | } 126 | -------------------------------------------------------------------------------- /model/bsp.go: -------------------------------------------------------------------------------- 1 | package model 2 | 3 | import "github.com/galaco/lambda-core/mesh" 4 | 5 | // Bsp is a specialised model that represents an entire bsp map 6 | // It is represented by a single mesh and a series of visiblity structures 7 | // that dictate what can and can't be seen from a given point 8 | type Bsp struct { 9 | internalMesh mesh.IMesh 10 | 11 | defaultClusterLeaf ClusterLeaf 12 | clusterLeafs []ClusterLeaf 13 | visibleClusterLeafs []*ClusterLeaf 14 | } 15 | 16 | // Mesh returns Bsp Mesh 17 | func (bsp *Bsp) Mesh() mesh.IMesh { 18 | return bsp.internalMesh 19 | } 20 | 21 | // ClusterLeafs returns all ClusterLeafs 22 | func (bsp *Bsp) ClusterLeafs() []ClusterLeaf { 23 | return bsp.clusterLeafs 24 | } 25 | 26 | // VisibleClusterLeafs returns clusterleafs that are known to 27 | // be visible. This is not calculated here, only stored for faster reference 28 | func (bsp *Bsp) VisibleClusterLeafs() []*ClusterLeaf { 29 | return bsp.visibleClusterLeafs 30 | } 31 | 32 | // SetClusterLeafs set the computed cluster leafs for a Bsp 33 | func (bsp *Bsp) SetClusterLeafs(clusterLeafs []ClusterLeaf) { 34 | bsp.clusterLeafs = clusterLeafs 35 | } 36 | 37 | // SetVisibleClusters update the visible ClusterLeafs 38 | func (bsp *Bsp) SetVisibleClusters(clusterLeafs []*ClusterLeaf) { 39 | bsp.visibleClusterLeafs = clusterLeafs 40 | } 41 | 42 | // SetDefaultCluster 43 | func (bsp *Bsp) SetDefaultCluster(dispFaces ClusterLeaf) { 44 | bsp.defaultClusterLeaf = dispFaces 45 | } 46 | 47 | // DefaultCluster 48 | func (bsp *Bsp) DefaultCluster() *ClusterLeaf { 49 | return &bsp.defaultClusterLeaf 50 | } 51 | 52 | // NewBsp returns a new bsp 53 | func NewBsp(refMesh *mesh.Mesh) *Bsp { 54 | return &Bsp{ 55 | internalMesh: refMesh, 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /model/imodel.go: -------------------------------------------------------------------------------- 1 | package model 2 | 3 | // IModel 4 | type IModel interface { 5 | // FilePath 6 | FilePath() string 7 | } 8 | -------------------------------------------------------------------------------- /model/leaf.go: -------------------------------------------------------------------------------- 1 | package model 2 | 3 | import ( 4 | "github.com/galaco/lambda-core/mesh" 5 | "github.com/go-gl/mathgl/mgl32" 6 | ) 7 | 8 | // ClusterLeaf represents a single cluster that contains the contents of 9 | // all the leafs that are contained within it 10 | type ClusterLeaf struct { 11 | Id int16 12 | Faces []mesh.Face 13 | StaticProps []*StaticProp 14 | DispFaces []int 15 | Mins, Maxs mgl32.Vec3 16 | Origin mgl32.Vec3 17 | } 18 | -------------------------------------------------------------------------------- /model/model.go: -------------------------------------------------------------------------------- 1 | package model 2 | 3 | import ( 4 | "github.com/galaco/lambda-core/mesh" 5 | ) 6 | 7 | // Model A collection of renderable primitives/submeshes 8 | type Model struct { 9 | meshes []mesh.IMesh 10 | fileName string 11 | } 12 | 13 | // AddMesh Add a new primitive 14 | func (model *Model) AddMesh(meshes ...mesh.IMesh) { 15 | model.meshes = append(model.meshes, meshes...) 16 | } 17 | 18 | // Meshes Get all primitives/submeshes 19 | func (model *Model) Meshes() []mesh.IMesh { 20 | return model.meshes 21 | } 22 | 23 | // Reset removes all meshes from this model 24 | func (model *Model) Reset() { 25 | model.meshes = []mesh.IMesh{} 26 | } 27 | 28 | // FilePath returns where is model was found on disk 29 | func (model *Model) FilePath() string { 30 | return model.fileName 31 | } 32 | 33 | //func (model *Model) Destroy() { 34 | // for _, m := range model.meshes { 35 | // m.Destroy() 36 | // } 37 | //} 38 | 39 | // NewModel returns a new Model 40 | func NewModel(filename string, meshes ...mesh.IMesh) *Model { 41 | return &Model{ 42 | fileName: filename, 43 | meshes: meshes, 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /model/staticprop.go: -------------------------------------------------------------------------------- 1 | package model 2 | 3 | import ( 4 | "github.com/galaco/bsp/primitives/game" 5 | "github.com/galaco/lambda-core/entity" 6 | ) 7 | 8 | // StaticProp is a somewhat specialised model 9 | // that implements a few core entity features (largely because 10 | // it is basically a renderable entity that cannot do anything or be reference) 11 | type StaticProp struct { 12 | entity.Base 13 | leafList []uint16 14 | model *Model 15 | fadeMinDistance float32 16 | fadeMaxDistance float32 17 | } 18 | 19 | // Model returns props model 20 | func (prop *StaticProp) Model() *Model { 21 | return prop.model 22 | } 23 | 24 | // LeafList returrns all leafs that this props is in 25 | func (prop *StaticProp) LeafList() []uint16 { 26 | return prop.leafList 27 | } 28 | 29 | func (prop *StaticProp) FadeMinDistance() float32 { 30 | return prop.fadeMinDistance 31 | } 32 | 33 | func (prop *StaticProp) FadeMaxDistance() float32 { 34 | return prop.fadeMaxDistance 35 | } 36 | 37 | // NewStaticProp returns new StaticProp 38 | func NewStaticProp(lumpProp game.IStaticPropDataLump, propLeafs *game.StaticPropLeafLump, renderable *Model) *StaticProp { 39 | prop := StaticProp{ 40 | model: renderable, 41 | } 42 | for i := uint16(0); i < lumpProp.GetLeafCount(); i++ { 43 | prop.leafList = append(prop.leafList, propLeafs.Leaf[lumpProp.GetFirstLeaf()+i]) 44 | } 45 | prop.Transform().Position = lumpProp.GetOrigin() 46 | prop.Transform().Rotation = lumpProp.GetAngles() 47 | prop.fadeMinDistance = lumpProp.GetFadeMinDist() 48 | prop.fadeMaxDistance = lumpProp.GetFadeMaxDist() 49 | 50 | return &prop 51 | } 52 | -------------------------------------------------------------------------------- /renovate.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": [ 3 | "config:base" 4 | ] 5 | } 6 | -------------------------------------------------------------------------------- /resource/iresource.go: -------------------------------------------------------------------------------- 1 | package resource 2 | 3 | // IResource Generic filesystem object. If it was loaded from a path, it should 4 | // implement this. 5 | type IResource interface { 6 | FilePath() string 7 | } 8 | -------------------------------------------------------------------------------- /resource/manager.go: -------------------------------------------------------------------------------- 1 | package resource 2 | 3 | import ( 4 | "github.com/galaco/lambda-core/event" 5 | "github.com/galaco/lambda-core/filesystem" 6 | "github.com/galaco/lambda-core/material" 7 | "github.com/galaco/lambda-core/model" 8 | "github.com/galaco/lambda-core/resource/message" 9 | "github.com/galaco/lambda-core/texture" 10 | "strings" 11 | "sync" 12 | ) 13 | 14 | // Very generic filesystem storage. 15 | // If the struct came from a filesystem, it should be obtainable from here 16 | type manager struct { 17 | errorModelName string 18 | errorTextureName string 19 | 20 | materials map[string]material.IMaterial 21 | materialReadMutex sync.Mutex 22 | textures map[string]texture.ITexture 23 | textureReadMutex sync.Mutex 24 | models map[string]*model.Model 25 | modelReadMutex sync.Mutex 26 | } 27 | 28 | // Add a new material 29 | func (m *manager) AddMaterial(file material.IMaterial) { 30 | if m.HasMaterial(file.FilePath()) { 31 | return 32 | } 33 | m.materialReadMutex.Lock() 34 | m.materials[strings.ToLower(file.FilePath())] = file 35 | m.materialReadMutex.Unlock() 36 | 37 | event.Manager().Dispatch(message.LoadedMaterial(file)) 38 | } 39 | 40 | // Add a new material 41 | func (m *manager) AddTexture(file texture.ITexture) { 42 | if m.HasTexture(file.FilePath()) { 43 | return 44 | } 45 | m.textureReadMutex.Lock() 46 | m.textures[strings.ToLower(file.FilePath())] = file 47 | m.textureReadMutex.Unlock() 48 | event.Manager().Dispatch(message.LoadedTexture(file)) 49 | } 50 | 51 | // Add a new model 52 | func (m *manager) AddModel(file *model.Model) { 53 | if m.HasModel(file.FilePath()) { 54 | return 55 | } 56 | m.modelReadMutex.Lock() 57 | m.models[strings.ToLower(file.FilePath())] = file 58 | m.modelReadMutex.Unlock() 59 | event.Manager().Dispatch(message.LoadedModel(file)) 60 | } 61 | 62 | // Get Find a specific filesystem 63 | func (m *manager) Material(filePath string) material.IMaterial { 64 | return m.materials[strings.ToLower(filePath)] 65 | } 66 | 67 | func (m *manager) Texture(filePath string) texture.ITexture { 68 | return m.textures[strings.ToLower(filePath)] 69 | } 70 | 71 | func (m *manager) Model(filePath string) *model.Model { 72 | return m.models[strings.ToLower(filePath)] 73 | } 74 | 75 | func (m *manager) Materials() map[string]material.IMaterial { 76 | return m.materials 77 | } 78 | 79 | func (m *manager) Textures() map[string]texture.ITexture { 80 | return m.textures 81 | } 82 | 83 | func (m *manager) Models() map[string]*model.Model { 84 | return m.models 85 | } 86 | 87 | // ErrorModelName Get error model name 88 | func (m *manager) ErrorModelName() string { 89 | return m.errorModelName 90 | } 91 | 92 | // SetErrorModelName Override the default error model. 93 | // Useful for when HL2 assets are not available (they include the engine 94 | // default model) 95 | func (m *manager) SetErrorModelName(name string) { 96 | m.errorModelName = name 97 | } 98 | 99 | // ErrorTextureName Get error texture name 100 | func (m *manager) ErrorTextureName() string { 101 | return m.errorTextureName 102 | } 103 | 104 | // SetErrorTextureName Override default error texture 105 | func (m *manager) SetErrorTextureName(name string) { 106 | m.errorTextureName = name 107 | } 108 | 109 | // Has the specified file been loaded 110 | func (m *manager) HasMaterial(filePath string) bool { 111 | m.materialReadMutex.Lock() 112 | if m.materials[strings.ToLower(filePath)] != nil { 113 | m.materialReadMutex.Unlock() 114 | return true 115 | } 116 | m.materialReadMutex.Unlock() 117 | return false 118 | } 119 | 120 | func (m *manager) HasTexture(filePath string) bool { 121 | m.textureReadMutex.Lock() 122 | if m.textures[strings.ToLower(filePath)] != nil { 123 | m.textureReadMutex.Unlock() 124 | return true 125 | } 126 | m.textureReadMutex.Unlock() 127 | return false 128 | } 129 | 130 | // Has the specified model been loaded 131 | func (m *manager) HasModel(filePath string) bool { 132 | m.modelReadMutex.Lock() 133 | if m.models[strings.ToLower(filePath)] != nil { 134 | m.modelReadMutex.Unlock() 135 | return true 136 | } 137 | m.modelReadMutex.Unlock() 138 | return false 139 | } 140 | 141 | func (m *manager) Empty() { 142 | for idx, val := range m.materials { 143 | event.Manager().Dispatch(message.UnloadedMaterial(val)) 144 | delete(m.materials, idx) 145 | } 146 | for idx, val := range m.textures { 147 | event.Manager().Dispatch(message.UnloadedTexture(val)) 148 | delete(m.textures, idx) 149 | } 150 | for idx, val := range m.models { 151 | event.Manager().Dispatch(message.UnloadedModel(val)) 152 | delete(m.models, idx) 153 | } 154 | } 155 | 156 | var resourceManager manager 157 | 158 | // Manager returns the static filemanager 159 | func Manager() *manager { 160 | if resourceManager.materials == nil { 161 | resourceManager.errorModelName = filesystem.BasePathModels + "error.mdl" 162 | resourceManager.errorTextureName = filesystem.BasePathMaterial + "error" + filesystem.ExtensionVtf 163 | resourceManager.materials = make(map[string]material.IMaterial, 1024) 164 | resourceManager.models = make(map[string]*model.Model, 256) 165 | resourceManager.textures = make(map[string]texture.ITexture, 256) 166 | } 167 | 168 | return &resourceManager 169 | } 170 | -------------------------------------------------------------------------------- /resource/manager_test.go: -------------------------------------------------------------------------------- 1 | package resource 2 | 3 | import "testing" 4 | 5 | func TestManager(t *testing.T) { 6 | t.Skip() 7 | } 8 | 9 | func TestManager_AddMaterial(t *testing.T) { 10 | t.Skip() 11 | } 12 | 13 | func TestManager_AddModel(t *testing.T) { 14 | t.Skip() 15 | } 16 | 17 | func TestManager_AddTexture(t *testing.T) { 18 | t.Skip() 19 | } 20 | 21 | func TestManager_Cleanup(t *testing.T) { 22 | t.Skip() 23 | } 24 | 25 | func TestManager_ErrorModelName(t *testing.T) { 26 | t.Skip() 27 | } 28 | 29 | func TestManager_ErrorTextureName(t *testing.T) { 30 | t.Skip() 31 | } 32 | 33 | func TestManager_Material(t *testing.T) { 34 | t.Skip() 35 | } 36 | 37 | func TestManager_Model(t *testing.T) { 38 | t.Skip() 39 | } 40 | 41 | func TestManager_Texture(t *testing.T) { 42 | t.Skip() 43 | } 44 | 45 | func TestManager_HasMaterial(t *testing.T) { 46 | t.Skip() 47 | } 48 | 49 | func TestManager_HasModel(t *testing.T) { 50 | t.Skip() 51 | } 52 | 53 | func TestManager_HasTexture(t *testing.T) { 54 | t.Skip() 55 | } 56 | 57 | func TestManager_SetErrorModelName(t *testing.T) { 58 | t.Skip() 59 | } 60 | 61 | func TestManager_SetErrorTextureName(t *testing.T) { 62 | t.Skip() 63 | } 64 | -------------------------------------------------------------------------------- /resource/message/map.go: -------------------------------------------------------------------------------- 1 | package message 2 | 3 | import ( 4 | "github.com/galaco/lambda-core/event" 5 | "github.com/galaco/lambda-core/model" 6 | ) 7 | 8 | const ( 9 | // TypeMapLoaded 10 | TypeMapLoaded = event.MessageType("MapLoaded") 11 | // TypeMapUnloaded 12 | TypeMapUnloaded = event.MessageType("MapUnloaded") 13 | ) 14 | 15 | // MapLoaded 16 | type MapLoaded struct { 17 | event.Message 18 | // Resource 19 | Resource *model.Bsp 20 | } 21 | 22 | // Type 23 | func (message *MapLoaded) Type() event.MessageType { 24 | return TypeMapLoaded 25 | } 26 | 27 | // MapUnloaded 28 | type MapUnloaded struct { 29 | event.Message 30 | // Resource 31 | Resource *model.Bsp 32 | } 33 | 34 | // Type 35 | func (message *MapUnloaded) Type() event.MessageType { 36 | return TypeMapUnloaded 37 | } 38 | 39 | // LoadedMap 40 | func LoadedMap(world *model.Bsp) event.IMessage { 41 | return &MapLoaded{ 42 | Resource: world, 43 | } 44 | } 45 | 46 | // UnloadedMap 47 | func UnloadedMap(world *model.Bsp) event.IMessage { 48 | return &MapUnloaded{ 49 | Resource: world, 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /resource/message/material.go: -------------------------------------------------------------------------------- 1 | package message 2 | 3 | import ( 4 | "github.com/galaco/lambda-core/event" 5 | "github.com/galaco/lambda-core/material" 6 | ) 7 | 8 | const ( 9 | // TypeMaterialLoaded 10 | TypeMaterialLoaded = event.MessageType("MaterialLoaded") 11 | // TypeMaterialUnloaded 12 | TypeMaterialUnloaded = event.MessageType("MaterialUnloaded") 13 | ) 14 | 15 | // MaterialLoaded 16 | type MaterialLoaded struct { 17 | event.Message 18 | // Resource 19 | Resource material.IMaterial 20 | } 21 | 22 | // Type 23 | func (message *MaterialLoaded) Type() event.MessageType { 24 | return TypeMaterialLoaded 25 | } 26 | 27 | // MaterialUnloaded 28 | type MaterialUnloaded struct { 29 | event.Message 30 | // Resource 31 | Resource material.IMaterial 32 | } 33 | 34 | // Type 35 | func (message *MaterialUnloaded) Type() event.MessageType { 36 | return TypeMaterialUnloaded 37 | } 38 | 39 | // LoadedMaterial 40 | func LoadedMaterial(mat material.IMaterial) event.IMessage { 41 | return &MaterialLoaded{ 42 | Resource: mat, 43 | } 44 | } 45 | 46 | // UnloadedMaterial 47 | func UnloadedMaterial(mat material.IMaterial) event.IMessage { 48 | return &MaterialUnloaded{ 49 | Resource: mat, 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /resource/message/model.go: -------------------------------------------------------------------------------- 1 | package message 2 | 3 | import ( 4 | "github.com/galaco/lambda-core/event" 5 | "github.com/galaco/lambda-core/model" 6 | ) 7 | 8 | const ( 9 | // TypeModelLoaded 10 | TypeModelLoaded = event.MessageType("ModelLoaded") 11 | // TypeModelUnloaded 12 | TypeModelUnloaded = event.MessageType("ModelUnloaded") 13 | ) 14 | 15 | // PropLoaded 16 | type PropLoaded struct { 17 | event.Message 18 | // Resource 19 | Resource *model.Model 20 | } 21 | 22 | // Type 23 | func (message *PropLoaded) Type() event.MessageType { 24 | return TypeModelLoaded 25 | } 26 | 27 | // PropUnloaded 28 | type PropUnloaded struct { 29 | event.Message 30 | // Resource 31 | Resource *model.Model 32 | } 33 | 34 | // Type 35 | func (message *PropUnloaded) Type() event.MessageType { 36 | return TypeModelUnloaded 37 | } 38 | 39 | // LoadedModel 40 | func LoadedModel(mod *model.Model) event.IMessage { 41 | return &PropLoaded{ 42 | Resource: mod, 43 | } 44 | } 45 | 46 | // UnloadedModel 47 | func UnloadedModel(mod *model.Model) event.IMessage { 48 | return &PropUnloaded{ 49 | Resource: mod, 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /resource/message/texture.go: -------------------------------------------------------------------------------- 1 | package message 2 | 3 | import ( 4 | "github.com/galaco/lambda-core/event" 5 | "github.com/galaco/lambda-core/texture" 6 | ) 7 | 8 | const ( 9 | // TypeTextureLoaded 10 | TypeTextureLoaded = event.MessageType("TextureLoaded") 11 | // TypeTextureUnloaded 12 | TypeTextureUnloaded = event.MessageType("TextureUnloaded") 13 | ) 14 | 15 | // TextureLoaded 16 | type TextureLoaded struct { 17 | event.Message 18 | // Resource 19 | Resource texture.ITexture 20 | } 21 | 22 | // Type 23 | func (message *TextureLoaded) Type() event.MessageType { 24 | return TypeTextureLoaded 25 | } 26 | 27 | // TextureUnloaded 28 | type TextureUnloaded struct { 29 | event.Message 30 | // Resource 31 | Resource texture.ITexture 32 | } 33 | 34 | // Type 35 | func (message *TextureUnloaded) Type() event.MessageType { 36 | return TypeTextureUnloaded 37 | } 38 | 39 | // LoadedTexture 40 | func LoadedTexture(tex texture.ITexture) event.IMessage { 41 | return &TextureLoaded{ 42 | Resource: tex, 43 | } 44 | } 45 | 46 | // UnloadedTexture 47 | func UnloadedTexture(tex texture.ITexture) event.IMessage { 48 | return &TextureUnloaded{ 49 | Resource: tex, 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /scene/iscene.go: -------------------------------------------------------------------------------- 1 | package scene 2 | 3 | import ( 4 | "github.com/galaco/lambda-core/model" 5 | ) 6 | 7 | // IScene 8 | type IScene interface { 9 | // Bsp 10 | Bsp() *model.Bsp 11 | // StaticProps 12 | StaticProps() []model.StaticProp 13 | } 14 | -------------------------------------------------------------------------------- /scene/scene.go: -------------------------------------------------------------------------------- 1 | package scene 2 | 3 | import ( 4 | "github.com/galaco/lambda-core/model" 5 | ) 6 | 7 | // Scene 8 | type Scene struct { 9 | bsp model.Bsp 10 | staticProps []model.StaticProp 11 | } 12 | 13 | // Bsp 14 | func (s *Scene) Bsp() *model.Bsp { 15 | return &s.bsp 16 | } 17 | 18 | // StaticProps 19 | func (s *Scene) StaticProps() []model.StaticProp { 20 | return s.staticProps 21 | } 22 | 23 | // NewScene 24 | func NewScene(bsp model.Bsp, staticProps []model.StaticProp) *Scene { 25 | return &Scene{ 26 | bsp: bsp, 27 | staticProps: staticProps, 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /scene/scene_test.go: -------------------------------------------------------------------------------- 1 | package scene 2 | 3 | import ( 4 | "github.com/galaco/lambda-core/mesh" 5 | "github.com/galaco/lambda-core/model" 6 | "reflect" 7 | "testing" 8 | ) 9 | 10 | func TestNewScene(t *testing.T) { 11 | sut := NewScene(model.Bsp{}, make([]model.StaticProp, 0)) 12 | if reflect.TypeOf(sut) != reflect.TypeOf(&Scene{}) { 13 | t.Error("unexpected type for NewScene") 14 | } 15 | } 16 | 17 | func TestScene_Bsp(t *testing.T) { 18 | bspMesh := mesh.NewMesh() 19 | bsp := model.NewBsp(bspMesh) 20 | 21 | sut := NewScene(*bsp, make([]model.StaticProp, 0)) 22 | if sut.Bsp().Mesh() != bsp.Mesh() { 23 | t.Error("unexpected model contained in scene") 24 | } 25 | } 26 | 27 | func TestScene_StaticProps(t *testing.T) { 28 | bspMesh := mesh.NewMesh() 29 | prop := model.StaticProp{} 30 | bsp := model.NewBsp(bspMesh) 31 | 32 | sut := NewScene(*bsp, []model.StaticProp{prop, prop, prop}) 33 | if len(sut.StaticProps()) != 3 { 34 | t.Error("unexpected props returned") 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /texture/atlas.go: -------------------------------------------------------------------------------- 1 | package texture 2 | 3 | import ( 4 | "github.com/galaco/lambda-core/lib/math/shape" 5 | "github.com/galaco/vtf/format" 6 | ) 7 | 8 | // @TODO THIS IS NOT COMPLETE. IT DOES NOT WORK 9 | 10 | // Atlas 11 | // A texture atlas implementation. 12 | type Atlas struct { 13 | Colour2D 14 | } 15 | 16 | // Format returns colour format 17 | // For now always RGBA 18 | func (atlas *Atlas) Format() uint32 { 19 | return uint32(format.RGB888) 20 | } 21 | 22 | // PackTextures 23 | func (atlas *Atlas) PackTextures(textures []ITexture, padding int) ([]shape.Rect, error) { 24 | uvRects := make([]shape.Rect, 0) 25 | 26 | return uvRects, nil 27 | } 28 | 29 | //// findSpace finds free space in atlas buffer to write rectangle to 30 | //func (atlas *Atlas) findSpace(width int, height int) (x, y int, err error) { 31 | // 32 | // return x, y, err 33 | //} 34 | 35 | 36 | // NewAtlas 37 | func NewAtlas(width int, height int) *Atlas { 38 | return &Atlas{ 39 | Colour2D: Colour2D{ 40 | rawColourData: make([]uint8, width*height*3), 41 | Texture2D: Texture2D{ 42 | width: width, 43 | height: height, 44 | }, 45 | }, 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /texture/colour2d.go: -------------------------------------------------------------------------------- 1 | package texture 2 | 3 | import ( 4 | "github.com/galaco/vtf/format" 5 | ) 6 | 7 | // Colour2D is a material defined by raw/computed colour data, 8 | // rather than loaded vtf data 9 | type Colour2D struct { 10 | Texture2D 11 | rawColourData []uint8 12 | } 13 | 14 | // Format returns colour format 15 | func (error *Colour2D) Format() uint32 { 16 | return uint32(format.RGB888) 17 | } 18 | 19 | // PixelDataForFrame returns raw colour data for specific animation 20 | // frame 21 | func (error *Colour2D) PixelDataForFrame(frame int) []byte { 22 | return error.rawColourData 23 | } 24 | 25 | // Thumbnail return a low resolution version of the image 26 | func (error *Colour2D) Thumbnail() []byte { 27 | return append(error.rawColourData, append(error.rawColourData, append(error.rawColourData, error.rawColourData...)...)...) 28 | } 29 | 30 | // NewError returns new Error material 31 | func NewError(name string) *Colour2D { 32 | mat := Colour2D{} 33 | 34 | mat.width = 8 35 | mat.height = 8 36 | mat.filePath = name 37 | 38 | // This generates purple & black chequers. 39 | mat.rawColourData = []uint8{ 40 | 255, 0, 255, 41 | 255, 0, 255, 42 | 255, 0, 255, 43 | 255, 0, 255, 44 | 0, 0, 0, 45 | 0, 0, 0, 46 | 0, 0, 0, 47 | 0, 0, 0, 48 | 49 | 255, 0, 255, 50 | 255, 0, 255, 51 | 255, 0, 255, 52 | 255, 0, 255, 53 | 0, 0, 0, 54 | 0, 0, 0, 55 | 0, 0, 0, 56 | 0, 0, 0, 57 | 58 | 255, 0, 255, 59 | 255, 0, 255, 60 | 255, 0, 255, 61 | 255, 0, 255, 62 | 0, 0, 0, 63 | 0, 0, 0, 64 | 0, 0, 0, 65 | 0, 0, 0, 66 | 67 | 255, 0, 255, 68 | 255, 0, 255, 69 | 255, 0, 255, 70 | 255, 0, 255, 71 | 0, 0, 0, 72 | 0, 0, 0, 73 | 0, 0, 0, 74 | 0, 0, 0, 75 | 76 | 0, 0, 0, 77 | 0, 0, 0, 78 | 0, 0, 0, 79 | 0, 0, 0, 80 | 255, 0, 255, 81 | 255, 0, 255, 82 | 255, 0, 255, 83 | 255, 0, 255, 84 | 85 | 0, 0, 0, 86 | 0, 0, 0, 87 | 0, 0, 0, 88 | 0, 0, 0, 89 | 255, 0, 255, 90 | 255, 0, 255, 91 | 255, 0, 255, 92 | 255, 0, 255, 93 | 94 | 0, 0, 0, 95 | 0, 0, 0, 96 | 0, 0, 0, 97 | 0, 0, 0, 98 | 255, 0, 255, 99 | 255, 0, 255, 100 | 255, 0, 255, 101 | 255, 0, 255, 102 | 103 | 0, 0, 0, 104 | 0, 0, 0, 105 | 0, 0, 0, 106 | 0, 0, 0, 107 | 255, 0, 255, 108 | 255, 0, 255, 109 | 255, 0, 255, 110 | 255, 0, 255, 111 | } 112 | 113 | return &mat 114 | } 115 | -------------------------------------------------------------------------------- /texture/colour2d_test.go: -------------------------------------------------------------------------------- 1 | package texture 2 | 3 | import ( 4 | "github.com/galaco/vtf/format" 5 | "testing" 6 | ) 7 | 8 | func TestNewError(t *testing.T) { 9 | tex := NewError("error.vtf") 10 | 11 | if tex.Width() != 8 { 12 | t.Error("unexpected width") 13 | } 14 | if tex.Height() != 8 { 15 | t.Error("unexpected height") 16 | } 17 | 18 | expectedColourData := []uint8{ 19 | 255, 0, 255, 20 | 255, 0, 255, 21 | 255, 0, 255, 22 | 255, 0, 255, 23 | 0, 0, 0, 24 | 0, 0, 0, 25 | 0, 0, 0, 26 | 0, 0, 0, 27 | 28 | 255, 0, 255, 29 | 255, 0, 255, 30 | 255, 0, 255, 31 | 255, 0, 255, 32 | 0, 0, 0, 33 | 0, 0, 0, 34 | 0, 0, 0, 35 | 0, 0, 0, 36 | 37 | 255, 0, 255, 38 | 255, 0, 255, 39 | 255, 0, 255, 40 | 255, 0, 255, 41 | 0, 0, 0, 42 | 0, 0, 0, 43 | 0, 0, 0, 44 | 0, 0, 0, 45 | 46 | 255, 0, 255, 47 | 255, 0, 255, 48 | 255, 0, 255, 49 | 255, 0, 255, 50 | 0, 0, 0, 51 | 0, 0, 0, 52 | 0, 0, 0, 53 | 0, 0, 0, 54 | 55 | 0, 0, 0, 56 | 0, 0, 0, 57 | 0, 0, 0, 58 | 0, 0, 0, 59 | 255, 0, 255, 60 | 255, 0, 255, 61 | 255, 0, 255, 62 | 255, 0, 255, 63 | 64 | 0, 0, 0, 65 | 0, 0, 0, 66 | 0, 0, 0, 67 | 0, 0, 0, 68 | 255, 0, 255, 69 | 255, 0, 255, 70 | 255, 0, 255, 71 | 255, 0, 255, 72 | 73 | 0, 0, 0, 74 | 0, 0, 0, 75 | 0, 0, 0, 76 | 0, 0, 0, 77 | 255, 0, 255, 78 | 255, 0, 255, 79 | 255, 0, 255, 80 | 255, 0, 255, 81 | 82 | 0, 0, 0, 83 | 0, 0, 0, 84 | 0, 0, 0, 85 | 0, 0, 0, 86 | 255, 0, 255, 87 | 255, 0, 255, 88 | 255, 0, 255, 89 | 255, 0, 255, 90 | } 91 | 92 | for idx, v := range expectedColourData { 93 | if tex.PixelDataForFrame(0)[idx] != v { 94 | t.Error("unexpected colour data for error texture") 95 | } 96 | } 97 | } 98 | 99 | func TestColour2D_Format(t *testing.T) { 100 | tex := NewError("error.vtf") 101 | if tex.Format() != uint32(format.RGB888) { 102 | t.Error("unexpected error colour data format") 103 | } 104 | } 105 | 106 | func TestColour2D_PixelDataForFrame(t *testing.T) { 107 | tex := NewError("error.vtf") 108 | 109 | expectedColourData := []uint8{ 110 | 255, 0, 255, 111 | 255, 0, 255, 112 | 255, 0, 255, 113 | 255, 0, 255, 114 | 0, 0, 0, 115 | 0, 0, 0, 116 | 0, 0, 0, 117 | 0, 0, 0, 118 | 119 | 255, 0, 255, 120 | 255, 0, 255, 121 | 255, 0, 255, 122 | 255, 0, 255, 123 | 0, 0, 0, 124 | 0, 0, 0, 125 | 0, 0, 0, 126 | 0, 0, 0, 127 | 128 | 255, 0, 255, 129 | 255, 0, 255, 130 | 255, 0, 255, 131 | 255, 0, 255, 132 | 0, 0, 0, 133 | 0, 0, 0, 134 | 0, 0, 0, 135 | 0, 0, 0, 136 | 137 | 255, 0, 255, 138 | 255, 0, 255, 139 | 255, 0, 255, 140 | 255, 0, 255, 141 | 0, 0, 0, 142 | 0, 0, 0, 143 | 0, 0, 0, 144 | 0, 0, 0, 145 | 146 | 0, 0, 0, 147 | 0, 0, 0, 148 | 0, 0, 0, 149 | 0, 0, 0, 150 | 255, 0, 255, 151 | 255, 0, 255, 152 | 255, 0, 255, 153 | 255, 0, 255, 154 | 155 | 0, 0, 0, 156 | 0, 0, 0, 157 | 0, 0, 0, 158 | 0, 0, 0, 159 | 255, 0, 255, 160 | 255, 0, 255, 161 | 255, 0, 255, 162 | 255, 0, 255, 163 | 164 | 0, 0, 0, 165 | 0, 0, 0, 166 | 0, 0, 0, 167 | 0, 0, 0, 168 | 255, 0, 255, 169 | 255, 0, 255, 170 | 255, 0, 255, 171 | 255, 0, 255, 172 | 173 | 0, 0, 0, 174 | 0, 0, 0, 175 | 0, 0, 0, 176 | 0, 0, 0, 177 | 255, 0, 255, 178 | 255, 0, 255, 179 | 255, 0, 255, 180 | 255, 0, 255, 181 | } 182 | 183 | for idx, v := range expectedColourData { 184 | if tex.PixelDataForFrame(0)[idx] != v { 185 | t.Error("unexpected colour data for error texture") 186 | } 187 | } 188 | } 189 | -------------------------------------------------------------------------------- /texture/cubemap.go: -------------------------------------------------------------------------------- 1 | package texture 2 | 3 | // Cubemap is a 6-sided edgeless texture that can be mapped to a cube, 4 | // Used mainly for pre-computed reflections 5 | type Cubemap struct { 6 | Texture2D 7 | Faces []ITexture 8 | } 9 | 10 | // Width Get material width. 11 | // Must have exactly 6 faces, and all faces are assumed the same size 12 | func (material *Cubemap) Width() int { 13 | if len(material.Faces) != 6 { 14 | return 0 15 | } 16 | return material.Faces[0].Width() 17 | } 18 | 19 | // Height Get material height. 20 | // Must have exactly 6 faces, and all faces are assumed the same size 21 | func (material *Cubemap) Height() int { 22 | if len(material.Faces) != 6 { 23 | return 0 24 | } 25 | return material.Faces[0].Height() 26 | } 27 | 28 | // Format get material format 29 | // Same format for all faces assumed 30 | func (material *Cubemap) Format() uint32 { 31 | if len(material.Faces) != 6 { 32 | return 0 33 | } 34 | return material.Faces[0].Format() 35 | } 36 | 37 | // NewCubemap returns a new cubemap material 38 | func NewCubemap(materials []ITexture) *Cubemap { 39 | return &Cubemap{ 40 | Faces: materials, 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /texture/cubemap_test.go: -------------------------------------------------------------------------------- 1 | package texture 2 | 3 | import ( 4 | "testing" 5 | ) 6 | 7 | func TestNewCubemap(t *testing.T) { 8 | tex := NewError("error.vtf") 9 | 10 | cb := NewCubemap([]ITexture{tex, tex, tex, tex, tex, tex}) 11 | if cb == nil { 12 | t.Error("failed to create cubemap from textures") 13 | } 14 | } 15 | 16 | func TestCubemap_Format(t *testing.T) { 17 | tex := NewError("error.vtf") 18 | 19 | cb := NewCubemap([]ITexture{tex, tex, tex, tex, tex, tex}) 20 | if cb.Width() != tex.Width() { 21 | t.Error("unexpected cubemap width") 22 | } 23 | if tex.Format() != cb.Format() { 24 | t.Error("unexpected error colour data format") 25 | } 26 | 27 | cb2 := NewCubemap([]ITexture{}) 28 | if cb2.Format() != 0 { 29 | t.Error("unexpected cubemap format") 30 | } 31 | } 32 | 33 | func TestCubemap_Height(t *testing.T) { 34 | tex := NewError("error.vtf") 35 | 36 | cb := NewCubemap([]ITexture{tex, tex, tex, tex, tex, tex}) 37 | if cb.Height() != tex.Height() { 38 | t.Error("unexpected cubemap height") 39 | } 40 | 41 | cb2 := NewCubemap([]ITexture{}) 42 | if cb2.Height() != 0 { 43 | t.Error("unexpected cubemap height") 44 | } 45 | } 46 | 47 | func TestCubemap_Width(t *testing.T) { 48 | tex := NewError("error.vtf") 49 | 50 | cb := NewCubemap([]ITexture{tex, tex, tex, tex, tex, tex}) 51 | if cb.Width() != tex.Width() { 52 | t.Error("unexpected cubemap width") 53 | } 54 | 55 | cb2 := NewCubemap([]ITexture{}) 56 | if cb2.Width() != 0 { 57 | t.Error("unexpected cubemap width") 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /texture/itexture.go: -------------------------------------------------------------------------------- 1 | package texture 2 | 3 | // ITexture Interface for a GPU texture 4 | type ITexture interface { 5 | Width() int 6 | Height() int 7 | Format() uint32 8 | PixelDataForFrame(int) []byte 9 | FilePath() string 10 | Thumbnail() []byte 11 | } 12 | -------------------------------------------------------------------------------- /texture/lightmap.go: -------------------------------------------------------------------------------- 1 | package texture 2 | 3 | import ( 4 | "github.com/galaco/bsp/primitives/common" 5 | ) 6 | 7 | // Lightmap is a material used for lighting a face 8 | type Lightmap struct { 9 | Colour2D 10 | } 11 | 12 | // LightmapFromColorRGBExp32 creates a lightmap from BSP stored colour data 13 | func LightmapFromColorRGBExp32(width int, height int, colorMaps []common.ColorRGBExponent32) *Lightmap { 14 | raw := make([]uint8, len(colorMaps)*3) 15 | 16 | for idx, sample := range colorMaps { 17 | raw[idx*3] = sample.R // * sample.Exponent 18 | raw[idx*3+1] = sample.G // * sample.Exponent 19 | raw[idx*3+2] = sample.B // * sample.Exponent 20 | } 21 | 22 | mat := &Lightmap{} 23 | 24 | mat.width = width 25 | mat.height = height 26 | mat.rawColourData = raw 27 | mat.filePath = "__lightmap" 28 | 29 | return mat 30 | } 31 | -------------------------------------------------------------------------------- /texture/texture2d.go: -------------------------------------------------------------------------------- 1 | package texture 2 | 3 | import ( 4 | "github.com/galaco/vtf" 5 | ) 6 | 7 | // Texture2D is a generic GPU material struct 8 | type Texture2D struct { 9 | filePath string 10 | width int 11 | height int 12 | vtf *vtf.Vtf 13 | } 14 | 15 | // FilePath Get the filepath this data was loaded from 16 | func (tex *Texture2D) FilePath() string { 17 | return tex.filePath 18 | } 19 | 20 | // Width returns materials width 21 | func (tex *Texture2D) Width() int { 22 | return tex.width 23 | } 24 | 25 | // Height returns materials height 26 | func (tex *Texture2D) Height() int { 27 | return tex.height 28 | } 29 | 30 | // Format returns this materials colour format 31 | func (tex *Texture2D) Format() uint32 { 32 | return tex.vtf.Header().HighResImageFormat 33 | } 34 | 35 | // PixelDataForFrame get raw colour data for this frame 36 | func (tex *Texture2D) PixelDataForFrame(frame int) []byte { 37 | return tex.vtf.HighestResolutionImageForFrame(frame) 38 | } 39 | 40 | // Thumbnail returns a small thumbnail image of a material 41 | func (tex *Texture2D) Thumbnail() []byte { 42 | return tex.vtf.LowResImageData() 43 | } 44 | 45 | // NewTexture2D returns a new texture from Vtf 46 | func NewTexture2D(filePath string, vtf *vtf.Vtf, width int, height int) *Texture2D { 47 | return &Texture2D{ 48 | filePath: filePath, 49 | width: width, 50 | height: height, 51 | vtf: vtf, 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /texture/texture2d_test.go: -------------------------------------------------------------------------------- 1 | package texture 2 | 3 | import "testing" 4 | 5 | func TestTexture2D_FilePath(t *testing.T) { 6 | tex := NewError("error.vtf") 7 | if tex.FilePath() != "error.vtf" { 8 | t.Error("incorrect filepath for texture") 9 | } 10 | } 11 | 12 | func TestTexture2D_Height(t *testing.T) { 13 | tex := NewError("error.vtf") 14 | 15 | if tex.Height() != 8 { 16 | t.Error("unexpected height") 17 | } 18 | } 19 | 20 | func TestTexture2D_Width(t *testing.T) { 21 | tex := NewError("error.vtf") 22 | 23 | if tex.Width() != 8 { 24 | t.Error("unexpected width") 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /vgui/Element.go: -------------------------------------------------------------------------------- 1 | package vgui 2 | 3 | type Element interface { 4 | Draw() 5 | Resize(parentWidth, parentHeight float64) 6 | } 7 | -------------------------------------------------------------------------------- /vgui/button.go: -------------------------------------------------------------------------------- 1 | package vgui 2 | 3 | type Button struct { 4 | text string 5 | } 6 | 7 | func (btn *Button) Draw() { 8 | 9 | } 10 | 11 | func (btn *Button) Resize(parentWidth, parentHeight float64) { 12 | 13 | } 14 | 15 | func NewButton(label string) *Button { 16 | return &Button{ 17 | text: label, 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /vgui/masterPanel.go: -------------------------------------------------------------------------------- 1 | package vgui 2 | 3 | type MasterPanel struct { 4 | Panel 5 | } 6 | 7 | func (panel *MasterPanel) Resize(width, height float64) { 8 | for _, child := range panel.children { 9 | child.Resize(width, height) 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /vgui/panel.go: -------------------------------------------------------------------------------- 1 | package vgui 2 | 3 | // Panel 4 | type Panel struct { 5 | X float64 6 | Y float64 7 | Width float64 8 | Height float64 9 | 10 | enabled bool 11 | proportional bool 12 | 13 | children []*Panel 14 | elements []Element 15 | } 16 | 17 | // Proportional 18 | func (panel *Panel) Proportional() bool { 19 | return panel.proportional 20 | } 21 | 22 | // SetProportional 23 | func (panel *Panel) SetProportional(proportional bool) { 24 | panel.proportional = proportional 25 | } 26 | 27 | // Enabled 28 | func (panel *Panel) Enabled() bool { 29 | return panel.enabled 30 | } 31 | 32 | // SetEnabled 33 | func (panel *Panel) SetEnabled(enabled bool) { 34 | panel.enabled = enabled 35 | } 36 | 37 | // Children returns all panels that are a child of this panel 38 | func (panel *Panel) Children() []*Panel { 39 | return panel.children 40 | } 41 | 42 | // AddChild adds a new child to this panel 43 | func (panel *Panel) AddChild(child *Panel) { 44 | panel.children = append(panel.children, child) 45 | } 46 | 47 | func (panel *Panel) AddElement(element Element) { 48 | panel.elements = append(panel.elements, element) 49 | } 50 | 51 | // Draw 52 | func (panel *Panel) Draw() { 53 | if !panel.enabled { 54 | return 55 | } 56 | for _, p := range panel.elements { 57 | p.Draw() 58 | } 59 | for _, child := range panel.Children() { 60 | child.Draw() 61 | } 62 | } 63 | 64 | // Resize 65 | func (panel *Panel) Resize(parentWidth, parentHeight float64) { 66 | for _, p := range panel.elements { 67 | p.Resize(parentWidth, parentHeight) 68 | } 69 | for _, child := range panel.Children() { 70 | child.Resize(parentWidth, parentHeight) 71 | } 72 | } 73 | 74 | // NewChildPanel 75 | func (panel *Panel) NewChildPanel(X, Y, Width, Height float64, Enabled bool) *Panel { 76 | p := &Panel{ 77 | X: X, 78 | Y: Y, 79 | Width: Width, 80 | Height: Height, 81 | enabled: Enabled, 82 | } 83 | 84 | panel.children = append(panel.children, p) 85 | 86 | return p 87 | } 88 | --------------------------------------------------------------------------------