From 1dd8dbc9a460befd8cca9a2cc9870f3915618636 Mon Sep 17 00:00:00 2001 From: Tom Wojciechowski Date: Thu, 3 Oct 2024 18:08:52 +0100 Subject: [PATCH] Atlas support in vfx. Spine updates first in update, so bones are transformed correctly and sync is in the same frame. Project splash show path of project to make it easier to see previous projects that are same anme. --- .../ParticleComponentProvider.java | 8 + .../talos/editor/project2/ProjectSplash.java | 7 +- .../runtime/scene/GameObjectRenderer.java | 726 ++++++++++-------- .../scene/render/ComponentRenderer.java | 4 + .../render/SkeletonComponentRenderer.java | 40 +- .../runtime/vfx/ParticleEffectInstance.java | 4 + .../vfx/modules/QuadMeshGeneratorModule.java | 51 +- .../vfx/modules/SpriteMaterialModule.java | 9 +- 8 files changed, 496 insertions(+), 353 deletions(-) diff --git a/editor/src/com/talosvfx/talos/editor/addons/scene/logic/componentwrappers/ParticleComponentProvider.java b/editor/src/com/talosvfx/talos/editor/addons/scene/logic/componentwrappers/ParticleComponentProvider.java index 07af137df..045ff38e6 100644 --- a/editor/src/com/talosvfx/talos/editor/addons/scene/logic/componentwrappers/ParticleComponentProvider.java +++ b/editor/src/com/talosvfx/talos/editor/addons/scene/logic/componentwrappers/ParticleComponentProvider.java @@ -1,6 +1,8 @@ package com.talosvfx.talos.editor.addons.scene.logic.componentwrappers; import com.badlogic.gdx.graphics.g2d.TextureAtlas; +import com.badlogic.gdx.scenes.scene2d.Actor; +import com.badlogic.gdx.scenes.scene2d.utils.ChangeListener; import com.badlogic.gdx.utils.Array; import com.talosvfx.talos.editor.addons.scene.apps.spriteeditor.SpriteEditorApp; import com.talosvfx.talos.editor.addons.scene.assets.AssetRepository; @@ -33,6 +35,12 @@ public Array getListOfProperties () { // } PropertyWidget descriptorWidget = WidgetFactory.generateForGameAsset(component, "gameAsset", null, "Effect", GameAssetType.VFX); + descriptorWidget.addListener(new ChangeListener() { + @Override + public void changed (ChangeEvent event, Actor actor) { + component.setEffectRef(null); + } + }); ButtonPropertyWidget linkedToWidget = new ButtonPropertyWidget("Effect Project", "Edit", new ButtonPropertyWidget.ButtonListener() { @Override diff --git a/editor/src/com/talosvfx/talos/editor/project2/ProjectSplash.java b/editor/src/com/talosvfx/talos/editor/project2/ProjectSplash.java index d92ef9195..14ead6d0c 100644 --- a/editor/src/com/talosvfx/talos/editor/project2/ProjectSplash.java +++ b/editor/src/com/talosvfx/talos/editor/project2/ProjectSplash.java @@ -169,7 +169,12 @@ private void buildOpenTable (Table table) { if(iter++ > 5) break; FileHandle handle = Gdx.files.absolute(recentProject.getProjectPath()); - ButtonLabel recentProjectLabel = new ButtonLabel(SharedResources.skin.getDrawable("ic-file-blank"), handle.name()); + String pathEnd = handle.path(); + //Show maximum of 40 characters of path end + if(pathEnd.length() > 40) { + pathEnd = "..." + pathEnd.substring(pathEnd.length() - 40); + } + ButtonLabel recentProjectLabel = new ButtonLabel(SharedResources.skin.getDrawable("ic-file-blank"), handle.name() + " (" + pathEnd + ")"); recentProjectLabel.addListener(new ClickListener() { @Override public void clicked (InputEvent event, float x, float y) { diff --git a/runtimes/talos/src/main/java/com/talosvfx/talos/runtime/scene/GameObjectRenderer.java b/runtimes/talos/src/main/java/com/talosvfx/talos/runtime/scene/GameObjectRenderer.java index 05974548b..01aaf6b0f 100644 --- a/runtimes/talos/src/main/java/com/talosvfx/talos/runtime/scene/GameObjectRenderer.java +++ b/runtimes/talos/src/main/java/com/talosvfx/talos/runtime/scene/GameObjectRenderer.java @@ -11,6 +11,7 @@ import com.badlogic.gdx.math.Vector2; import com.badlogic.gdx.utils.Array; import com.badlogic.gdx.utils.GdxRuntimeException; +import com.esotericsoftware.spine.Bone; import com.talosvfx.talos.runtime.scene.components.*; import com.talosvfx.talos.runtime.scene.render.*; import com.talosvfx.talos.runtime.RuntimeContext; @@ -19,338 +20,431 @@ import java.util.Comparator; public class GameObjectRenderer { - private ComponentRenderer spriteRenderer; - private ComponentRenderer mapRenderer; - private ComponentRenderer> particleRenderer; - private ComponentRenderer> routineRenderer; - private ComponentRenderer spineRenderer; - private ComponentRenderer pathRenderer; + private ComponentRenderer spriteRenderer; + private ComponentRenderer mapRenderer; + private ComponentRenderer> particleRenderer; + private ComponentRenderer> routineRenderer; + private ComponentRenderer spineRenderer; + private ComponentRenderer pathRenderer; - public final Comparator layerAndDrawOrderComparator; - public final Comparator yDownDrawOrderComparator; + public final Comparator layerAndDrawOrderComparator; + public final Comparator yDownDrawOrderComparator; - public final Comparator parentSorter; + public final Comparator parentSorter; - private Camera camera; + private Camera camera; - @Getter - private boolean skipUpdates; + @Getter + private boolean skipUpdates; - private static TextureRegion brokenRegion; + private static TextureRegion brokenRegion; - public GameObjectRenderer () { - brokenRegion = new TextureRegion(new Texture(Gdx.files.classpath("missing.png"))); + public GameObjectRenderer () { + brokenRegion = new TextureRegion(new Texture(Gdx.files.classpath("missing.png"))); - spriteRenderer = createSpriteRenderer(); - mapRenderer = createMapRenderer(); - particleRenderer = createParticleRenderer(); - routineRenderer = createRoutineRenderer(); - spineRenderer = createSpineRenderer(); - pathRenderer = createPathRenderer(); + spriteRenderer = createSpriteRenderer(); + mapRenderer = createMapRenderer(); + particleRenderer = createParticleRenderer(); + routineRenderer = createRoutineRenderer(); + spineRenderer = createSpineRenderer(); + pathRenderer = createPathRenderer(); - layerAndDrawOrderComparator = new Comparator() { - @Override - public int compare (GameObject o1, GameObject o2) { - float aSort = GameObjectRenderer.getDrawOrderSafe(o1); - float bSort = GameObjectRenderer.getDrawOrderSafe(o2); - return Float.compare(aSort, bSort); - } - }; + layerAndDrawOrderComparator = new Comparator() { + @Override + public int compare (GameObject o1, GameObject o2) { + float aSort = GameObjectRenderer.getDrawOrderSafe(o1); + float bSort = GameObjectRenderer.getDrawOrderSafe(o2); + return Float.compare(aSort, bSort); + } + }; - yDownDrawOrderComparator = new Comparator() { - @Override - public int compare (GameObject o1, GameObject o2) { - float aSort = GameObjectRenderer.getBottomY(o1); - float bSort = GameObjectRenderer.getBottomY(o2); - return -Float.compare(aSort, bSort); - } - }; + yDownDrawOrderComparator = new Comparator() { + @Override + public int compare (GameObject o1, GameObject o2) { + float aSort = GameObjectRenderer.getBottomY(o1); + float bSort = GameObjectRenderer.getBottomY(o2); + return -Float.compare(aSort, bSort); + } + }; - parentSorter = new Comparator() { - @Override - public int compare (GameObject o1, GameObject o2) { - SceneLayer o1Layer = GameObjectRenderer.getLayerSafe(o1); - SceneLayer o2Layer = GameObjectRenderer.getLayerSafe(o2); + parentSorter = new Comparator() { + @Override + public int compare (GameObject o1, GameObject o2) { + SceneLayer o1Layer = GameObjectRenderer.getLayerSafe(o1); + SceneLayer o2Layer = GameObjectRenderer.getLayerSafe(o2); - if (o1Layer.equals(o2Layer)) { + if (o1Layer.equals(o2Layer)) { - RenderStrategy renderStrategy = o1Layer.getRenderStrategy(); + RenderStrategy renderStrategy = o1Layer.getRenderStrategy(); - Comparator sorter = getSorter(renderStrategy); + Comparator sorter = getSorter(renderStrategy); - return sorter.compare(o1, o2); - } else { - return Integer.compare(o1Layer.getIndex(), o2Layer.getIndex()); - } - } - }; + return sorter.compare(o1, o2); + } else { + return Integer.compare(o1Layer.getIndex(), o2Layer.getIndex()); + } + } + }; - } + } - private Comparator getSorter (RenderStrategy renderMode) { - switch (renderMode) { - case SCENE: - return layerAndDrawOrderComparator; - case YDOWN: - return yDownDrawOrderComparator; - } - - throw new GdxRuntimeException("No sorter found for render mode: " + renderMode); - } - - private static float getBottomY (GameObject gameObject) { - if (gameObject.hasComponentType(RendererComponent.class)) { - RendererComponent componentAssignableFrom = gameObject.getComponentAssignableFrom(RendererComponent.class); - TransformComponent transformComponent = gameObject.getComponent(TransformComponent.class); - - float y = transformComponent.worldPosition.y; - - - if (componentAssignableFrom instanceof SpriteRendererComponent) { - Vector2 size = ((SpriteRendererComponent)componentAssignableFrom).size; - Vector2 worldScale = transformComponent.worldScale; - - float totalHeight = size.y * worldScale.y; - y -= totalHeight/2f; - } - - if (componentAssignableFrom instanceof RendererComponent) { - float fakeOffsetY = componentAssignableFrom.fakeOffsetY; - y += fakeOffsetY; - } - - return y; - - } else { - if (gameObject.hasComponent(TransformComponent.class)) { - TransformComponent component = gameObject.getComponent(TransformComponent.class); - return component.worldPosition.y; - } - } - - return 0; - } - - private static SceneLayer getLayerSafe(GameObject gameObject) { - if (gameObject.hasComponentType(RendererComponent.class)) { - RendererComponent rendererComponent = gameObject.getComponentAssignableFrom(RendererComponent.class); - return rendererComponent.sortingLayer; - } - - return RuntimeContext.getInstance().sceneData.getPreferredSceneLayer(); - } - - public static float getDrawOrderSafe (GameObject gameObject) { - if (gameObject.hasComponentType(RendererComponent.class)) { - RendererComponent rendererComponent = gameObject.getComponentAssignableFrom(RendererComponent.class); - return rendererComponent.orderingInLayer; - } - - return -55; - } - - - protected ComponentRenderer createMapRenderer () { - return new MapComponentRenderer(this); - } - - protected ComponentRenderer> createRoutineRenderer () { - return new RoutineComponentRenderer(this); - } - - protected ComponentRenderer createSpineRenderer () { - return new SkeletonComponentRenderer(this); - } - - protected ComponentRenderer> createParticleRenderer () { - return new SimpleParticleComponentRenderer(this); - } - - protected ComponentRenderer createSpriteRenderer () { - return new SpriteComponentRenderer(this); - } - - protected ComponentRenderer createPathRenderer () { - return new PathComponentRenderer(this); - } - - protected void sort (Array list) { - list.sort(parentSorter); - } - - public void update (GameObject gameObject, float delta) { - if (!gameObject.active || !gameObject.isEditorVisible()) - return; - if (gameObject.hasComponent(TransformComponent.class) && !gameObject.hasComponent(BoneComponent.class)) { - TransformComponent transform = gameObject.getComponent(TransformComponent.class); - - transform.worldPosition.set(transform.position); - transform.worldScale.set(transform.scale); - transform.worldRotation = transform.rotation; - - if (gameObject.parent != null) { - - if (gameObject.parent.hasComponent(TransformComponent.class)) { - //Combine our world with the parent - - TransformComponent parentTransform = gameObject.parent.getComponent(TransformComponent.class); - transform.worldPosition.scl(parentTransform.worldScale); - transform.worldPosition.rotateDeg(parentTransform.worldRotation); - transform.worldPosition.add(parentTransform.worldPosition); - - transform.worldRotation += parentTransform.worldRotation; - transform.worldScale.scl(parentTransform.worldScale); - } - } - } - - // if root has render component try mixing colors if they exist - if (gameObject.hasComponentType(RendererComponent.class)) { - final RendererComponent rendererComponent = gameObject.getComponentAssignableFrom(RendererComponent.class); - - // check if render component has color value - if (rendererComponent instanceof IColorHolder) { - final IColorHolder colorHolder = (IColorHolder)rendererComponent; - - // update final color by Renderer color - final Color finalColor = (colorHolder.getFinalColor()); - finalColor.set(colorHolder.getColor()); - - // should inherit parent color update final color by parent color - if (colorHolder.shouldInheritParentColor()) { - if (gameObject.parent != null) { - // check if parent contains render component - if (gameObject.parent.hasComponentType(RendererComponent.class)) { - final RendererComponent parentRendererComponent = gameObject.parent.getComponentAssignableFrom(RendererComponent.class); - - // check if parent render component has color value - if (parentRendererComponent instanceof IColorHolder) { - // combine colors - finalColor.mul(((IColorHolder)parentRendererComponent).getFinalColor()); - } - } - } - } - } - } - - if (gameObject.getGameObjects() != null) { - for (int i = 0; i < gameObject.getGameObjects().size; i++) { - GameObject child = gameObject.getGameObjects().get(i); - update(child, delta); - } - } - } - - protected void fillRenderableEntities (Array rootObjects, Array list) { - for (GameObject root : rootObjects) { - if (!root.active || !root.isEditorVisible()) continue; - - boolean childrenVisibleFlag = true; - if (root.hasComponentType(RendererComponent.class)) { - RendererComponent rendererComponent = root.getComponentAssignableFrom(RendererComponent.class); - childrenVisibleFlag = rendererComponent.childrenVisible; - if (rendererComponent.visible) { - list.add(root); - } - } - if (childrenVisibleFlag) { - if (root.getGameObjects() != null) { - fillRenderableEntities(root.getGameObjects(), list); - } - } - } - - } - - - public static void renderBrokenComponent (Batch batch, GameObject gameObject, TransformComponent transformComponent) { - - float width = 1f; - float height = 1f; - if (gameObject.hasComponent(SpriteRendererComponent.class)) { - SpriteRendererComponent component = gameObject.getComponent(SpriteRendererComponent.class); - width = component.size.x; - height = component.size.y; - } - - batch.draw(brokenRegion, - transformComponent.worldPosition.x - 0.5f, transformComponent.worldPosition.y - 0.5f, - 0.5f, 0.5f, - 1f, 1f, - width * transformComponent.worldScale.x, height * transformComponent.worldScale.y, - transformComponent.worldRotation); - } - - - Array temp = new Array<>(); - - - public void buildRenderState (PolygonBatch batch, RenderState state, Array rootObjects) { - boolean hiearchyDirty = false; - for (GameObject rootObject : rootObjects) { - if (rootObject.hierarchyDirty) { - hiearchyDirty = true; - - //reset it because we gonna sort it this pass - rootObject.hierarchyDirty = false; - } - } - if (hiearchyDirty) { - state.list.clear(); - fillRenderableEntities(rootObjects, state.list); - } - sort(state.list); - } - - - public void renderObject (Batch batch, GameObject gameObject) { - if (gameObject.hasComponent(RoutineRendererComponent.class)) { - routineRenderer.render(batch, camera, gameObject, gameObject.getComponent(RoutineRendererComponent.class)); - } - - if (gameObject.hasComponent(SpriteRendererComponent.class)) { - spriteRenderer.render(batch, camera, gameObject, gameObject.getComponent(SpriteRendererComponent.class)); - } else if (gameObject.hasComponent(ParticleComponent.class)) { - particleRenderer.render(batch,camera, gameObject, gameObject.getComponent(ParticleComponent.class)); - } else if (gameObject.hasComponent(SpineRendererComponent.class)) { - spineRenderer.render(batch, camera, gameObject, gameObject.getComponent(SpineRendererComponent.class)); - } else if (gameObject.hasComponent(MapComponent.class)) { - mapRenderer.render(batch, camera, gameObject, gameObject.getComponent(MapComponent.class)); - } else if (gameObject.hasComponent(PathRendererComponent.class)) { - pathRenderer.render(batch, camera, gameObject, gameObject.getComponent(PathRendererComponent.class)); - } - - } - - public void buildRenderStateAndRender (PolygonBatch batch, Camera camera, RenderState state, GameObject root) { - temp.clear(); - temp.add(root); - buildRenderStateAndRender(batch, camera, state, temp); - } - - public void buildRenderStateAndRender (PolygonBatch batch, Camera camera, RenderState state, Array rootObjects) { - setCamera(camera); - - buildRenderState(batch, state, rootObjects); - for (int i = 0; i < state.list.size; i++) { - GameObject gameObject = state.list.get(i); - renderObject(batch, gameObject); - } - batch.setBlendFunction(GL20.GL_SRC_ALPHA, GL20.GL_ONE_MINUS_SRC_ALPHA); - } - - public void setCamera (Camera camera) { - this.camera = camera; - } - - /** - * Any renderers that may want to skip updates do it here - * @param skipUpdates - */ - public void setSkipUpdates (boolean skipUpdates) { - this.skipUpdates = skipUpdates; - } + private Comparator getSorter (RenderStrategy renderMode) { + switch (renderMode) { + case SCENE: + return layerAndDrawOrderComparator; + case YDOWN: + return yDownDrawOrderComparator; + } + + throw new GdxRuntimeException("No sorter found for render mode: " + renderMode); + } + + private static float getBottomY (GameObject gameObject) { + if (gameObject.hasComponentType(RendererComponent.class)) { + RendererComponent componentAssignableFrom = gameObject.getComponentAssignableFrom(RendererComponent.class); + TransformComponent transformComponent = gameObject.getComponent(TransformComponent.class); + + float y = transformComponent.worldPosition.y; + + + if (componentAssignableFrom instanceof SpriteRendererComponent) { + Vector2 size = ((SpriteRendererComponent) componentAssignableFrom).size; + Vector2 worldScale = transformComponent.worldScale; + + float totalHeight = size.y * worldScale.y; + y -= totalHeight / 2f; + } + + if (componentAssignableFrom instanceof RendererComponent) { + float fakeOffsetY = componentAssignableFrom.fakeOffsetY; + y += fakeOffsetY; + } + + return y; + + } else { + if (gameObject.hasComponent(TransformComponent.class)) { + TransformComponent component = gameObject.getComponent(TransformComponent.class); + return component.worldPosition.y; + } + } + + return 0; + } + + private static SceneLayer getLayerSafe (GameObject gameObject) { + if (gameObject.hasComponentType(RendererComponent.class)) { + RendererComponent rendererComponent = gameObject.getComponentAssignableFrom(RendererComponent.class); + return rendererComponent.sortingLayer; + } + + return RuntimeContext.getInstance().sceneData.getPreferredSceneLayer(); + } + + public static float getDrawOrderSafe (GameObject gameObject) { + if (gameObject.hasComponentType(RendererComponent.class)) { + RendererComponent rendererComponent = gameObject.getComponentAssignableFrom(RendererComponent.class); + return rendererComponent.orderingInLayer; + } + + return -55; + } + + + protected ComponentRenderer createMapRenderer () { + return new MapComponentRenderer(this); + } + + protected ComponentRenderer> createRoutineRenderer () { + return new RoutineComponentRenderer(this); + } + + protected ComponentRenderer createSpineRenderer () { + return new SkeletonComponentRenderer(this); + } + + protected ComponentRenderer> createParticleRenderer () { + return new SimpleParticleComponentRenderer(this); + } + + protected ComponentRenderer createSpriteRenderer () { + return new SpriteComponentRenderer(this); + } + + protected ComponentRenderer createPathRenderer () { + return new PathComponentRenderer(this); + } + + protected void sort (Array list) { + list.sort(parentSorter); + } + + public static void debugTransforms (GameObject gameObject, int indent) { + if (gameObject.hasComponent(TransformComponent.class)) { + TransformComponent transformComponent = gameObject.getComponent(TransformComponent.class); + StringBuilder builder = new StringBuilder(); + + //Indent all by indent * 2 spaces + //print in format + //name + // local: [Position]: (x,y,z), [Scale]: (x,y,z), [Rotation]: (x,y,z) + // world: [Position]: (x,y,z), [Scale]: (x,y,z), [Rotation]: (x,y,z) + + for (int i = 0; i < indent; i++) { + builder.append(" "); + } + builder.append(gameObject.getName()); + builder.append("\n"); + for (int i = 0; i < indent; i++) { + builder.append(" "); + } + builder.append(" local: [Position]: "); + builder.append(transformComponent.position); + builder.append(", [Scale]: "); + builder.append(transformComponent.scale); + builder.append(", [Rotation]: "); + builder.append(transformComponent.rotation); + builder.append("\n"); + + for (int i = 0; i < indent; i++) { + builder.append(" "); + } + builder.append(" world: [Position]: "); + builder.append(transformComponent.worldPosition); + builder.append(", [Scale]: "); + builder.append(transformComponent.worldScale); + builder.append(", [Rotation]: "); + builder.append(transformComponent.worldRotation); + builder.append("\n"); + + if (gameObject.hasComponent(ParticleComponent.class)) { + ParticleComponent particleComponent = gameObject.getComponent(ParticleComponent.class); + if (particleComponent.getEffectRef() != null) { + float worldRotation = particleComponent.getEffectRef().getWorldRotation(); + Vector2 worldScale = particleComponent.getEffectRef().getWorldScale(); + //print it + for (int i = 0; i < indent; i++) { + builder.append(" "); + } + builder.append(" particle: [Position]: "); + builder.append(particleComponent.getEffectRef().getPosition()); + builder.append(", [Scale]: "); + builder.append(worldScale); + builder.append(", [Rotation]: "); + builder.append(worldRotation); + + + } + + } + System.out.println(builder); + + } + for (GameObject object : gameObject.getGameObjects()) { + debugTransforms(object, indent + 1); + } + } + + public void update (GameObject gameObject, float delta) { + if (!gameObject.active || !gameObject.isEditorVisible()) + return; + + //If we are a skeleton, we update it now + if (gameObject.hasComponent(SpineRendererComponent.class)) { + SpineRendererComponent spineRendererComponent = gameObject.getComponent(SpineRendererComponent.class); + spineRenderer.update(gameObject, spineRendererComponent, delta); + + // update bone game objects + if (spineRendererComponent.generateGameObjectBones) { + Array boneGOs = gameObject.getChildrenWithBoneComponent(); + + for (GameObject boneGO : boneGOs) { + BoneComponent boneComponent = boneGO.getComponent(BoneComponent.class); + Bone bone = boneComponent.getBone(); + TransformComponent transform = boneGO.getComponent(TransformComponent.class); + + transform.worldScale.set(bone.getWorldScaleX(), bone.getWorldScaleY()); + transform.worldRotation = bone.localToWorldRotation(bone.getRotation()); + transform.worldPosition.set(bone.getWorldX(), bone.getWorldY()); + + transform.position.set(bone.getX(), bone.getY()); + transform.rotation = bone.getRotation(); + transform.scale.set(bone.getScaleX(), bone.getScaleY()); + } + } + } + + if (gameObject.hasComponent(TransformComponent.class) && !gameObject.hasComponent(BoneComponent.class)) { + TransformComponent transform = gameObject.getComponent(TransformComponent.class); + + transform.worldPosition.set(transform.position); + transform.worldScale.set(transform.scale); + transform.worldRotation = transform.rotation; + + if (gameObject.parent != null) { + + if (gameObject.parent.hasComponent(TransformComponent.class)) { + //Combine our world with the parent + + TransformComponent parentTransform = gameObject.parent.getComponent(TransformComponent.class); + transform.worldPosition.scl(parentTransform.worldScale); + transform.worldPosition.rotateDeg(parentTransform.worldRotation); + transform.worldPosition.add(parentTransform.worldPosition); + + transform.worldRotation += parentTransform.worldRotation; + transform.worldScale.scl(parentTransform.worldScale); + } + } + } + + // if root has render component try mixing colors if they exist + if (gameObject.hasComponentType(RendererComponent.class)) { + final RendererComponent rendererComponent = gameObject.getComponentAssignableFrom(RendererComponent.class); + + // check if render component has color value + if (rendererComponent instanceof IColorHolder) { + final IColorHolder colorHolder = (IColorHolder) rendererComponent; + + // update final color by Renderer color + final Color finalColor = (colorHolder.getFinalColor()); + finalColor.set(colorHolder.getColor()); + + // should inherit parent color update final color by parent color + if (colorHolder.shouldInheritParentColor()) { + if (gameObject.parent != null) { + // check if parent contains render component + if (gameObject.parent.hasComponentType(RendererComponent.class)) { + final RendererComponent parentRendererComponent = gameObject.parent.getComponentAssignableFrom(RendererComponent.class); + + // check if parent render component has color value + if (parentRendererComponent instanceof IColorHolder) { + // combine colors + finalColor.mul(((IColorHolder) parentRendererComponent).getFinalColor()); + } + } + } + } + } + } + + if (gameObject.getGameObjects() != null) { + for (int i = 0; i < gameObject.getGameObjects().size; i++) { + GameObject child = gameObject.getGameObjects().get(i); + update(child, delta); + } + } + } + + protected void fillRenderableEntities (Array rootObjects, Array list) { + for (GameObject root : rootObjects) { + if (!root.active || !root.isEditorVisible()) continue; + + boolean childrenVisibleFlag = true; + if (root.hasComponentType(RendererComponent.class)) { + RendererComponent rendererComponent = root.getComponentAssignableFrom(RendererComponent.class); + childrenVisibleFlag = rendererComponent.childrenVisible; + if (rendererComponent.visible) { + list.add(root); + } + } + if (childrenVisibleFlag) { + if (root.getGameObjects() != null) { + fillRenderableEntities(root.getGameObjects(), list); + } + } + } + + } + + + public static void renderBrokenComponent (Batch batch, GameObject gameObject, TransformComponent transformComponent) { + + float width = 1f; + float height = 1f; + if (gameObject.hasComponent(SpriteRendererComponent.class)) { + SpriteRendererComponent component = gameObject.getComponent(SpriteRendererComponent.class); + width = component.size.x; + height = component.size.y; + } + + batch.draw(brokenRegion, + transformComponent.worldPosition.x - 0.5f, transformComponent.worldPosition.y - 0.5f, + 0.5f, 0.5f, + 1f, 1f, + width * transformComponent.worldScale.x, height * transformComponent.worldScale.y, + transformComponent.worldRotation); + } + + + Array temp = new Array<>(); + + + public void buildRenderState (PolygonBatch batch, RenderState state, Array rootObjects) { + boolean hiearchyDirty = false; + for (GameObject rootObject : rootObjects) { + if (rootObject.hierarchyDirty) { + hiearchyDirty = true; + + //reset it because we gonna sort it this pass + rootObject.hierarchyDirty = false; + } + } + if (hiearchyDirty) { + state.list.clear(); + fillRenderableEntities(rootObjects, state.list); + } + sort(state.list); + } + + + public void renderObject (Batch batch, GameObject gameObject) { + if (gameObject.hasComponent(RoutineRendererComponent.class)) { + routineRenderer.render(batch, camera, gameObject, gameObject.getComponent(RoutineRendererComponent.class)); + } + + if (gameObject.hasComponent(SpriteRendererComponent.class)) { + spriteRenderer.render(batch, camera, gameObject, gameObject.getComponent(SpriteRendererComponent.class)); + } else if (gameObject.hasComponent(ParticleComponent.class)) { + particleRenderer.render(batch, camera, gameObject, gameObject.getComponent(ParticleComponent.class)); + } else if (gameObject.hasComponent(SpineRendererComponent.class)) { + spineRenderer.render(batch, camera, gameObject, gameObject.getComponent(SpineRendererComponent.class)); + } else if (gameObject.hasComponent(MapComponent.class)) { + mapRenderer.render(batch, camera, gameObject, gameObject.getComponent(MapComponent.class)); + } else if (gameObject.hasComponent(PathRendererComponent.class)) { + pathRenderer.render(batch, camera, gameObject, gameObject.getComponent(PathRendererComponent.class)); + } + + } + + public void buildRenderStateAndRender (PolygonBatch batch, Camera camera, RenderState state, GameObject root) { + temp.clear(); + temp.add(root); + buildRenderStateAndRender(batch, camera, state, temp); + } + + public void buildRenderStateAndRender (PolygonBatch batch, Camera camera, RenderState state, Array rootObjects) { + setCamera(camera); + + buildRenderState(batch, state, rootObjects); + for (int i = 0; i < state.list.size; i++) { + GameObject gameObject = state.list.get(i); + renderObject(batch, gameObject); + } + batch.setBlendFunction(GL20.GL_SRC_ALPHA, GL20.GL_ONE_MINUS_SRC_ALPHA); + } + + public void setCamera (Camera camera) { + this.camera = camera; + } + + /** + * Any renderers that may want to skip updates do it here + * + * @param skipUpdates + */ + public void setSkipUpdates (boolean skipUpdates) { + this.skipUpdates = skipUpdates; + } } diff --git a/runtimes/talos/src/main/java/com/talosvfx/talos/runtime/scene/render/ComponentRenderer.java b/runtimes/talos/src/main/java/com/talosvfx/talos/runtime/scene/render/ComponentRenderer.java index 681282f5f..62a2b318c 100644 --- a/runtimes/talos/src/main/java/com/talosvfx/talos/runtime/scene/render/ComponentRenderer.java +++ b/runtimes/talos/src/main/java/com/talosvfx/talos/runtime/scene/render/ComponentRenderer.java @@ -15,4 +15,8 @@ public ComponentRenderer (GameObjectRenderer gameObjectRenderer) { } public abstract void render (Batch batch, Camera camera, GameObject parent, T rendererComponent); + + public void update (GameObject gameObject, T component, float delta) { + + } } diff --git a/runtimes/talos/src/main/java/com/talosvfx/talos/runtime/scene/render/SkeletonComponentRenderer.java b/runtimes/talos/src/main/java/com/talosvfx/talos/runtime/scene/render/SkeletonComponentRenderer.java index b553efcc0..2bf606a6d 100644 --- a/runtimes/talos/src/main/java/com/talosvfx/talos/runtime/scene/render/SkeletonComponentRenderer.java +++ b/runtimes/talos/src/main/java/com/talosvfx/talos/runtime/scene/render/SkeletonComponentRenderer.java @@ -26,15 +26,8 @@ public SkeletonComponentRenderer (GameObjectRenderer gameObjectRenderer) { } @Override - public void render (Batch batch, Camera camera, GameObject gameObject, SpineRendererComponent rendererComponent) { + public void update (GameObject gameObject, SpineRendererComponent spineRendererComponent, float delta) { TransformComponent parentTransform = gameObject.getComponent(TransformComponent.class); - SpineRendererComponent spineRendererComponent = gameObject.getComponent(SpineRendererComponent.class); - - GameAsset gameResource = rendererComponent.getGameResource(); - if (gameResource.isBroken()) { - GameObjectRenderer.renderBrokenComponent(batch, gameObject, parentTransform); - return; - } spineRendererComponent.skeleton.setPosition(parentTransform.worldPosition.x, parentTransform.worldPosition.y); spineRendererComponent.skeleton.setScale(parentTransform.worldScale.x * spineRendererComponent.scale, parentTransform.worldScale.y * spineRendererComponent.scale); @@ -44,7 +37,21 @@ public void render (Batch batch, Camera camera, GameObject gameObject, SpineRend spineRendererComponent.animationState.update(Gdx.graphics.getDeltaTime()); spineRendererComponent.animationState.apply(spineRendererComponent.skeleton); } + spineRendererComponent.skeleton.updateWorldTransform(); + } + + @Override + public void render (Batch batch, Camera camera, GameObject gameObject, SpineRendererComponent rendererComponent) { + TransformComponent parentTransform = gameObject.getComponent(TransformComponent.class); + SpineRendererComponent spineRendererComponent = gameObject.getComponent(SpineRendererComponent.class); + + GameAsset gameResource = rendererComponent.getGameResource(); + if (gameResource.isBroken()) { + GameObjectRenderer.renderBrokenComponent(batch, gameObject, parentTransform); + return; + } + spineRendererComponent.skeleton.getColor().set(spineRendererComponent.finalColor); skeletonRenderer.draw(batch, spineRendererComponent.skeleton); @@ -52,24 +59,7 @@ public void render (Batch batch, Camera camera, GameObject gameObject, SpineRend batch.setBlendFunction(GL20.GL_SRC_ALPHA, GL20.GL_ONE_MINUS_SRC_ALPHA); - // update bone game objects - if (spineRendererComponent.generateGameObjectBones) { - Array boneGOs = gameObject.getChildrenWithBoneComponent(); - for (GameObject boneGO : boneGOs) { - BoneComponent boneComponent = boneGO.getComponent(BoneComponent.class); - Bone bone = boneComponent.getBone(); - TransformComponent transform = boneGO.getComponent(TransformComponent.class); - - transform.worldScale.set(bone.getWorldScaleX(), bone.getWorldScaleY()); - transform.worldRotation = bone.localToWorldRotation(bone.getRotation()); - transform.worldPosition.set(bone.getWorldX(), bone.getWorldY()); - - transform.position.set(bone.getX(), bone.getY()); - transform.rotation = bone.getRotation(); - transform.scale.set(bone.getScaleX(), bone.getScaleY()); - } - } } } diff --git a/runtimes/talos/src/main/java/com/talosvfx/talos/runtime/vfx/ParticleEffectInstance.java b/runtimes/talos/src/main/java/com/talosvfx/talos/runtime/vfx/ParticleEffectInstance.java index 6c720124b..e383a23d7 100644 --- a/runtimes/talos/src/main/java/com/talosvfx/talos/runtime/vfx/ParticleEffectInstance.java +++ b/runtimes/talos/src/main/java/com/talosvfx/talos/runtime/vfx/ParticleEffectInstance.java @@ -20,6 +20,7 @@ import com.badlogic.gdx.math.Vector3; import com.badlogic.gdx.utils.Array; import com.talosvfx.talos.runtime.vfx.render.ParticleRenderer; +import lombok.Getter; import java.util.Comparator; @@ -31,7 +32,10 @@ public class ParticleEffectInstance { Vector3 position = new Vector3(); + @Getter float worldRotation; + + @Getter Vector2 worldScale = new Vector2(1f, 1f); ScopePayload scopePayload = new ScopePayload(); diff --git a/runtimes/talos/src/main/java/com/talosvfx/talos/runtime/vfx/modules/QuadMeshGeneratorModule.java b/runtimes/talos/src/main/java/com/talosvfx/talos/runtime/vfx/modules/QuadMeshGeneratorModule.java index f468e5a3e..805917398 100644 --- a/runtimes/talos/src/main/java/com/talosvfx/talos/runtime/vfx/modules/QuadMeshGeneratorModule.java +++ b/runtimes/talos/src/main/java/com/talosvfx/talos/runtime/vfx/modules/QuadMeshGeneratorModule.java @@ -2,6 +2,7 @@ import com.badlogic.gdx.graphics.Camera; import com.badlogic.gdx.graphics.Color; +import com.badlogic.gdx.graphics.g2d.TextureAtlas; import com.badlogic.gdx.graphics.g2d.TextureRegion; import com.badlogic.gdx.math.MathUtils; import com.badlogic.gdx.math.Vector2; @@ -100,16 +101,40 @@ public void render (ParticleRenderer particleRenderer, MaterialModule materialMo float V = 0f; float V2 = 1f; + float offsetXNormalized = 0f; + float offsetYNormalized = 0f; + + float atlasOffsetScaleX = 1f; + float atlasOffsetScaleY = 1f; if (materialModule instanceof SpriteMaterialModule) { - TextureRegion textureRegion = ((SpriteMaterialModule)materialModule).getTextureRegion(); - if (textureRegion == null) return; + TextureAtlas.AtlasSprite atlasSprite = ((SpriteMaterialModule)materialModule).getTextureRegion(); + if (atlasSprite == null) return; + + TextureAtlas.AtlasRegion atlasRegion = atlasSprite.getAtlasRegion(); + + float offsetX = atlasRegion.getRegionX(); + float offsetY = atlasRegion.offsetY; + + int regionWidth = atlasRegion.getRegionWidth(); + int regionHeight = atlasRegion.getRegionHeight(); + + int originalWidth = atlasRegion.originalWidth; + int originalHeight = atlasRegion.originalHeight; - U = textureRegion.getU(); - U2 = textureRegion.getU2(); - V = textureRegion.getV(); - V2 = textureRegion.getV2(); + atlasOffsetScaleX = originalWidth / (float) regionWidth; + atlasOffsetScaleY = originalHeight / (float) regionHeight; + + // Calculate the normalized offset + offsetXNormalized = offsetX / (float) originalWidth; + offsetYNormalized = offsetY / (float) originalHeight; + + + U = atlasSprite.getU(); + U2 = atlasSprite.getU2(); + V = atlasSprite.getV(); + V2 = atlasSprite.getV2(); } rightWorldSpace.set(viewValues[0], viewValues[4], viewValues[8]); @@ -160,6 +185,9 @@ public void render (ParticleRenderer particleRenderer, MaterialModule materialMo float halfWidth = width / 2f; float halfHeight = height / 2f; + halfWidth /= atlasOffsetScaleX; + halfHeight /= atlasOffsetScaleY; + p1.set(-halfWidth, -halfHeight, 0); p2.set(halfWidth, -halfHeight, 0); p3.set(halfWidth, halfHeight, 0); @@ -243,6 +271,17 @@ public void render (ParticleRenderer particleRenderer, MaterialModule materialMo //get uvs from material +// p1.x = (p1.x + offsetXNormalized) * atlasOffsetScaleX; +// p1.y = (p1.y + offsetYNormalized) * atlasOffsetScaleY; +// +// p2.x = (p2.x + offsetXNormalized) * atlasOffsetScaleX; +// p2.y = (p2.y + offsetYNormalized) * atlasOffsetScaleY; +// +// p3.x = (p3.x + offsetXNormalized) * atlasOffsetScaleX; +// p3.y = (p3.y + offsetYNormalized) * atlasOffsetScaleY; +// +// p4.x = (p4.x + offsetXNormalized) * atlasOffsetScaleX; +// p4.y = (p4.y + offsetYNormalized) * atlasOffsetScaleY; verts[idx++] = p1.x; // x1 verts[idx++] = p1.y; // y1 diff --git a/runtimes/talos/src/main/java/com/talosvfx/talos/runtime/vfx/modules/SpriteMaterialModule.java b/runtimes/talos/src/main/java/com/talosvfx/talos/runtime/vfx/modules/SpriteMaterialModule.java index 0bcb641ff..6b3fa0c74 100644 --- a/runtimes/talos/src/main/java/com/talosvfx/talos/runtime/vfx/modules/SpriteMaterialModule.java +++ b/runtimes/talos/src/main/java/com/talosvfx/talos/runtime/vfx/modules/SpriteMaterialModule.java @@ -29,14 +29,13 @@ import com.talosvfx.talos.runtime.assets.GameAsset; import com.talosvfx.talos.runtime.assets.GameAssetType; import com.talosvfx.talos.runtime.assets.GameResourceOwner; -import com.talosvfx.talos.runtime.vfx.ParticleEmitterDescriptor; import com.talosvfx.talos.runtime.vfx.values.ModuleValue; public class SpriteMaterialModule extends MaterialModule implements GameResourceOwner, GameAsset.GameAssetUpdateListener { - private transient TextureRegion region; + private transient TextureAtlas.AtlasSprite region; public GameAsset asset; @@ -86,7 +85,7 @@ public void setToDefault () { @Override public void onUpdate () { if (asset != null && !asset.isBroken()) { - region = new TextureRegion(asset.getResource()); + region = new TextureAtlas.AtlasSprite(asset.getResource()); } } @@ -119,13 +118,13 @@ public void setGameAsset (GameAsset gameAsset) { asset.listeners.add(this); if (asset != null && !asset.isBroken()) { - region = new TextureRegion(asset.getResource()); + region = new TextureAtlas.AtlasSprite(asset.getResource()); } else { System.out.println("Sprite material asset broken " + asset.nameIdentifier); } } - public TextureRegion getTextureRegion () { + public TextureAtlas.AtlasSprite getTextureRegion () { return region; }