diff --git a/src/main/java/com/terminalvelocitycabbage/editor/Editor.java b/src/main/java/com/terminalvelocitycabbage/editor/Editor.java new file mode 100644 index 00000000..d76efc0b --- /dev/null +++ b/src/main/java/com/terminalvelocitycabbage/editor/Editor.java @@ -0,0 +1,67 @@ +package com.terminalvelocitycabbage.editor; + +import com.terminalvelocitycabbage.editor.registry.*; +import com.terminalvelocitycabbage.engine.client.ClientBase; +import com.terminalvelocitycabbage.engine.client.window.WindowProperties; +import com.terminalvelocitycabbage.engine.event.EventDispatcher; +import com.terminalvelocitycabbage.engine.filesystem.resources.ResourceSource; +import com.terminalvelocitycabbage.engine.filesystem.sources.MainSource; +import com.terminalvelocitycabbage.engine.registry.Identifier; +import com.terminalvelocitycabbage.templates.events.*; + +public abstract class Editor extends ClientBase { + + public static Identifier ENGINE_RESOURCE_SOURCE; + + public static Identifier EDITOR_SCENE; + + protected T gameClient; + + public static final String ID = "editor"; + + public Editor(T gameClient) { + super(ID, 20); + this.gameClient = gameClient; + } + + @Override + public void registerEventListeners(EventDispatcher dispatcher) { + gameClient.registerEventListeners(dispatcher); + + dispatcher.listenToEvent(ResourceCategoryRegistrationEvent.EVENT, event -> EditorResources.registerResourceCategories((ResourceCategoryRegistrationEvent) event)); + dispatcher.listenToEvent(ResourceSourceRegistrationEvent.EVENT, event -> { + ResourceSource mainSource = new MainSource(getInstance(), getNamespace()); + mainSource.registerDefaultSources(getNamespace()); + ENGINE_RESOURCE_SOURCE = ((ResourceSourceRegistrationEvent) event).registerResourceSource(getNamespace(), "editor", mainSource); + }); + dispatcher.listenToEvent(EntityComponentRegistrationEvent.EVENT, event -> EditorEntities.registerComponents((EntityComponentRegistrationEvent) event)); + dispatcher.listenToEvent(EntitySystemRegistrationEvent.EVENT, event -> EditorEntities.createSystems((EntitySystemRegistrationEvent) event)); + dispatcher.listenToEvent(RoutineRegistrationEvent.EVENT, event -> EditorRoutines.init((RoutineRegistrationEvent) event)); + dispatcher.listenToEvent(RendererRegistrationEvent.EVENT, event -> EditorRenderers.init((RendererRegistrationEvent) event)); + dispatcher.listenToEvent(SceneRegistrationEvent.EVENT, event -> { + EditorScenes.init((SceneRegistrationEvent) event); + EDITOR_SCENE = EditorScenes.EDITOR_SCENE; + }); + dispatcher.listenToEvent(LocalizedTextKeyRegistrationEvent.EVENT, event -> EditorLocalizedTexts.registerLocalizedTextKeys((LocalizedTextKeyRegistrationEvent) event)); + dispatcher.listenToEvent(MeshRegistrationEvent.EVENT, event -> EditorMeshes.init((MeshRegistrationEvent) event)); + dispatcher.listenToEvent(AnimationConfigurationEvent.EVENT, event -> EditorModels.initAnimations((AnimationConfigurationEvent) event)); + dispatcher.listenToEvent(ModelConfigRegistrationEvent.EVENT, event -> EditorModels.init((ModelConfigRegistrationEvent) event)); + + EditorFonts.init(dispatcher); + EditorInput.init(dispatcher); + EditorTextures.init(dispatcher); + EditorStates.init(dispatcher); + } + + @Override + public void init() { + super.init(); + WindowProperties properties = new WindowProperties(1800, 900, "Terminal Velocity Engine", EDITOR_SCENE); + long window = getWindowManager().createNewWindow(properties); + getWindowManager().focusWindow(window); + } + + public T getGameClient() { + return gameClient; + } +} diff --git a/src/main/java/com/terminalvelocitycabbage/editor/data/EditorMeshData.java b/src/main/java/com/terminalvelocitycabbage/editor/data/EditorMeshData.java new file mode 100644 index 00000000..5a4ae17a --- /dev/null +++ b/src/main/java/com/terminalvelocitycabbage/editor/data/EditorMeshData.java @@ -0,0 +1,23 @@ +package com.terminalvelocitycabbage.editor.data; + +import com.terminalvelocitycabbage.engine.client.renderer.elements.VertexAttribute; +import com.terminalvelocitycabbage.engine.client.renderer.elements.VertexFormat; + +public class EditorMeshData { + + public static final VertexFormat MESH_FORMAT = VertexFormat.builder() + .addElement(VertexAttribute.XYZ_POSITION) + .addElement(VertexAttribute.XYZ_NORMAL) + .addElement(VertexAttribute.RGB_COLOR) + .addElement(VertexAttribute.UV) + .build(); + + public static final VertexFormat ANIMATED_MESH_FORMAT = VertexFormat.builder() + .addElement(VertexAttribute.XYZ_POSITION) + .addElement(VertexAttribute.XYZ_NORMAL) + .addElement(VertexAttribute.RGB_COLOR) + .addElement(VertexAttribute.UV) + .addElement(VertexAttribute.BONE_INDEX) + .build(); + +} diff --git a/src/main/java/com/terminalvelocitycabbage/editor/ecs/components/EditorCameraComponent.java b/src/main/java/com/terminalvelocitycabbage/editor/ecs/components/EditorCameraComponent.java new file mode 100644 index 00000000..81b6035b --- /dev/null +++ b/src/main/java/com/terminalvelocitycabbage/editor/ecs/components/EditorCameraComponent.java @@ -0,0 +1,28 @@ +package com.terminalvelocitycabbage.editor.ecs.components; + +import com.terminalvelocitycabbage.editor.hints.EditorHint; +import com.terminalvelocitycabbage.engine.client.renderer.Projection; +import com.terminalvelocitycabbage.templates.ecs.components.CameraComponent; +import com.terminalvelocitycabbage.engine.util.Transformation; +import org.joml.Matrix4f; + +@EditorHint.ComponentName(name = "Editor Camera") +public class EditorCameraComponent extends CameraComponent { + + public EditorCameraComponent() { + super(new Projection(Projection.Type.PERSPECTIVE, 70, 0.1f, 1000f)); + } + + @Override + public void updateProjectionMatrix(int width, int height) { + getProjection().updateProjectionMatrix(width, height); + } + + @Override + public Matrix4f getViewMatrix(Transformation transformation) { + var pos = transformation.getPosition(); + return viewMatrix.identity() + .rotate(transformation.getRotation().invert(new org.joml.Quaternionf())) + .translate(-pos.x, -pos.y, -pos.z); + } +} diff --git a/src/main/java/com/terminalvelocitycabbage/editor/hints/EditorHint.java b/src/main/java/com/terminalvelocitycabbage/editor/hints/EditorHint.java new file mode 100644 index 00000000..eeab7f72 --- /dev/null +++ b/src/main/java/com/terminalvelocitycabbage/editor/hints/EditorHint.java @@ -0,0 +1,13 @@ +package com.terminalvelocitycabbage.editor.hints; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + +public class EditorHint { + + @Retention(RetentionPolicy.RUNTIME) + public @interface ComponentName { + String name(); + } + +} diff --git a/src/main/java/com/terminalvelocitycabbage/editor/registry/EditorEntities.java b/src/main/java/com/terminalvelocitycabbage/editor/registry/EditorEntities.java new file mode 100644 index 00000000..918ac980 --- /dev/null +++ b/src/main/java/com/terminalvelocitycabbage/editor/registry/EditorEntities.java @@ -0,0 +1,23 @@ +package com.terminalvelocitycabbage.editor.registry; + +import com.terminalvelocitycabbage.editor.ecs.components.EditorCameraComponent; +import com.terminalvelocitycabbage.templates.ecs.components.CameraComponent; +import com.terminalvelocitycabbage.templates.ecs.components.DirectionalLightComponent; +import com.terminalvelocitycabbage.templates.ecs.components.NameComponent; +import com.terminalvelocitycabbage.templates.events.EntityComponentRegistrationEvent; +import com.terminalvelocitycabbage.templates.events.EntitySystemRegistrationEvent; + +public class EditorEntities { + + public static void registerComponents(EntityComponentRegistrationEvent event) { + event.registerComponent(NameComponent.class); + event.registerComponent(CameraComponent.class); + event.registerComponent(EditorCameraComponent.class); + event.registerComponent(DirectionalLightComponent.class); + } + + public static void createSystems(EntitySystemRegistrationEvent event) { + + } + +} diff --git a/src/main/java/com/terminalvelocitycabbage/editor/registry/EditorFonts.java b/src/main/java/com/terminalvelocitycabbage/editor/registry/EditorFonts.java new file mode 100644 index 00000000..0c680d9e --- /dev/null +++ b/src/main/java/com/terminalvelocitycabbage/editor/registry/EditorFonts.java @@ -0,0 +1,37 @@ +package com.terminalvelocitycabbage.editor.registry; + +import com.terminalvelocitycabbage.editor.Editor; +import com.terminalvelocitycabbage.engine.event.EventDispatcher; +import com.terminalvelocitycabbage.engine.registry.Identifier; +import com.terminalvelocitycabbage.templates.events.FontRegistrationEvent; + +import static com.terminalvelocitycabbage.engine.filesystem.resources.ResourceCategory.FONT; + +public class EditorFonts { + + public static final Identifier THIN = FONT.identifierOf(Editor.ID, "outfit_thin"); + public static final Identifier EXTRA_LIGHT = FONT.identifierOf(Editor.ID, "outfit_extralight"); + public static final Identifier LIGHT = FONT.identifierOf(Editor.ID, "outfit_light"); + public static final Identifier REGULAR = FONT.identifierOf(Editor.ID, "outfit_regular"); + public static final Identifier MEDIUM = FONT.identifierOf(Editor.ID, "outfit_medium"); + public static final Identifier SEMI_BOLD = FONT.identifierOf(Editor.ID, "outfit_semibold"); + public static final Identifier BOLD = FONT.identifierOf(Editor.ID, "outfit_bold"); + public static final Identifier EXTRA_BOLD = FONT.identifierOf(Editor.ID, "outfit_extrabold"); + public static final Identifier BLACK = FONT.identifierOf(Editor.ID, "outfit_black"); + + public static void init(EventDispatcher eventDispatcher) { + registerFont(eventDispatcher, THIN); + registerFont(eventDispatcher, EXTRA_LIGHT); + registerFont(eventDispatcher, LIGHT); + registerFont(eventDispatcher, REGULAR); + registerFont(eventDispatcher, MEDIUM); + registerFont(eventDispatcher, SEMI_BOLD); + registerFont(eventDispatcher, BOLD); + registerFont(eventDispatcher, EXTRA_BOLD); + registerFont(eventDispatcher, BLACK); + } + + public static void registerFont(EventDispatcher eventDispatcher, Identifier name) { + eventDispatcher.listenToEvent(FontRegistrationEvent.EVENT, e -> ((FontRegistrationEvent) e).registerFont(name)); + } +} diff --git a/src/main/java/com/terminalvelocitycabbage/editor/registry/EditorInput.java b/src/main/java/com/terminalvelocitycabbage/editor/registry/EditorInput.java new file mode 100644 index 00000000..3821ea0b --- /dev/null +++ b/src/main/java/com/terminalvelocitycabbage/editor/registry/EditorInput.java @@ -0,0 +1,39 @@ +package com.terminalvelocitycabbage.editor.registry; + +import com.terminalvelocitycabbage.editor.Editor; +import com.terminalvelocitycabbage.engine.client.input.control.Control; +import com.terminalvelocitycabbage.engine.client.input.control.MouseButtonControl; +import com.terminalvelocitycabbage.engine.client.input.control.MouseScrollControl; +import com.terminalvelocitycabbage.engine.client.input.controller.ControlGroup; +import com.terminalvelocitycabbage.engine.client.input.types.MouseInput; +import com.terminalvelocitycabbage.engine.event.EventDispatcher; +import com.terminalvelocitycabbage.engine.registry.Identifier; +import com.terminalvelocitycabbage.templates.events.InputHandlerRegistrationEvent; +import com.terminalvelocitycabbage.templates.inputcontrollers.UIClickController; +import com.terminalvelocitycabbage.templates.inputcontrollers.UIScrollController; + +public class EditorInput { + + public static Identifier UI_CLICK; + public static Identifier UI_SCROLL; + + public static void init(EventDispatcher eventDispatcher) { + + eventDispatcher.listenToEvent(InputHandlerRegistrationEvent.EVENT, e -> { + InputHandlerRegistrationEvent event = (InputHandlerRegistrationEvent) e; + + var inputHandler = event.getInputHandler(); + + Control leftClickControl = inputHandler.registerControlListener(new MouseButtonControl(MouseInput.Button.LEFT_CLICK)); + Control mouseScrollUpControl = inputHandler.registerControlListener(new MouseScrollControl(MouseInput.ScrollDirection.UP, 1f)); + Control mouseScrollDownControl = inputHandler.registerControlListener(new MouseScrollControl(MouseInput.ScrollDirection.DOWN, 1f)); + + UI_CLICK = inputHandler.registerController(Editor.getInstance().getNamespace(), "ui_click", new UIClickController(MouseInput.Button.LEFT_CLICK, leftClickControl)); + UI_SCROLL = inputHandler.registerController(Editor.getInstance().getNamespace(), "scroll", new UIScrollController( + new ControlGroup(mouseScrollUpControl), + new ControlGroup(mouseScrollDownControl) + )); + }); + } + +} diff --git a/src/main/java/com/terminalvelocitycabbage/editor/registry/EditorLocalizedTexts.java b/src/main/java/com/terminalvelocitycabbage/editor/registry/EditorLocalizedTexts.java new file mode 100644 index 00000000..99c0534a --- /dev/null +++ b/src/main/java/com/terminalvelocitycabbage/editor/registry/EditorLocalizedTexts.java @@ -0,0 +1,11 @@ +package com.terminalvelocitycabbage.editor.registry; + +import com.terminalvelocitycabbage.templates.events.LocalizedTextKeyRegistrationEvent; + +public class EditorLocalizedTexts { + + public static void registerLocalizedTextKeys(LocalizedTextKeyRegistrationEvent event) { + + } + +} diff --git a/src/main/java/com/terminalvelocitycabbage/editor/registry/EditorMeshes.java b/src/main/java/com/terminalvelocitycabbage/editor/registry/EditorMeshes.java new file mode 100644 index 00000000..bf6dfadb --- /dev/null +++ b/src/main/java/com/terminalvelocitycabbage/editor/registry/EditorMeshes.java @@ -0,0 +1,11 @@ +package com.terminalvelocitycabbage.editor.registry; + +import com.terminalvelocitycabbage.templates.events.MeshRegistrationEvent; + +public class EditorMeshes { + + public static void init(MeshRegistrationEvent event) { + + } + +} diff --git a/src/main/java/com/terminalvelocitycabbage/editor/registry/EditorModels.java b/src/main/java/com/terminalvelocitycabbage/editor/registry/EditorModels.java new file mode 100644 index 00000000..3f3b179e --- /dev/null +++ b/src/main/java/com/terminalvelocitycabbage/editor/registry/EditorModels.java @@ -0,0 +1,16 @@ +package com.terminalvelocitycabbage.editor.registry; + +import com.terminalvelocitycabbage.templates.events.AnimationConfigurationEvent; +import com.terminalvelocitycabbage.templates.events.ModelConfigRegistrationEvent; + +public class EditorModels { + + public static void init(ModelConfigRegistrationEvent event) { + + } + + public static void initAnimations(AnimationConfigurationEvent event) { + + } + +} diff --git a/src/main/java/com/terminalvelocitycabbage/editor/registry/EditorRenderers.java b/src/main/java/com/terminalvelocitycabbage/editor/registry/EditorRenderers.java new file mode 100644 index 00000000..b715bc04 --- /dev/null +++ b/src/main/java/com/terminalvelocitycabbage/editor/registry/EditorRenderers.java @@ -0,0 +1,31 @@ +package com.terminalvelocitycabbage.editor.registry; + +import com.terminalvelocitycabbage.editor.Editor; +import com.terminalvelocitycabbage.editor.rendernodes.DrawEditorUIRenderNode; +import com.terminalvelocitycabbage.editor.rendernodes.EditorDrawSceneRenderNode; +import com.terminalvelocitycabbage.engine.client.renderer.RenderGraph; +import com.terminalvelocitycabbage.engine.client.renderer.shader.ShaderProgramConfig; +import com.terminalvelocitycabbage.engine.registry.Identifier; +import com.terminalvelocitycabbage.templates.events.RendererRegistrationEvent; + +public class EditorRenderers { + + public static Identifier DRAW_SCENE_RENDER_NODE; + public static Identifier DRAW_EDITOR_UI_RENDER_NODE; + + public static Identifier EDITOR_RENDER_GRAPH; + + public static void init(RendererRegistrationEvent event) { + + DRAW_SCENE_RENDER_NODE = event.registerNode(Editor.ID, "draw_scene"); + DRAW_EDITOR_UI_RENDER_NODE = event.registerNode(Editor.ID, "draw_editor_ui"); + + EDITOR_RENDER_GRAPH = event.registerGraph(Editor.ID, "draw_scene", + new RenderGraph(RenderGraph.RenderPath.builder() + .addRenderNode(DRAW_SCENE_RENDER_NODE, EditorDrawSceneRenderNode.class, EditorShaders.MESH_SHADER_PROGRAM_CONFIG) + .addRenderNode(DRAW_EDITOR_UI_RENDER_NODE, DrawEditorUIRenderNode.class, ShaderProgramConfig.EMPTY) + ) + ); + } + +} diff --git a/src/main/java/com/terminalvelocitycabbage/editor/registry/EditorResources.java b/src/main/java/com/terminalvelocitycabbage/editor/registry/EditorResources.java new file mode 100644 index 00000000..8e1767f8 --- /dev/null +++ b/src/main/java/com/terminalvelocitycabbage/editor/registry/EditorResources.java @@ -0,0 +1,13 @@ +package com.terminalvelocitycabbage.editor.registry; + +import com.terminalvelocitycabbage.editor.Editor; +import com.terminalvelocitycabbage.engine.filesystem.resources.ResourceCategory; +import com.terminalvelocitycabbage.templates.events.ResourceCategoryRegistrationEvent; + +public class EditorResources { + + public static void registerResourceCategories(ResourceCategoryRegistrationEvent event) { + ResourceCategory.registerEngineDefaults(event.getRegistry(), Editor.ID); + } + +} diff --git a/src/main/java/com/terminalvelocitycabbage/editor/registry/EditorRoutines.java b/src/main/java/com/terminalvelocitycabbage/editor/registry/EditorRoutines.java new file mode 100644 index 00000000..321e7fd1 --- /dev/null +++ b/src/main/java/com/terminalvelocitycabbage/editor/registry/EditorRoutines.java @@ -0,0 +1,12 @@ +package com.terminalvelocitycabbage.editor.registry; + +import com.terminalvelocitycabbage.editor.Editor; +import com.terminalvelocitycabbage.templates.events.RoutineRegistrationEvent; + +public class EditorRoutines { + + public static void init(RoutineRegistrationEvent event) { + event.registerRoutineFromFile(Editor.ID, "editor_default"); + } + +} diff --git a/src/main/java/com/terminalvelocitycabbage/editor/registry/EditorScenes.java b/src/main/java/com/terminalvelocitycabbage/editor/registry/EditorScenes.java new file mode 100644 index 00000000..a9fcfe5f --- /dev/null +++ b/src/main/java/com/terminalvelocitycabbage/editor/registry/EditorScenes.java @@ -0,0 +1,15 @@ +package com.terminalvelocitycabbage.editor.registry; + +import com.terminalvelocitycabbage.editor.Editor; +import com.terminalvelocitycabbage.engine.registry.Identifier; +import com.terminalvelocitycabbage.templates.events.SceneRegistrationEvent; + +public class EditorScenes { + + public static Identifier EDITOR_SCENE; + + public static void init(SceneRegistrationEvent event) { + EDITOR_SCENE = event.registerSceneFromFile(Editor.ID, "editor"); + } + +} diff --git a/src/main/java/com/terminalvelocitycabbage/editor/registry/EditorShaders.java b/src/main/java/com/terminalvelocitycabbage/editor/registry/EditorShaders.java new file mode 100644 index 00000000..6704ff07 --- /dev/null +++ b/src/main/java/com/terminalvelocitycabbage/editor/registry/EditorShaders.java @@ -0,0 +1,41 @@ +package com.terminalvelocitycabbage.editor.registry; + +import com.terminalvelocitycabbage.editor.Editor; +import com.terminalvelocitycabbage.engine.client.renderer.shader.Shader; +import com.terminalvelocitycabbage.engine.client.renderer.shader.ShaderProgramConfig; +import com.terminalvelocitycabbage.engine.client.renderer.shader.Uniform; +import com.terminalvelocitycabbage.engine.registry.Identifier; + +import static com.terminalvelocitycabbage.editor.data.EditorMeshData.ANIMATED_MESH_FORMAT; +import static com.terminalvelocitycabbage.editor.data.EditorMeshData.MESH_FORMAT; +import static com.terminalvelocitycabbage.engine.filesystem.resources.ResourceCategory.SHADER; + +public class EditorShaders { + + public static final Identifier TEST_VERTEX_SHADER = SHADER.identifierOf(Editor.ID, "default_vertex"); + public static final Identifier ANIMATED_VERTEX_SHADER = SHADER.identifierOf(Editor.ID, "animated_vertex"); + public static final Identifier TEST_FRAGMENT_SHADER = SHADER.identifierOf(Editor.ID, "default_fragment"); + public static final ShaderProgramConfig MESH_SHADER_PROGRAM_CONFIG = ShaderProgramConfig.builder() + .vertexFormat(MESH_FORMAT) + .addShader(Shader.Type.VERTEX, EditorShaders.TEST_VERTEX_SHADER) + .addShader(Shader.Type.FRAGMENT, EditorShaders.TEST_FRAGMENT_SHADER) + .addUniform(new Uniform("textureSampler")) + .addUniform(new Uniform("projectionMatrix")) + .addUniform(new Uniform("viewMatrix")) + .addUniform(new Uniform("modelMatrix")) + .addUniform(new Uniform("directionalLight")) + .build(); + + public static final ShaderProgramConfig ANIMATED_MESH_SHADER_PROGRAM_CONFIG = ShaderProgramConfig.builder() + .vertexFormat(ANIMATED_MESH_FORMAT) + .addShader(Shader.Type.VERTEX, EditorShaders.ANIMATED_VERTEX_SHADER) + .addShader(Shader.Type.FRAGMENT, EditorShaders.TEST_FRAGMENT_SHADER) + .addUniform(new Uniform("textureSampler")) + .addUniform(new Uniform("projectionMatrix")) + .addUniform(new Uniform("viewMatrix")) + .addUniform(new Uniform("modelMatrix")) + .addUniform(new Uniform("boneMatrices")) + .addUniform(new Uniform("directionalLight")) + .build(); + +} diff --git a/src/main/java/com/terminalvelocitycabbage/editor/registry/EditorStates.java b/src/main/java/com/terminalvelocitycabbage/editor/registry/EditorStates.java new file mode 100644 index 00000000..a2b85cbc --- /dev/null +++ b/src/main/java/com/terminalvelocitycabbage/editor/registry/EditorStates.java @@ -0,0 +1,33 @@ +package com.terminalvelocitycabbage.editor.registry; + +import com.terminalvelocitycabbage.editor.Editor; +import com.terminalvelocitycabbage.engine.debug.Log; +import com.terminalvelocitycabbage.engine.event.EventDispatcher; +import com.terminalvelocitycabbage.engine.registry.Identifier; +import com.terminalvelocitycabbage.templates.events.GameStateRegistrationEvent; + +import java.nio.file.Path; + +public class EditorStates { + + public static Identifier ASSET_LOCATION; + + public static void init(EventDispatcher eventDispatcher) { + + eventDispatcher.listenToEvent(GameStateRegistrationEvent.EVENT, e -> { + GameStateRegistrationEvent event = (GameStateRegistrationEvent) e; + + ASSET_LOCATION = event.registerState(Editor.getInstance().getNamespace(), "asset_location", Path.of("unset")); + }); + } + + public static Path getAssetLocation() { + return (Path) Editor.getInstance().getStateHandler().getState(ASSET_LOCATION).getValue(); + } + + public static void setAssetLocation(Path location) { + Editor.getInstance().getStateHandler().getState(ASSET_LOCATION).setValue(location); + Log.info("Asset location set to: " + location); + } + +} diff --git a/src/main/java/com/terminalvelocitycabbage/editor/registry/EditorTextures.java b/src/main/java/com/terminalvelocitycabbage/editor/registry/EditorTextures.java new file mode 100644 index 00000000..c14b3428 --- /dev/null +++ b/src/main/java/com/terminalvelocitycabbage/editor/registry/EditorTextures.java @@ -0,0 +1,61 @@ +package com.terminalvelocitycabbage.editor.registry; + +import com.terminalvelocitycabbage.editor.Editor; +import com.terminalvelocitycabbage.engine.event.EventDispatcher; +import com.terminalvelocitycabbage.engine.registry.Identifier; +import com.terminalvelocitycabbage.templates.events.ConfigureTexturesEvent; + +import static com.terminalvelocitycabbage.engine.filesystem.resources.ResourceCategory.TEXTURE; + +public class EditorTextures { + + public static final Identifier UI_ATLAS = new Identifier(Editor.ID, "atlas", "ui_atlas"); + + public static final Identifier TRANSLATE_ICON = TEXTURE.identifierOf(Editor.ID, "translate_icon"); + public static final Identifier ROTATE_ICON = TEXTURE.identifierOf(Editor.ID, "rotate_icon"); + public static final Identifier SCALE_ICON = TEXTURE.identifierOf(Editor.ID, "scale_icon"); + public static final Identifier CARET_OPEN_ICON = TEXTURE.identifierOf(Editor.ID, "caret_open_icon"); + public static final Identifier CARET_CLOSED_ICON = TEXTURE.identifierOf(Editor.ID, "caret_closed_icon"); + + public static final Identifier SMILE_TEXTURE = TEXTURE.identifierOf(Editor.ID, "smile"); + public static final Identifier SAD_TEXTURE = TEXTURE.identifierOf(Editor.ID, "sad"); + + public static void init(EventDispatcher eventDispatcher) { + + registerAtlas(eventDispatcher, UI_ATLAS); + + registerTexture(eventDispatcher, TRANSLATE_ICON, UI_ATLAS); + registerTexture(eventDispatcher, ROTATE_ICON, UI_ATLAS); + registerTexture(eventDispatcher, SCALE_ICON, UI_ATLAS); + registerTexture(eventDispatcher, CARET_OPEN_ICON, UI_ATLAS); + registerTexture(eventDispatcher, CARET_CLOSED_ICON, UI_ATLAS); + + registerStandaloneTexture(eventDispatcher, SMILE_TEXTURE); + registerStandaloneTexture(eventDispatcher, SAD_TEXTURE); + } + + private static void registerStandaloneTexture(EventDispatcher eventDispatcher, Identifier textureIdentifier) { + eventDispatcher.listenToEvent(ConfigureTexturesEvent.EVENT, e -> { + ConfigureTexturesEvent event = (ConfigureTexturesEvent) e; + event.addTexture(textureIdentifier); + }); + } + + private static void registerAtlas(EventDispatcher eventDispatcher, Identifier atlasIdentifier) { + eventDispatcher.listenToEvent(ConfigureTexturesEvent.EVENT, e -> { + ConfigureTexturesEvent event = (ConfigureTexturesEvent) e; + event.registerAtlas(atlasIdentifier); + }); + } + + private static void registerTexture(EventDispatcher eventDispatcher, Identifier textureIdentifier, Identifier... atlasIdentifiers) { + eventDispatcher.listenToEvent(ConfigureTexturesEvent.EVENT, e -> { + ConfigureTexturesEvent event = (ConfigureTexturesEvent) e; + event.addTexture(textureIdentifier, atlasIdentifiers); + }); + } + + public static void generateAtlases() { + Editor.getInstance().getTextureCache().generateAtlas(UI_ATLAS); + } +} diff --git a/src/main/java/com/terminalvelocitycabbage/editor/rendernodes/DrawEditorUIRenderNode.java b/src/main/java/com/terminalvelocitycabbage/editor/rendernodes/DrawEditorUIRenderNode.java new file mode 100644 index 00000000..ee37ba6b --- /dev/null +++ b/src/main/java/com/terminalvelocitycabbage/editor/rendernodes/DrawEditorUIRenderNode.java @@ -0,0 +1,220 @@ +package com.terminalvelocitycabbage.editor.rendernodes; + +import com.terminalvelocitycabbage.editor.Editor; +import com.terminalvelocitycabbage.editor.registry.EditorTextures; +import com.terminalvelocitycabbage.engine.client.renderer.shader.ShaderProgramConfig; +import com.terminalvelocitycabbage.engine.client.scene.Scene; +import com.terminalvelocitycabbage.engine.client.ui.UI; +import com.terminalvelocitycabbage.engine.client.window.WindowProperties; +import com.terminalvelocitycabbage.engine.debug.Log; +import com.terminalvelocitycabbage.engine.ecs.Entity; +import com.terminalvelocitycabbage.engine.filesystem.GameFileSystem; +import com.terminalvelocitycabbage.engine.filesystem.resources.Resource; +import com.terminalvelocitycabbage.engine.filesystem.resources.ResourceCategory; +import com.terminalvelocitycabbage.engine.registry.Identifier; +import com.terminalvelocitycabbage.engine.state.State; +import com.terminalvelocitycabbage.engine.util.HeterogeneousMap; +import com.terminalvelocitycabbage.engine.util.StringUtils; +import com.terminalvelocitycabbage.templates.ecs.components.NameComponent; +import com.terminalvelocitycabbage.templates.events.UIClickEvent; + +import java.util.Map; + +import static com.terminalvelocitycabbage.engine.client.ui.UI.LayoutDirection.TOP_TO_BOTTOM; +import static com.terminalvelocitycabbage.engine.client.ui.UI.UIUnit.PIXELS; + +public class DrawEditorUIRenderNode extends EditorUIRenderNode { + + private Scene currentScene; + + public DrawEditorUIRenderNode(ShaderProgramConfig shaderProgramConfig) { + super(shaderProgramConfig); + } + + @Override + public void execute(Scene scene, WindowProperties properties, HeterogeneousMap renderConfig, long deltaTime) { + this.currentScene = scene; + super.execute(scene, properties, renderConfig, deltaTime); + } + + @Override + protected void declareUI() { + + State selectedEntity = useState("selected_entity", null); + State selectedAsset = useState("selected_asset", null); + + container(props(UI.pT(10, PIXELS), UI.gap(5, PIXELS), UI.backgroundColor(BACKGROUND_COLOR), UI.grow(), UI.layout(TOP_TO_BOTTOM)), () -> { + //optionsBar(); + container(props(UI.gap(5, PIXELS), UI.grow(), UI.backgroundColor(BACKGROUND_COLOR), UI.layout(UI.LayoutDirection.LEFT_TO_RIGHT)), () -> { + hierarchy(selectedEntity); + container(props(UI.gap(5, PIXELS), UI.backgroundColor(BACKGROUND_COLOR), UI.grow(), UI.layout(TOP_TO_BOTTOM)), () -> { + scene(); + browser(selectedEntity, selectedAsset); + }); + inspector(selectedEntity, selectedAsset); + }); + }); + } + + //TODO this might be useful in the future, but for now it's really not needed + private void optionsBar() { + container(props( + UI.growX(), UI.fitY(), UI.backgroundColor(BACKGROUND_COLOR), UI.direction(UI.LayoutDirection.LEFT_TO_RIGHT), + UI.p(5, PIXELS), UI.gap(5, PIXELS) + ), () -> { + text("Options Bar", props(UI.font(REGULAR_FONT), UI.textSize(15, PIXELS), UI.textColor(LABEL_COLOR))); + }); + } + + private void hierarchy(State selectedEntity) { + container(props( + UI.growY(), UI.width(240, PIXELS), UI.backgroundColor(ELEMENT_COLOR), UI.direction(UI.LayoutDirection.LEFT_TO_RIGHT), + UI.gap(5, PIXELS) + ), () -> { + tabbedMenu("hierarchyTabs", + new Tab("Scene Hierarchy", () -> { + if (currentScene != null) { + verticalScrollableContainer("scene_tree_container", () -> { + for (Entity entity : Editor.getInstance().getManager().getEntities()) { + entitySelector(selectedEntity, entity); + } + }); + } else { + text("No scene active", props(UI.font(REGULAR_FONT), UI.textSize(15, PIXELS), UI.textColor(LABEL_COLOR))); + } + }) + ); + }); + } + + private void entitySelector(State selectedEntity, Entity entity) { + int buttonID = id(entity.getID().toString() + "_button"); + if (heardEvent(buttonID, UIClickEvent.EVENT) instanceof UIClickEvent) { + selectedEntity.setValue(entity); + Log.info("Selected entity: " + entity.getID()); + } + + boolean isSelected = selectedEntity.getValue() != null && selectedEntity.getValue().getID().equals(entity.getID()); + String name = entity.getID().toString(); + if (entity.hasComponent(NameComponent.class)) { + name = entity.getComponent(NameComponent.class).getName(); + } + text(buttonID, name, props(UI.font(REGULAR_FONT), UI.textSize(15, PIXELS), UI.textColor(isSelected ? TEXT_COLOR : LABEL_COLOR))); + } + + private void commands() { + container(props( + UI.fit(), UI.backgroundColor(BACKGROUND_COLOR), UI.direction(UI.LayoutDirection.LEFT_TO_RIGHT), + UI.gap(5, PIXELS), UI.floatParent(), UI.attachTo(UI.FloatingAttachPointType.TOP_LEFT, UI.FloatingAttachPointType.TOP_LEFT), + UI.floatOffsetX(5, PIXELS), UI.floatOffsetY(5, PIXELS) + ), () -> { + iconButton(EditorTextures.TRANSLATE_ICON, EditorTextures.UI_ATLAS, () -> Log.info("Translate button pressed")); + iconButton(EditorTextures.ROTATE_ICON, EditorTextures.UI_ATLAS, () -> Log.info("Rotate button pressed")); + iconButton(EditorTextures.SCALE_ICON, EditorTextures.UI_ATLAS, () -> Log.info("Scale button pressed")); + }); + } + + private void scene() { + container(props( + UI.grow(), UI.backgroundColor(TRANSPARENT) + ), () -> { + tabbedMenu("sceneTabs", + new Tab("3D", () -> { + container(props(UI.grow(), UI.backgroundColor(TRANSPARENT)), () -> { + commands(); + }); + }), + new Tab("2D", () -> { + container(props(UI.grow(), UI.backgroundColor(TRANSPARENT)), () -> { + commands(); + }); + }) + ); + }); + } + + private void browser(State selectedEntity, State selectedAsset) { + container(props( + UI.growX(), UI.height(240, PIXELS), UI.backgroundColor(BACKGROUND_COLOR) + ), () -> { + tabbedMenu("browserTabs", + new Tab("Filesystem", () -> assetBrowser(selectedEntity, selectedAsset)) + ); + }); + } + + private void assetBrowser(State selectedEntity, State selectedAsset) { + + State selectedCategory = useState(null); + + Editor editor = (Editor) Editor.getInstance(); + GameFileSystem fileSystem = editor.getFileSystem(); + + container(props(UI.backgroundColor(BORDER_COLOR), UI.grow(), UI.gap(5, PIXELS), UI.direction(UI.LayoutDirection.LEFT_TO_RIGHT)), () -> { + container(props(UI.width(200, PIXELS), UI.growY(),UI.backgroundColor(ELEMENT_COLOR), UI.layout(TOP_TO_BOTTOM)), () -> { + verticalScrollableContainer("asset_category_container", () -> { + for (ResourceCategory category : fileSystem.getResourceCategoryRegistry().getRegistryContents().values()) { + resourceCategorySelector(selectedCategory, category); + } + }); + }); + container(props(UI.grow(), UI.backgroundColor(ELEMENT_COLOR)), () -> { + verticalScrollableContainer("asset_container_by_category", () -> { + if (selectedCategory.getValue() != null) { + Map resourcesOfType = fileSystem.getResourcesOfType(selectedCategory.getValue()); + for (Identifier resourceIdentifier : resourcesOfType.keySet()) { + assetSelector(selectedAsset, resourceIdentifier); + } + if (resourcesOfType.isEmpty()) { + text("No " + selectedCategory.getValue().plural() + " registered to this filesystem", props(UI.font(REGULAR_FONT), UI.textSize(15, PIXELS), UI.textColor(LABEL_COLOR))); + } + } else { + text("Select a resource category to view resources", props(UI.font(REGULAR_FONT), UI.textSize(15, PIXELS), UI.textColor(LABEL_COLOR))); + } + }); + }); + }); + } + + private void assetSelector(State selectedAsset, Identifier identifier) { + int buttonID = id(identifier.toString() + "_button"); + if (heardEvent(buttonID, UIClickEvent.EVENT) instanceof UIClickEvent) selectedAsset.setValue(identifier); + + boolean isSelected = selectedAsset.getValue() != null && selectedAsset.getValue().equals(identifier); + text(buttonID, identifier.toString(), props(UI.font(REGULAR_FONT), UI.textSize(15, PIXELS), UI.textColor(isSelected ? TEXT_COLOR : LABEL_COLOR))); + } + + private void resourceCategorySelector(State selectedCategory, ResourceCategory category) { + int buttonID = id(category.name() + "_button"); + if (heardEvent(buttonID, UIClickEvent.EVENT) instanceof UIClickEvent) selectedCategory.setValue(category); + + text(buttonID, StringUtils.convertSnakeCaseToCapitalized(category.plural()), props(UI.font(REGULAR_FONT), UI.textSize(15, PIXELS), UI.textColor(TEXT_COLOR))); + } + + private void inspector(State selectedEntity, State selectedAsset) { + container(props( + UI.growY(), UI.width(340, PIXELS), UI.backgroundColor(ELEMENT_COLOR), + UI.gap(5, PIXELS) + ), () -> { + tabbedMenu("inspectorTabs", + new Tab("Element Inspector", () -> elementInspector(selectedEntity)), + new Tab("State Inspector", () -> { + text("State Inspector TODO", props(UI.font(REGULAR_FONT), UI.textSize(15, PIXELS), UI.textColor(LABEL_COLOR))); + }) + ); + }); + } + + private void elementInspector(State selectedEntity) { + container(props(UI.grow(), UI.direction(TOP_TO_BOTTOM)), () -> { + if (selectedEntity.getValue() != null) { + for (Class componentClass : selectedEntity.getValue().getComponents().keySet()) { + component(componentClass); + horizontalDivider(); + } + } else { + text("Select an entity to view its components", props(UI.font(REGULAR_FONT), UI.textSize(15, PIXELS), UI.textColor(LABEL_COLOR))); + } + }); + } +} diff --git a/src/main/java/com/terminalvelocitycabbage/editor/rendernodes/EditorDrawSceneRenderNode.java b/src/main/java/com/terminalvelocitycabbage/editor/rendernodes/EditorDrawSceneRenderNode.java new file mode 100644 index 00000000..01eebd93 --- /dev/null +++ b/src/main/java/com/terminalvelocitycabbage/editor/rendernodes/EditorDrawSceneRenderNode.java @@ -0,0 +1,109 @@ +package com.terminalvelocitycabbage.editor.rendernodes; + +import com.terminalvelocitycabbage.editor.ecs.components.EditorCameraComponent; +import com.terminalvelocitycabbage.engine.client.ClientBase; +import com.terminalvelocitycabbage.engine.client.renderer.model.Model; +import com.terminalvelocitycabbage.engine.client.renderer.shader.ShaderProgramConfig; +import com.terminalvelocitycabbage.engine.client.scene.Scene; +import com.terminalvelocitycabbage.engine.client.window.WindowProperties; +import com.terminalvelocitycabbage.engine.ecs.Entity; +import com.terminalvelocitycabbage.engine.graph.RenderNode; +import com.terminalvelocitycabbage.engine.registry.Identifier; +import com.terminalvelocitycabbage.engine.util.HeterogeneousMap; +import com.terminalvelocitycabbage.templates.ecs.components.*; +import org.joml.Matrix4f; + +import java.util.ArrayList; +import java.util.Comparator; +import java.util.List; + +public class EditorDrawSceneRenderNode extends RenderNode { + + public EditorDrawSceneRenderNode(ShaderProgramConfig shaderProgramConfig) { + super(shaderProgramConfig); + } + + @Override + public void execute(Scene scene, WindowProperties properties, HeterogeneousMap renderConfig, long deltaTime) { + + var client = ClientBase.getInstance(); + var manager = client.getManager(); + + var cameraEntity = getCamera(); + if (cameraEntity == null) return; + + var camera = cameraEntity.getComponent(CameraComponent.class); + var transformation = cameraEntity.getComponent(TransformationComponent.class).getTransformation(); + + var shaderProgram = getShaderProgram(); + if (properties.isResized()) camera.updateProjectionMatrix(properties.getWidth(), properties.getHeight()); + + shaderProgram.bind(); + if (shaderProgram.getUniform("textureSampler") != null) shaderProgram.getUniform("textureSampler").setUniform(0); + if (shaderProgram.getUniform("projectionMatrix") != null) shaderProgram.getUniform("projectionMatrix").setUniform(camera.getProjectionMatrix()); + if (shaderProgram.getUniform("viewMatrix") != null) shaderProgram.getUniform("viewMatrix").setUniform(camera.getViewMatrix(transformation)); + + var lightEntity = manager.getFirstEntityWith(DirectionalLightComponent.class); + if (lightEntity != null && shaderProgram.getConfig().getUniform("directionalLight") != null) { + shaderProgram.getUniform("directionalLight").setUniform(lightEntity.getComponent(DirectionalLightComponent.class).getLight()); + } + + //Sort entities for efficient rendering (by texture then by model) + List entities = new ArrayList<>(manager.getEntitiesWith(ModelComponent.class, TransformationComponent.class)); + entities.sort(Comparator + .comparingInt((Entity entity) -> client.getTextureCache().getTexture(client.getModelRegistry().get(entity.getComponent(ModelComponent.class).getModel()).textureIdentifier()).getTextureID()) + .thenComparing(entity -> entity.getComponent(ModelComponent.class).getModel().hashCode()) + ); + + //Render entities + Identifier lastTextureID = null; + Identifier lastModelID = null; + Model model; + for (Entity entity : entities) { + + //Update the transformation to that of this entity + shaderProgram.getUniform("modelMatrix").setUniform(entity.getComponent(TransformationComponent.class).getTransformationMatrix()); + + //Handle animations + var modelIdentifier = entity.getComponent(ModelComponent.class).getModel(); + model = client.getModelRegistry().get(modelIdentifier); + if (model.skeleton() != null && shaderProgram.getConfig().getUniform("boneMatrices") != null) { + Matrix4f[] matrices; + if (entity.hasComponent(AnimationControllerComponent.class)) { + var animComp = entity.getComponent(AnimationControllerComponent.class); + matrices = animComp.getBoneMatrices(model); + } else { + matrices = model.skeleton().bindPoseMatrices(); + } + shaderProgram.getUniform("boneMatrices").setUniform(matrices); + } + + //Early draw if this is the same model as the last entity (save on uploads) + if (model.compiledMesh().getFormat().equals(shaderProgram.getConfig().getVertexFormat())) { + if (modelIdentifier.equals(lastModelID)) { + model.draw(); + continue; + } + + lastModelID = modelIdentifier; + + //Optimization: only bind texture and mesh if they've changed since the last entity + var textureIdentifier = model.textureIdentifier(); + if (textureIdentifier != null && !textureIdentifier.equals(lastTextureID)) { + model.bindTexture(client.getTextureCache()); + lastTextureID = textureIdentifier; + } + + model.bind(); + model.draw(); + } + } + + shaderProgram.unbind(); + } + + private Entity getCamera() { + var manager = ClientBase.getInstance().getManager(); + return manager.getFirstEntityWith(CameraComponent.class, TransformationComponent.class); + } +} diff --git a/src/main/java/com/terminalvelocitycabbage/editor/rendernodes/EditorUIRenderNode.java b/src/main/java/com/terminalvelocitycabbage/editor/rendernodes/EditorUIRenderNode.java new file mode 100644 index 00000000..68467899 --- /dev/null +++ b/src/main/java/com/terminalvelocitycabbage/editor/rendernodes/EditorUIRenderNode.java @@ -0,0 +1,215 @@ +package com.terminalvelocitycabbage.editor.rendernodes; + +import com.terminalvelocitycabbage.editor.hints.EditorHint; +import com.terminalvelocitycabbage.editor.registry.EditorFonts; +import com.terminalvelocitycabbage.engine.client.renderer.shader.ShaderProgramConfig; +import com.terminalvelocitycabbage.engine.client.ui.UI; +import com.terminalvelocitycabbage.engine.client.ui.UIRenderNode; +import com.terminalvelocitycabbage.engine.registry.Identifier; +import com.terminalvelocitycabbage.engine.state.State; +import com.terminalvelocitycabbage.engine.util.Color; +import com.terminalvelocitycabbage.templates.events.UICharInputEvent; +import com.terminalvelocitycabbage.templates.events.UIClickEvent; +import com.terminalvelocitycabbage.templates.events.UIScrollEvent; + +import static com.terminalvelocitycabbage.editor.registry.EditorTextures.*; +import static com.terminalvelocitycabbage.engine.client.ui.UI.UIUnit.PIXELS; + +public abstract class EditorUIRenderNode extends UIRenderNode { + + public static final Color BLACK = Color.ofHex("#000000"); + public static final Color ULTRA_DARK = Color.ofHex("#191919"); + public static final Color DARK = Color.ofHex("#262626"); + public static final Color MEDIUM = Color.ofHex("#303030"); + public static final Color MEDIUM_LIGHT = Color.ofHex("#383838"); + public static final Color LIGHT = Color.ofHex("#474747"); + public static final Color ULTRALIGHT = Color.ofHex("#fafafa"); + public static final Color WHITE = Color.ofHex("#ffffff"); + + public static final Color TRANSPARENT = new Color(0, 0, 0, 0); + + public static final Color LIGHT_RED = Color.ofHex("#ef4444"); + public static final Color RED = Color.ofHex("#dc2626"); + public static final Color DARK_RED = Color.ofHex("#b91c1c"); + + public static final Color LIGHT_ORANGE = Color.ofHex("#f97316"); + public static final Color ORANGE = Color.ofHex("#ea580c"); + public static final Color DARK_ORANGE = Color.ofHex("#c2410c"); + + public static final Color LIGHT_YELLOW = Color.ofHex("#fbbf24"); + public static final Color YELLOW = Color.ofHex("#f59e0b"); + public static final Color DARK_YELLOW = Color.ofHex("#d97706"); + + public static final Color LIGHT_GREEN = Color.ofHex("#84cc16"); + public static final Color GREEN = Color.ofHex("#65a30d"); + public static final Color DARK_GREEN = Color.ofHex("#4d7c0f"); + + public static final Color LIGHT_BLUE = Color.ofHex("#0ea5e9"); + public static final Color BLUE = Color.ofHex("#0284c7"); + public static final Color DARK_BLUE = Color.ofHex("#0369a1"); + + public static final Color LIGHT_PURPLE = Color.ofHex("#8b5cf6"); + public static final Color PURPLE = Color.ofHex("#7c3aed"); + public static final Color DARK_PURPLE = Color.ofHex("#6d28d9"); + + public static final Color BACKGROUND_COLOR = BLACK; + public static final Color BORDER_COLOR = DARK; + public static final Color ELEMENT_COLOR = MEDIUM; + public static final Color FIELD_COLOR = DARK; + public static final Color TEXT_COLOR = WHITE; + public static final Color LABEL_COLOR = LIGHT; + + public static final Identifier REGULAR_FONT = EditorFonts.REGULAR; + + public EditorUIRenderNode(ShaderProgramConfig shaderProgramConfig) { + super(shaderProgramConfig); + } + + @Override + protected Identifier[] getInterestedEvents() { + return new Identifier[]{ + UIClickEvent.EVENT, + UIScrollEvent.EVENT, + UICharInputEvent.EVENT + }; + } + + public record Tab(String name, Runnable action) {} + + public void tabbedMenu(String menuName, Tab... tabs) { + + int tabbedMenuID = id(menuName); + + State selectedTab = useState(0); + + // Overall container for tabs + container(tabbedMenuID, props( + UI.grow(), UI.backgroundColor(TRANSPARENT), UI.direction(UI.LayoutDirection.TOP_TO_BOTTOM) + ), () -> { + //Tabs + container(props( + UI.growX(), UI.fitY(), UI.backgroundColor(BACKGROUND_COLOR), UI.direction(UI.LayoutDirection.LEFT_TO_RIGHT), + UI.gap(5, PIXELS) + ), () -> { + for (int i = 0; i < tabs.length; i++) { + if (tab(tabs[i].name(), i == selectedTab.getValue())) selectedTab.setValue(i); + } + }); + //Selected tab content + container(props( + UI.grow(), UI.backgroundColor(TRANSPARENT) + ), () -> tabs[selectedTab.getValue()].action().run()); + }); + } + + private boolean tab(String tabName, boolean selected) { + + int tabID = id("tab_" + tabName); + boolean hovered = isHovered(tabID); + + container(tabID, props( + UI.height(24, PIXELS), UI.backgroundColor((selected || hovered) ? ELEMENT_COLOR : BORDER_COLOR), UI.pY(4, PIXELS), UI.pX(10, PIXELS), + UI.roundedT(5) + ), () -> { + text(tabName, props( + UI.font(REGULAR_FONT), UI.textSize(15, PIXELS), UI.textColor(selected ? TEXT_COLOR : LABEL_COLOR) + )); + }); + + return heardEvent(tabID, UIClickEvent.EVENT) instanceof UIClickEvent; + } + + public void iconButton(Identifier icon, Identifier atlas, Runnable action) { + + int buttonID = id("icon_button_" + icon.toString()); + + if (heardEvent(buttonID, UIClickEvent.EVENT) instanceof UIClickEvent) action.run(); + + container(buttonID, props( + UI.height(24, PIXELS), UI.width(24, PIXELS), UI.backgroundColor(ELEMENT_COLOR), UI.rounded(5), + UI.border(2), UI.borderColor(MEDIUM_LIGHT), UI.p(3, PIXELS) + ), () -> { + image(props( + UI.width(16, PIXELS), UI.height(16, PIXELS), UI.backgroundColor(TRANSPARENT), + UI.bgImage(icon, atlas), UI.imageBackground(TRANSPARENT) + )); + }); + } + + public void button(int buttonID, String label, Runnable action) { + + if (heardEvent(buttonID, UIClickEvent.EVENT) instanceof UIClickEvent) action.run(); + + container(buttonID, props( + UI.height(24, PIXELS), UI.fitX(), UI.backgroundColor(ELEMENT_COLOR), UI.rounded(5), + UI.border(2), UI.borderColor(MEDIUM_LIGHT), UI.p(3, PIXELS) + ), () -> { + text(label, props(UI.font(REGULAR_FONT), UI.textSize(15, PIXELS), UI.textColor(TEXT_COLOR))); + }); + } + + public void horizontalDivider() { + container(props(UI.height(3, PIXELS), UI.backgroundColor(ULTRA_DARK), UI.growX()), () -> {}); + } + + public void verticalScrollableContainer(String id, Runnable action) { + scrollableContainer(id, + props(UI.pB(25, PIXELS), UI.backgroundColor(ELEMENT_COLOR), UI.layout(UI.LayoutDirection.TOP_TO_BOTTOM), UI.grow(), UI.p(10, PIXELS), UI.gap(5, PIXELS)), + props(UI.backgroundColor(LABEL_COLOR), UI.width(10, PIXELS), UI.fitY()), + action); + } + + public void component(Class componentClass) { + + //TODO this is not needed if the compoent doesn't have any fields so + var hasFields = componentClass.getDeclaredFields().length > 0; + + State expanded = useState("open_component_" + componentClass.getSimpleName(), false); + + int caratContainer = id("icon_button_" + componentClass.getSimpleName()); + + if (heardEvent(caratContainer, UIClickEvent.EVENT) instanceof UIClickEvent) { + expanded.setValue(!expanded.getValue()); + } + + container(props(UI.pX(5, PIXELS), UI.pY(2, PIXELS), UI.direction(UI.LayoutDirection.TOP_TO_BOTTOM), UI.growX()), () -> { + container(props(UI.direction(UI.LayoutDirection.LEFT_TO_RIGHT), UI.backgroundColor(ELEMENT_COLOR), + UI.alignY(UI.VerticalAlignment.CENTER), UI.gap(5, PIXELS), UI.growX(), UI.fitY(), UI.p(5, PIXELS)), () -> { + container(caratContainer, props(UI.width(14, PIXELS), UI.height(14, PIXELS), UI.mT(3, PIXELS)), () -> { + if (hasFields) { + image(props(UI.bgImage(expanded.getValue() ? CARET_OPEN_ICON : CARET_CLOSED_ICON, UI_ATLAS), + UI.width(14, PIXELS), UI.height(14, PIXELS), UI.mT(2, PIXELS))); + } else { + container(props(UI.width(14, PIXELS), UI.height(14, PIXELS)), () -> {}); + } + }); + container(props(UI.width(16, PIXELS), UI.height(16, PIXELS), UI.rounded(8), UI.backgroundColor(BLUE), UI.mT(4, PIXELS)), () -> { + //Icon for components goes here eventually - configure it by annotation like Godot + }); + container(props(UI.backgroundColor(FIELD_COLOR), UI.rounded(5), UI.pX(6, PIXELS), UI.pT(4, PIXELS), + UI.pB(2, PIXELS), UI.growX(), UI.border(1), UI.borderColor(MEDIUM_LIGHT)), () -> { + EditorHint.ComponentName hintName = (EditorHint.ComponentName) componentClass.getAnnotation(EditorHint.ComponentName.class); + text(hintName == null ? componentClass.getSimpleName() : hintName.name(), props(UI.font(REGULAR_FONT), UI.textSize(15, PIXELS), UI.textColor(TEXT_COLOR))); + }); + }); + //Component fields + if (expanded.getValue()) { + container(props(UI.pL(24, PIXELS), UI.direction(UI.LayoutDirection.LEFT_TO_RIGHT)), () -> { + container(props(UI.gap(2, PIXELS), UI.direction(UI.LayoutDirection.TOP_TO_BOTTOM), UI.fit(), UI.p(5, PIXELS)), () -> { + //TODO add editor name for type + for (var field : componentClass.getDeclaredFields()) { + text(field.getType().getSimpleName(), props(UI.font(REGULAR_FONT), UI.textSize(15, PIXELS), UI.textColor(LABEL_COLOR))); + } + }); + container(props(UI.gap(2, PIXELS), UI.direction(UI.LayoutDirection.TOP_TO_BOTTOM), UI.growX(), UI.fitY(), UI.p(5, PIXELS)), () -> { + //TODO add editor component for configuring by type + for (var field : componentClass.getDeclaredFields()) { + text(field.getName(), props(UI.font(REGULAR_FONT), UI.textSize(15, PIXELS), UI.textColor(LABEL_COLOR))); + } + }); + }); + } + }); + + } +} diff --git a/src/main/java/com/terminalvelocitycabbage/engine/client/renderer/elements/VertexFormat.java b/src/main/java/com/terminalvelocitycabbage/engine/client/renderer/elements/VertexFormat.java index 491e8658..c9839334 100644 --- a/src/main/java/com/terminalvelocitycabbage/engine/client/renderer/elements/VertexFormat.java +++ b/src/main/java/com/terminalvelocitycabbage/engine/client/renderer/elements/VertexFormat.java @@ -2,10 +2,7 @@ import com.terminalvelocitycabbage.engine.debug.Log; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; -import java.util.Map; +import java.util.*; /** * Represents and ordered list of vertex attributes as used by a shader program. @@ -109,6 +106,19 @@ public VertexFormat build() { } + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + VertexFormat that = (VertexFormat) o; + return stride == that.stride && numComponents == that.numComponents && Objects.equals(vertexAttributeOffsetMap, that.vertexAttributeOffsetMap) && Objects.equals(attributes, that.attributes); + } + + @Override + public int hashCode() { + return Objects.hash(stride, numComponents, vertexAttributeOffsetMap, attributes); + } + @Override public String toString() { return "VertexFormat{" + diff --git a/src/main/java/com/terminalvelocitycabbage/engine/client/renderer/model/MeshCache.java b/src/main/java/com/terminalvelocitycabbage/engine/client/renderer/model/MeshCache.java index ea2771f8..859ecde5 100644 --- a/src/main/java/com/terminalvelocitycabbage/engine/client/renderer/model/MeshCache.java +++ b/src/main/java/com/terminalvelocitycabbage/engine/client/renderer/model/MeshCache.java @@ -43,6 +43,7 @@ public MeshCache(Registry modelConfigRegistry, Registry mode } var compiledMesh = Mesh.of(meshesToMerge); + compiledMesh.init(); var atlasIdentifier = config.meshTexturePairs().get(0).textureIdentifier(); modelRegistry.register(identifier, new Model(compiledMesh, atlasIdentifier, config.skeleton()), true); }); diff --git a/src/main/java/com/terminalvelocitycabbage/engine/client/ui/UIRenderNode.java b/src/main/java/com/terminalvelocitycabbage/engine/client/ui/UIRenderNode.java index 609ea7e8..562052f3 100644 --- a/src/main/java/com/terminalvelocitycabbage/engine/client/ui/UIRenderNode.java +++ b/src/main/java/com/terminalvelocitycabbage/engine/client/ui/UIRenderNode.java @@ -171,6 +171,10 @@ protected UIElement container(int id, ElementDeclaration declaration, Runnable c return new UIElement(id, getUIContext()); } + protected UIElement text(int id, String text, String props) { + return text(id, text, TextElementConfig.of(props)); + } + protected UIElement text(String text, String props) { return text(text, TextElementConfig.of(props)); } @@ -332,7 +336,7 @@ protected UIElement scrollableContainer(int id, ElementDeclaration containerDecl UIElement containerElement = new UIElement(id, getUIContext()); UIElement contentElement = new UIElement(contentContainerId, getUIContext()); - float containerHeight = containerElement.height(); + float containerHeight = containerElement.height() - 10; float contentHeight = contentElement.preferredHeight(); float scrollableRange = Math.max(0, contentHeight - containerHeight); @@ -382,7 +386,8 @@ protected UIElement scrollableContainer(int id, ElementDeclaration containerDecl .wrap(containerDecl.layout().wrap()) .childGap(containerDecl.layout().childGap()) .childAlignment(containerDecl.layout().childAlignment()) - .aspectRatio(containerDecl.layout().aspectRatio())) + .aspectRatio(containerDecl.layout().aspectRatio()) + .padding(containerDecl.layout().padding())) .build(); var finalScrollbarDecl = new ElementDeclaration( diff --git a/src/main/java/com/terminalvelocitycabbage/engine/ecs/Entity.java b/src/main/java/com/terminalvelocitycabbage/engine/ecs/Entity.java index 9397ea39..b238f0b7 100644 --- a/src/main/java/com/terminalvelocitycabbage/engine/ecs/Entity.java +++ b/src/main/java/com/terminalvelocitycabbage/engine/ecs/Entity.java @@ -44,6 +44,13 @@ protected void setManager(Manager manager) { this.manager = manager; } + /** + * @return a map containing all components that define this entity + */ + public Map, Component> getComponents() { + return components; + } + public T addComponent(Class componentClass) { if (hasComponent(componentClass)) { Log.warn("Tried to add component " + componentClass.getName() + " to entity with id " + getID() + " which already contains it"); @@ -69,18 +76,25 @@ private void addConfiguredComponent(Component component) { } } - /** - * @param componentClass The class of the component you want to retrieve from this entity - * @param A class that implements {@link Component} - * @return The component requested or null - */ @SuppressWarnings("unchecked") public T getComponent(Class componentClass) { - if (!hasComponent(componentClass)) { + T component = getComponentInternal(componentClass); + if (component == null) { Log.warn("Entity does not contain component " + componentClass.getName() + " but it was attempted to be retrieved."); - return null; } - return getComponentUnsafe(componentClass); + return component; + } + + @SuppressWarnings("unchecked") + private T getComponentInternal(Class componentClass) { + T component = (T) components.get(componentClass); + if (component != null) return component; + for (Component c : components.values()) { + if (componentClass.isInstance(c)) { + return (T) c; + } + } + return null; } /** @@ -91,7 +105,7 @@ public T getComponent(Class componentClass) { * @return The component requested or null */ public T getComponentUnsafe(Class componentClass) { - return (T) components.get(componentClass); + return getComponentInternal(componentClass); } /** @@ -102,7 +116,7 @@ public T getComponentUnsafe(Class componentClass) { * @return A boolean representing whether this entity contains the specified component */ public boolean hasComponent(Class componentClass) { - return components.containsKey(componentClass); + return getComponentInternal(componentClass) != null; } /** diff --git a/src/main/java/com/terminalvelocitycabbage/engine/ecs/Manager.java b/src/main/java/com/terminalvelocitycabbage/engine/ecs/Manager.java index 7d99d606..6233a5fc 100644 --- a/src/main/java/com/terminalvelocitycabbage/engine/ecs/Manager.java +++ b/src/main/java/com/terminalvelocitycabbage/engine/ecs/Manager.java @@ -10,6 +10,7 @@ import com.terminalvelocitycabbage.engine.util.touples.Pair; import javax.management.ReflectionException; +import java.lang.reflect.Modifier; import java.util.*; /** @@ -84,7 +85,9 @@ public void registerComponent(Class componentType) { public void registerComponent(Class componentType, int initialPoolSize) { String stringId = componentType.getSimpleName().toLowerCase().replace("component", ""); componentNameToClassMap.put(stringId, componentType); - componentPool.getPool(componentType, true, initialPoolSize); + if (!Modifier.isAbstract(componentType.getModifiers()) && !componentType.isInterface()) { + componentPool.getPool(componentType, true, initialPoolSize); + } activeComponents.put(componentType, new ArrayList<>()); } @@ -188,11 +191,22 @@ private String generateKeyFromComponentQuery(Class... compo * @param componentType The type of component that was modified on an entity and which will require a new cache generated */ protected void invalidateQueryCacheForComponents(Class componentType) { + if (componentType == null) return; List keysToRemove = new ArrayList<>(); entityQueryCache.keySet().forEach(key -> { if (key.contains(componentType.getName())) keysToRemove.add(key); }); keysToRemove.forEach(key -> entityQueryCache.remove(key)); + + //Recurse for superclasses and interfaces to invalidate queries for base types + if (componentType.getSuperclass() != null && Component.class.isAssignableFrom(componentType.getSuperclass())) { + invalidateQueryCacheForComponents((Class) componentType.getSuperclass()); + } + for (Class iface : componentType.getInterfaces()) { + if (Component.class.isAssignableFrom(iface)) { + invalidateQueryCacheForComponents((Class) iface); + } + } } /** @@ -204,7 +218,7 @@ public final Set getEntitiesWith(Class... component //Early exit for unregistered components for (Class componentType : componentTypes) { - if (!componentPool.hasType(componentType)) Log.crash("No component of type found: " + componentType); + if (!activeComponents.containsKey(componentType)) Log.crash("No component of type found: " + componentType); } //check the cache if this query has been made before and is the same as before @@ -215,11 +229,17 @@ public final Set getEntitiesWith(Class... component List> entitySets = new ArrayList<>(); for (Class componentType : componentTypes) { - List entities = activeComponents.get(componentType); - if (entities == null) { + Set matchingEntities = new HashSet<>(); + //Polymorphic lookup: find all entities that have a component that is a subtype of componentType + for (Map.Entry, List> entry : activeComponents.entrySet()) { + if (componentType.isAssignableFrom(entry.getKey())) { + matchingEntities.addAll(entry.getValue()); + } + } + if (matchingEntities.isEmpty()) { return Collections.emptySet(); } - entitySets.add(new HashSet<>(entities)); + entitySets.add(matchingEntities); } // Sort the sets by their size (smallest first for faster intersection) @@ -242,13 +262,11 @@ public final Set getEntitiesWith(Class... component return common; } - /** - * @param componentTypes a list of component types which the entity returned must have - * @return an entity which matches this selection - */ @SafeVarargs public final Entity getFirstEntityWith(Class... componentTypes) { - return getEntitiesWith(componentTypes).iterator().next(); + var entities = getEntitiesWith(componentTypes); + if (entities.isEmpty()) return null; + return entities.iterator().next(); } /** diff --git a/src/main/java/com/terminalvelocitycabbage/engine/event/RegistryEvent.java b/src/main/java/com/terminalvelocitycabbage/engine/event/RegistryEvent.java index 85cfb38a..387cf838 100644 --- a/src/main/java/com/terminalvelocitycabbage/engine/event/RegistryEvent.java +++ b/src/main/java/com/terminalvelocitycabbage/engine/event/RegistryEvent.java @@ -22,9 +22,8 @@ public RegistryPair register(Identifier identifier, T item) { public RegistryPair register(T item) { if (item instanceof Identifiable) { return registry.getAndRegister(item); - } else { - Log.crash("Cannot register item " + item.getClass().getName() + " since it does not implement Identifiable. Use method with explicit identifier instead."); } + Log.crash("Cannot register item " + item.getClass().getName() + " since it does not implement Identifiable. Use method with explicit identifier instead."); return null; } diff --git a/src/main/java/com/terminalvelocitycabbage/engine/filesystem/resources/ResourceCategory.java b/src/main/java/com/terminalvelocitycabbage/engine/filesystem/resources/ResourceCategory.java index 6ec5c50d..3b9bfca5 100644 --- a/src/main/java/com/terminalvelocitycabbage/engine/filesystem/resources/ResourceCategory.java +++ b/src/main/java/com/terminalvelocitycabbage/engine/filesystem/resources/ResourceCategory.java @@ -30,31 +30,30 @@ public ResourceCategory(String name) { /** * @param registry the registry to register the default values to - * @param namespace The namespace to register these under */ public static void registerEngineDefaults(Registry registry, String namespace) { - register(registry, namespace, MODEL); - register(registry, namespace, TEXTURE); - register(registry, namespace, ANIMATION); - register(registry, namespace, ANIMATION_CONTROLLER); - register(registry, namespace, SHADER); - register(registry, namespace, DEFAULT_CONFIG); - register(registry, namespace, SOUND); - register(registry, namespace, FONT); - register(registry, namespace, GENERIC_FILE); - register(registry, namespace, LOCALIZATION); - register(registry, namespace, PROPERTIES); - register(registry, namespace, ENTITY); - register(registry, namespace, ROUTINE); - register(registry, namespace, SCENE); + register(registry, MODEL); + register(registry, TEXTURE); + register(registry, ANIMATION); + register(registry, ANIMATION_CONTROLLER); + register(registry, SHADER); + register(registry, DEFAULT_CONFIG); + register(registry, SOUND); + register(registry, FONT); + register(registry, GENERIC_FILE); + register(registry, LOCALIZATION); + register(registry, PROPERTIES); + register(registry, ENTITY); + register(registry, ROUTINE); + register(registry, SCENE); } - private static void register(Registry registry, String namespace, ResourceCategory resourceCategory) { - registry.getAndRegister(resourceCategory.createIdentifier(namespace), resourceCategory); + private static void register(Registry registry, ResourceCategory resourceCategory) { + registry.getAndRegister(resourceCategory.createIdentifier(), resourceCategory); } - public Identifier createIdentifier(String namespace) { - return new Identifier(namespace, "resource_category", this.name()); + public Identifier createIdentifier() { + return new Identifier("global", "resource_category", this.name()); } public String getAssetsPath(String namespace) { diff --git a/src/main/java/com/terminalvelocitycabbage/engine/filesystem/resources/ResourceSource.java b/src/main/java/com/terminalvelocitycabbage/engine/filesystem/resources/ResourceSource.java index 59f61086..860208f7 100644 --- a/src/main/java/com/terminalvelocitycabbage/engine/filesystem/resources/ResourceSource.java +++ b/src/main/java/com/terminalvelocitycabbage/engine/filesystem/resources/ResourceSource.java @@ -36,7 +36,7 @@ public Registry getResourceRootRegistry() { * @param type The type of resource being retrieved */ public void registerDefaultSourceRoot(String namespace, ResourceCategory type) { - getResourceRootRegistry().getAndRegister(type.createIdentifier(namespace), type.getAssetsPath(namespace)); + getResourceRootRegistry().getAndRegister(type.createIdentifier(), type.getAssetsPath(namespace)); } /** diff --git a/src/main/java/com/terminalvelocitycabbage/engine/registry/Registry.java b/src/main/java/com/terminalvelocitycabbage/engine/registry/Registry.java index a934c15b..d5f4bc40 100644 --- a/src/main/java/com/terminalvelocitycabbage/engine/registry/Registry.java +++ b/src/main/java/com/terminalvelocitycabbage/engine/registry/Registry.java @@ -35,7 +35,7 @@ public Registry() { public Identifier register(Identifier identifier, T item, boolean replaceIfExists) { if (contains(identifier)) { if (!replaceIfExists) { - Log.warn("Tried to register item of same identifier " + identifier.toString() + " twice, the second addition has been ignored. This will likely cause problems later one (probably crashes)"); + Log.debug("Tried to register item of same identifier " + identifier.toString() + " twice, the second addition has been ignored."); return null; } replace(identifier, item); @@ -51,10 +51,6 @@ public Identifier register(Identifier identifier, T item, boolean replaceIfExist * @param item The item to be registered */ public Identifier register(Identifier identifier, T item) { - if (contains(identifier)) { - Log.warn("Tried to register item of same identifier " + identifier.toString() + " twice, the second addition has been ignored."); - return null; - } return register(identifier, item, false); } @@ -81,8 +77,8 @@ public Identifier register(T item) { public RegistryPair getAndRegister(Identifier identifier, T item, boolean replaceIfExists) { if (contains(identifier)) { if (!replaceIfExists) { - Log.warn("Tried to register item of same identifier " + identifier.toString() + " twice, the second addition has been ignored. This will likely cause problems later one (probably crashes)"); - return null; + Log.debug("Tried to register item of same identifier " + identifier.toString() + " twice, the second addition has been ignored."); + return new RegistryPair<>(identifier, get(identifier)); } replace(identifier, item); } else { @@ -97,10 +93,6 @@ public RegistryPair getAndRegister(Identifier identifier, T item, boolean rep * @param item The item to be registered */ public RegistryPair getAndRegister(Identifier identifier, T item) { - if (contains(identifier)) { - Log.warn("Tried to register item of same identifier " + identifier.toString() + " twice, the second addition has been ignored."); - return new RegistryPair<>(identifier, get(identifier)); - } return getAndRegister(identifier, item, false); } @@ -126,7 +118,7 @@ public RegistryPair getAndRegister(T item) { */ public RegistryPair replace(Identifier identifier, T newItem) { if (!contains(identifier)) { - Log.warn("Cannot replace registry item with ID: " + identifier.toString() + " since it does not exist in this registry."); + Log.error("Cannot replace registry item with ID: " + identifier.toString() + " since it does not exist in this registry."); return null; } registryContents.replace(identifier, newItem); diff --git a/src/main/java/com/terminalvelocitycabbage/engine/util/StringUtils.java b/src/main/java/com/terminalvelocitycabbage/engine/util/StringUtils.java index dea82693..98b61068 100644 --- a/src/main/java/com/terminalvelocitycabbage/engine/util/StringUtils.java +++ b/src/main/java/com/terminalvelocitycabbage/engine/util/StringUtils.java @@ -15,4 +15,19 @@ public static String getStringBetween(String source, String startDelimiter, Stri return source.substring(startIndex + startDelimiter.length(), endIndex); } + public static String capitalize(String name) { + return name.substring(0, 1).toUpperCase() + name.substring(1); + } + + public static String convertSnakeCaseToCapitalized(String name) { + String[] words = name.split("_"); + StringBuilder result = new StringBuilder(); + for (int i = 0; i < words.length; i++) { + if (i > 0) { + result.append(" "); + } + result.append(capitalize(words[i])); + } + return result.toString(); + } } diff --git a/src/main/java/com/terminalvelocitycabbage/templates/ecs/components/CameraComponent.java b/src/main/java/com/terminalvelocitycabbage/templates/ecs/components/CameraComponent.java index f966d51d..9929a46c 100644 --- a/src/main/java/com/terminalvelocitycabbage/templates/ecs/components/CameraComponent.java +++ b/src/main/java/com/terminalvelocitycabbage/templates/ecs/components/CameraComponent.java @@ -1,14 +1,16 @@ package com.terminalvelocitycabbage.templates.ecs.components; +import com.terminalvelocitycabbage.editor.hints.EditorHint; import com.terminalvelocitycabbage.engine.client.renderer.Projection; import com.terminalvelocitycabbage.engine.ecs.Component; import com.terminalvelocitycabbage.engine.util.Transformation; import org.joml.Matrix4f; +@EditorHint.ComponentName(name = "Camera") public abstract class CameraComponent implements Component { private final Projection projection; - private final Matrix4f viewMatrix = new Matrix4f(); + protected final Matrix4f viewMatrix = new Matrix4f(); public CameraComponent(Projection projection) { this.projection = projection; diff --git a/src/main/java/com/terminalvelocitycabbage/templates/ecs/components/DirectionalLightComponent.java b/src/main/java/com/terminalvelocitycabbage/templates/ecs/components/DirectionalLightComponent.java index 04116c37..a4e501cb 100644 --- a/src/main/java/com/terminalvelocitycabbage/templates/ecs/components/DirectionalLightComponent.java +++ b/src/main/java/com/terminalvelocitycabbage/templates/ecs/components/DirectionalLightComponent.java @@ -1,10 +1,12 @@ package com.terminalvelocitycabbage.templates.ecs.components; +import com.terminalvelocitycabbage.editor.hints.EditorHint; import com.terminalvelocitycabbage.engine.client.renderer.lighting.DirectionalLight; import com.terminalvelocitycabbage.engine.ecs.Component; import com.terminalvelocitycabbage.engine.util.Color; import org.joml.Vector3f; +@EditorHint.ComponentName(name = "Directional Light") public class DirectionalLightComponent implements Component { private DirectionalLight light; diff --git a/src/main/java/com/terminalvelocitycabbage/templates/ecs/components/MeshComponent.java b/src/main/java/com/terminalvelocitycabbage/templates/ecs/components/MeshComponent.java index 1368a449..1bb7b16c 100644 --- a/src/main/java/com/terminalvelocitycabbage/templates/ecs/components/MeshComponent.java +++ b/src/main/java/com/terminalvelocitycabbage/templates/ecs/components/MeshComponent.java @@ -1,8 +1,10 @@ package com.terminalvelocitycabbage.templates.ecs.components; +import com.terminalvelocitycabbage.editor.hints.EditorHint; import com.terminalvelocitycabbage.engine.client.renderer.model.Mesh; import com.terminalvelocitycabbage.engine.ecs.Component; +@EditorHint.ComponentName(name = "Mesh") public class MeshComponent implements Component { Mesh mesh; diff --git a/src/main/java/com/terminalvelocitycabbage/templates/ecs/components/ModelComponent.java b/src/main/java/com/terminalvelocitycabbage/templates/ecs/components/ModelComponent.java index db9eff7c..ca07cfd4 100644 --- a/src/main/java/com/terminalvelocitycabbage/templates/ecs/components/ModelComponent.java +++ b/src/main/java/com/terminalvelocitycabbage/templates/ecs/components/ModelComponent.java @@ -1,8 +1,10 @@ package com.terminalvelocitycabbage.templates.ecs.components; +import com.terminalvelocitycabbage.editor.hints.EditorHint; import com.terminalvelocitycabbage.engine.ecs.Component; import com.terminalvelocitycabbage.engine.registry.Identifier; +@EditorHint.ComponentName(name = "Model") public class ModelComponent implements Component { Identifier model; diff --git a/src/main/java/com/terminalvelocitycabbage/templates/ecs/components/NameComponent.java b/src/main/java/com/terminalvelocitycabbage/templates/ecs/components/NameComponent.java new file mode 100644 index 00000000..1090a290 --- /dev/null +++ b/src/main/java/com/terminalvelocitycabbage/templates/ecs/components/NameComponent.java @@ -0,0 +1,30 @@ +package com.terminalvelocitycabbage.templates.ecs.components; + +import com.terminalvelocitycabbage.editor.hints.EditorHint; +import com.terminalvelocitycabbage.engine.ecs.Component; + +@EditorHint.ComponentName(name = "Name") +public class NameComponent implements Component { + + String name; + + @Override + public void parseComponentField(String field, String value) { + if (field.equals("name")) { + this.name = value; + } + } + + @Override + public void setDefaults() { + name = "Entity"; + } + + public void setName(String name) { + this.name = name; + } + + public String getName() { + return name; + } +} diff --git a/src/main/java/com/terminalvelocitycabbage/templates/ecs/components/PitchYawRotationComponent.java b/src/main/java/com/terminalvelocitycabbage/templates/ecs/components/PitchYawRotationComponent.java new file mode 100644 index 00000000..cec91ea5 --- /dev/null +++ b/src/main/java/com/terminalvelocitycabbage/templates/ecs/components/PitchYawRotationComponent.java @@ -0,0 +1,34 @@ +package com.terminalvelocitycabbage.templates.ecs.components; + +import com.terminalvelocitycabbage.editor.hints.EditorHint; +import com.terminalvelocitycabbage.engine.ecs.Component; +import org.joml.Vector2f; + +//TODO change setter methods to have a min and max y rotation and to return a normalized x rotation +@EditorHint.ComponentName(name = "Pitch-Yaw Rotation") +public class PitchYawRotationComponent implements Component { + + Vector2f rotation; + + @Override + public void setDefaults() { + rotation = new Vector2f(); + } + + public Vector2f getRotation() { + return rotation; + } + + public void setRotation(Vector2f rotation) { + this.rotation = rotation; + } + + public void setRotation(float x, float y) { + this.rotation.set(x, y); + } + + public void rotate(float x, float y) { + this.rotation.x += x; + this.rotation.y += y; + } +} diff --git a/src/main/java/com/terminalvelocitycabbage/templates/ecs/components/PositionComponent.java b/src/main/java/com/terminalvelocitycabbage/templates/ecs/components/PositionComponent.java index f09a53ae..18dc4599 100644 --- a/src/main/java/com/terminalvelocitycabbage/templates/ecs/components/PositionComponent.java +++ b/src/main/java/com/terminalvelocitycabbage/templates/ecs/components/PositionComponent.java @@ -1,8 +1,10 @@ package com.terminalvelocitycabbage.templates.ecs.components; +import com.terminalvelocitycabbage.editor.hints.EditorHint; import com.terminalvelocitycabbage.engine.ecs.Component; import org.joml.Vector3f; +@EditorHint.ComponentName(name = "Position") public class PositionComponent implements Component { Vector3f position; diff --git a/src/main/java/com/terminalvelocitycabbage/templates/ecs/components/RotationComponent.java b/src/main/java/com/terminalvelocitycabbage/templates/ecs/components/RotationComponent.java new file mode 100644 index 00000000..1f1b10af --- /dev/null +++ b/src/main/java/com/terminalvelocitycabbage/templates/ecs/components/RotationComponent.java @@ -0,0 +1,41 @@ +package com.terminalvelocitycabbage.templates.ecs.components; + +import com.terminalvelocitycabbage.editor.hints.EditorHint; +import com.terminalvelocitycabbage.engine.ecs.Component; +import org.joml.Quaternionf; +import org.joml.Vector3f; + +@EditorHint.ComponentName(name = "Rotation") +public class RotationComponent implements Component { + + Quaternionf rotation; + + @Override + public void setDefaults() { + rotation = new Quaternionf(); + } + + public Quaternionf getRotation() { + return rotation; + } + + public void setRotation(Quaternionf rotation) { + this.rotation = rotation; + } + + public void setRotation(float x, float y, float z) { + this.rotation.identity().rotateXYZ(x, y, z); + } + + public void rotate(Vector3f rotation) { + this.rotation.rotateXYZ(rotation.x, rotation.y, rotation.z); + } + + public void rotate(float x, float y, float z) { + this.rotation.rotateXYZ(x, y, z); + } + + public void rotateAxis(float angle, Vector3f axis) { + this.rotation.rotateAxis(angle, axis); + } +} diff --git a/src/main/java/com/terminalvelocitycabbage/templates/ecs/components/SoundListenerComponent.java b/src/main/java/com/terminalvelocitycabbage/templates/ecs/components/SoundListenerComponent.java index 3463a9ca..a1692ded 100644 --- a/src/main/java/com/terminalvelocitycabbage/templates/ecs/components/SoundListenerComponent.java +++ b/src/main/java/com/terminalvelocitycabbage/templates/ecs/components/SoundListenerComponent.java @@ -1,9 +1,11 @@ package com.terminalvelocitycabbage.templates.ecs.components; +import com.terminalvelocitycabbage.editor.hints.EditorHint; import com.terminalvelocitycabbage.engine.client.ClientBase; import com.terminalvelocitycabbage.engine.client.sound.SoundListener; import com.terminalvelocitycabbage.engine.ecs.Component; +@EditorHint.ComponentName(name = "Sound Listener") public class SoundListenerComponent implements Component { SoundListener listener; diff --git a/src/main/java/com/terminalvelocitycabbage/templates/ecs/components/SoundSourceComponent.java b/src/main/java/com/terminalvelocitycabbage/templates/ecs/components/SoundSourceComponent.java index d07b4078..76d963d6 100644 --- a/src/main/java/com/terminalvelocitycabbage/templates/ecs/components/SoundSourceComponent.java +++ b/src/main/java/com/terminalvelocitycabbage/templates/ecs/components/SoundSourceComponent.java @@ -1,5 +1,6 @@ package com.terminalvelocitycabbage.templates.ecs.components; +import com.terminalvelocitycabbage.editor.hints.EditorHint; import com.terminalvelocitycabbage.engine.client.ClientBase; import com.terminalvelocitycabbage.engine.client.sound.SoundSource; import com.terminalvelocitycabbage.engine.debug.Log; @@ -8,6 +9,7 @@ import org.joml.Vector3f; //TODO add utilities for playing with different pitches etc, not required in MVP +@EditorHint.ComponentName(name = "Sound Source") public class SoundSourceComponent implements Component { SoundSource source; diff --git a/src/main/java/com/terminalvelocitycabbage/templates/ecs/components/TransformationComponent.java b/src/main/java/com/terminalvelocitycabbage/templates/ecs/components/TransformationComponent.java index df8b9528..f1f322e1 100644 --- a/src/main/java/com/terminalvelocitycabbage/templates/ecs/components/TransformationComponent.java +++ b/src/main/java/com/terminalvelocitycabbage/templates/ecs/components/TransformationComponent.java @@ -1,11 +1,13 @@ package com.terminalvelocitycabbage.templates.ecs.components; +import com.terminalvelocitycabbage.editor.hints.EditorHint; import com.terminalvelocitycabbage.engine.ecs.Component; import com.terminalvelocitycabbage.engine.util.Transformation; import org.joml.Matrix4f; import org.joml.Quaternionf; import org.joml.Vector3f; +@EditorHint.ComponentName(name = "Transformation") public class TransformationComponent implements Component { Transformation transformation; diff --git a/src/main/java/com/terminalvelocitycabbage/templates/ecs/components/VelocityComponent.java b/src/main/java/com/terminalvelocitycabbage/templates/ecs/components/VelocityComponent.java index ba518c9d..85d0a08e 100644 --- a/src/main/java/com/terminalvelocitycabbage/templates/ecs/components/VelocityComponent.java +++ b/src/main/java/com/terminalvelocitycabbage/templates/ecs/components/VelocityComponent.java @@ -1,8 +1,10 @@ package com.terminalvelocitycabbage.templates.ecs.components; +import com.terminalvelocitycabbage.editor.hints.EditorHint; import com.terminalvelocitycabbage.engine.ecs.Component; import org.joml.Vector3f; +@EditorHint.ComponentName(name = "Velocity") public class VelocityComponent implements Component { Vector3f velocity; diff --git a/src/main/java/com/terminalvelocitycabbage/templates/events/ConfigureTexturesEvent.java b/src/main/java/com/terminalvelocitycabbage/templates/events/ConfigureTexturesEvent.java index 67a1c0be..867ec666 100644 --- a/src/main/java/com/terminalvelocitycabbage/templates/events/ConfigureTexturesEvent.java +++ b/src/main/java/com/terminalvelocitycabbage/templates/events/ConfigureTexturesEvent.java @@ -33,6 +33,10 @@ public Identifier registerAtlas(String namespace, String atlasName) { return atlasIdentifier; } + public void registerAtlas(Identifier identifier) { + texturesToCompileToAtlas.putIfAbsent(identifier, new HashMap<>()); + } + public void addTexture(Identifier textureIdentifier, Identifier... atlasIdentifiers) { if (atlasIdentifiers.length == 0) { singleTextures.putIfAbsent(textureIdentifier, fileSystem.getResource(ResourceCategory.TEXTURE, textureIdentifier)); diff --git a/src/main/resources/assets/editor/fonts/OFL.txt b/src/main/resources/assets/editor/fonts/OFL.txt new file mode 100644 index 00000000..fd0cb995 --- /dev/null +++ b/src/main/resources/assets/editor/fonts/OFL.txt @@ -0,0 +1,93 @@ +Copyright 2021 The Outfit Project Authors (https://github.com/Outfitio/Outfit-Fonts) + +This Font Software is licensed under the SIL Open Font License, Version 1.1. +This license is copied below, and is also available with a FAQ at: +https://openfontlicense.org + + +----------------------------------------------------------- +SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007 +----------------------------------------------------------- + +PREAMBLE +The goals of the Open Font License (OFL) are to stimulate worldwide +development of collaborative font projects, to support the font creation +efforts of academic and linguistic communities, and to provide a free and +open framework in which fonts may be shared and improved in partnership +with others. + +The OFL allows the licensed fonts to be used, studied, modified and +redistributed freely as long as they are not sold by themselves. The +fonts, including any derivative works, can be bundled, embedded, +redistributed and/or sold with any software provided that any reserved +names are not used by derivative works. The fonts and derivatives, +however, cannot be released under any other type of license. The +requirement for fonts to remain under this license does not apply +to any document created using the fonts or their derivatives. + +DEFINITIONS +"Font Software" refers to the set of files released by the Copyright +Holder(s) under this license and clearly marked as such. This may +include source files, build scripts and documentation. + +"Reserved Font Name" refers to any names specified as such after the +copyright statement(s). + +"Original Version" refers to the collection of Font Software components as +distributed by the Copyright Holder(s). + +"Modified Version" refers to any derivative made by adding to, deleting, +or substituting -- in part or in whole -- any of the components of the +Original Version, by changing formats or by porting the Font Software to a +new environment. + +"Author" refers to any designer, engineer, programmer, technical +writer or other person who contributed to the Font Software. + +PERMISSION & CONDITIONS +Permission is hereby granted, free of charge, to any person obtaining +a copy of the Font Software, to use, study, copy, merge, embed, modify, +redistribute, and sell modified and unmodified copies of the Font +Software, subject to the following conditions: + +1) Neither the Font Software nor any of its individual components, +in Original or Modified Versions, may be sold by itself. + +2) Original or Modified Versions of the Font Software may be bundled, +redistributed and/or sold with any software, provided that each copy +contains the above copyright notice and this license. These can be +included either as stand-alone text files, human-readable headers or +in the appropriate machine-readable metadata fields within text or +binary files as long as those fields can be easily viewed by the user. + +3) No Modified Version of the Font Software may use the Reserved Font +Name(s) unless explicit written permission is granted by the corresponding +Copyright Holder. This restriction only applies to the primary font name as +presented to the users. + +4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font +Software shall not be used to promote, endorse or advertise any +Modified Version, except to acknowledge the contribution(s) of the +Copyright Holder(s) and the Author(s) or with their explicit written +permission. + +5) The Font Software, modified or unmodified, in part or in whole, +must be distributed entirely under this license, and must not be +distributed under any other license. The requirement for fonts to +remain under this license does not apply to any document created +using the Font Software. + +TERMINATION +This license becomes null and void if any of the above conditions are +not met. + +DISCLAIMER +THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT +OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE +COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL +DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM +OTHER DEALINGS IN THE FONT SOFTWARE. diff --git a/src/main/resources/assets/editor/fonts/outfit_black.ttf b/src/main/resources/assets/editor/fonts/outfit_black.ttf new file mode 100644 index 00000000..c65a76aa Binary files /dev/null and b/src/main/resources/assets/editor/fonts/outfit_black.ttf differ diff --git a/src/main/resources/assets/editor/fonts/outfit_bold.ttf b/src/main/resources/assets/editor/fonts/outfit_bold.ttf new file mode 100644 index 00000000..f9f2f72a Binary files /dev/null and b/src/main/resources/assets/editor/fonts/outfit_bold.ttf differ diff --git a/src/main/resources/assets/editor/fonts/outfit_extrabold.ttf b/src/main/resources/assets/editor/fonts/outfit_extrabold.ttf new file mode 100644 index 00000000..08dce383 Binary files /dev/null and b/src/main/resources/assets/editor/fonts/outfit_extrabold.ttf differ diff --git a/src/main/resources/assets/editor/fonts/outfit_extralight.ttf b/src/main/resources/assets/editor/fonts/outfit_extralight.ttf new file mode 100644 index 00000000..35cca91c Binary files /dev/null and b/src/main/resources/assets/editor/fonts/outfit_extralight.ttf differ diff --git a/src/main/resources/assets/editor/fonts/outfit_light.ttf b/src/main/resources/assets/editor/fonts/outfit_light.ttf new file mode 100644 index 00000000..a874ba16 Binary files /dev/null and b/src/main/resources/assets/editor/fonts/outfit_light.ttf differ diff --git a/src/main/resources/assets/editor/fonts/outfit_medium.ttf b/src/main/resources/assets/editor/fonts/outfit_medium.ttf new file mode 100644 index 00000000..4bbf1776 Binary files /dev/null and b/src/main/resources/assets/editor/fonts/outfit_medium.ttf differ diff --git a/src/main/resources/assets/editor/fonts/outfit_regular.ttf b/src/main/resources/assets/editor/fonts/outfit_regular.ttf new file mode 100644 index 00000000..3939ab24 Binary files /dev/null and b/src/main/resources/assets/editor/fonts/outfit_regular.ttf differ diff --git a/src/main/resources/assets/editor/fonts/outfit_semibold.ttf b/src/main/resources/assets/editor/fonts/outfit_semibold.ttf new file mode 100644 index 00000000..1f28bda7 Binary files /dev/null and b/src/main/resources/assets/editor/fonts/outfit_semibold.ttf differ diff --git a/src/main/resources/assets/editor/fonts/outfit_thin.ttf b/src/main/resources/assets/editor/fonts/outfit_thin.ttf new file mode 100644 index 00000000..989ae794 Binary files /dev/null and b/src/main/resources/assets/editor/fonts/outfit_thin.ttf differ diff --git a/src/main/resources/assets/editor/localizations/en-us.toml b/src/main/resources/assets/editor/localizations/en-us.toml new file mode 100644 index 00000000..66f900f0 --- /dev/null +++ b/src/main/resources/assets/editor/localizations/en-us.toml @@ -0,0 +1,2 @@ +"editor.hello" = "Hello from the Editor!" +"editor.goodbye" = "Goodbye from the Editor!" diff --git a/src/main/resources/assets/editor/routines/editor_default.routine.toml b/src/main/resources/assets/editor/routines/editor_default.routine.toml new file mode 100644 index 00000000..b17d5d96 --- /dev/null +++ b/src/main/resources/assets/editor/routines/editor_default.routine.toml @@ -0,0 +1,6 @@ +[routine] +name = "editor_default" + +[[steps]] +id = "editor:update_animations" +system = "animation" diff --git a/src/main/resources/assets/editor/scenes/editor.scene.toml b/src/main/resources/assets/editor/scenes/editor.scene.toml new file mode 100644 index 00000000..78a081a9 --- /dev/null +++ b/src/main/resources/assets/editor/scenes/editor.scene.toml @@ -0,0 +1,34 @@ +[scene] +name = "editor" +texture_atlases = [ + "editor:ui_atlas" +] +input_controllers = [ + "editor:ui_click", + "editor:scroll" +] +routines = [ "editor:editor_default" ] +render_graph = "editor:draw_scene" + +[[entities]] +[entities.component.name] +name = "Editor Camera" +[entities.component.transformation] +position = [0.0, 0.0, 5.0] +[entities.component.editor_camera] + +[[entities]] +[entities.component.name] +name = "Sun" +[entities.component.directional_light] +direction = [-1.0, -1.0, -1.0] +color = [1.0, 1.0, 1.0, 1.0] +intensity = 1.0 + +[[entities]] +[entities.component.name] +name = "Smile Square" +[entities.component.transformation] +position = [0.0, 0.0, 0.0] +[entities.component.model] +model = "editor:smile_square" diff --git a/src/main/resources/assets/editor/textures/caret_closed_icon.png b/src/main/resources/assets/editor/textures/caret_closed_icon.png new file mode 100644 index 00000000..917248de Binary files /dev/null and b/src/main/resources/assets/editor/textures/caret_closed_icon.png differ diff --git a/src/main/resources/assets/editor/textures/caret_open_icon.png b/src/main/resources/assets/editor/textures/caret_open_icon.png new file mode 100644 index 00000000..c3784a17 Binary files /dev/null and b/src/main/resources/assets/editor/textures/caret_open_icon.png differ diff --git a/src/main/resources/assets/editor/textures/rotate_icon.png b/src/main/resources/assets/editor/textures/rotate_icon.png new file mode 100644 index 00000000..e7fb8165 Binary files /dev/null and b/src/main/resources/assets/editor/textures/rotate_icon.png differ diff --git a/src/main/resources/assets/editor/textures/scale_icon.png b/src/main/resources/assets/editor/textures/scale_icon.png new file mode 100644 index 00000000..017a4389 Binary files /dev/null and b/src/main/resources/assets/editor/textures/scale_icon.png differ diff --git a/src/main/resources/assets/editor/textures/translate_icon.png b/src/main/resources/assets/editor/textures/translate_icon.png new file mode 100644 index 00000000..aa833c93 Binary files /dev/null and b/src/main/resources/assets/editor/textures/translate_icon.png differ