From fb2047937944c02c107a4c34532e30665a097620 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matthias=20Mail=C3=A4nder?= Date: Sun, 27 Sep 2020 13:36:04 +0200 Subject: [PATCH] Add .vxl support to the asset browser. --- OpenRA.Game/Graphics/Model.cs | 6 + .../Graphics/VoxelModelSequenceLoader.cs | 5 + .../Widgets/Logic/AssetBrowserLogic.cs | 77 ++++++- OpenRA.Mods.Common/Widgets/ModelWidget.cs | 217 ++++++++++++++++++ mods/common/chrome/assetbrowser.yaml | 54 +++++ mods/ts/mod.yaml | 2 +- 6 files changed, 354 insertions(+), 7 deletions(-) create mode 100644 OpenRA.Mods.Common/Widgets/ModelWidget.cs diff --git a/OpenRA.Game/Graphics/Model.cs b/OpenRA.Game/Graphics/Model.cs index e24e60762f..b912534992 100644 --- a/OpenRA.Game/Graphics/Model.cs +++ b/OpenRA.Game/Graphics/Model.cs @@ -45,6 +45,7 @@ namespace OpenRA.Graphics public interface IModelCache : IDisposable { + IModel GetModel(string model); IModel GetModelSequence(string model, string sequence); bool HasModelSequence(string model, string sequence); IVertexBuffer VertexBuffer { get; } @@ -66,6 +67,11 @@ namespace OpenRA.Graphics public void Dispose() { } + public IModel GetModel(string model) + { + throw new NotImplementedException(); + } + public IModel GetModelSequence(string model, string sequence) { throw new NotImplementedException(); diff --git a/OpenRA.Mods.Cnc/Graphics/VoxelModelSequenceLoader.cs b/OpenRA.Mods.Cnc/Graphics/VoxelModelSequenceLoader.cs index 0335275f20..d46b1b544a 100644 --- a/OpenRA.Mods.Cnc/Graphics/VoxelModelSequenceLoader.cs +++ b/OpenRA.Mods.Cnc/Graphics/VoxelModelSequenceLoader.cs @@ -86,6 +86,11 @@ namespace OpenRA.Mods.Cnc.Graphics return loader.Load(vxl, hva); } + public IModel GetModel(string model) + { + return loader.Load(model, model); + } + public IModel GetModelSequence(string model, string sequence) { try { return models[model][sequence]; } diff --git a/OpenRA.Mods.Common/Widgets/Logic/AssetBrowserLogic.cs b/OpenRA.Mods.Common/Widgets/Logic/AssetBrowserLogic.cs index 8f9295d111..f41a6de8fa 100644 --- a/OpenRA.Mods.Common/Widgets/Logic/AssetBrowserLogic.cs +++ b/OpenRA.Mods.Common/Widgets/Logic/AssetBrowserLogic.cs @@ -43,10 +43,12 @@ namespace OpenRA.Mods.Common.Widgets.Logic string currentFilename; IReadOnlyPackage currentPackage; Sprite[] currentSprites; + IModel currentVoxel; VqaPlayerWidget player = null; bool isVideoLoaded = false; bool isLoadError = false; int currentFrame; + WRot modelOrientation; [ObjectCreator.UseCtor] public AssetBrowserLogic(Widget widget, Action onExit, ModData modData, World world, Dictionary logicArgs) @@ -79,13 +81,24 @@ namespace OpenRA.Mods.Common.Widgets.Logic spriteWidget.GetSprite = () => currentSprites != null ? currentSprites[currentFrame] : null; currentPalette = spriteWidget.Palette; spriteWidget.GetPalette = () => currentPalette; - spriteWidget.IsVisible = () => !isVideoLoaded && !isLoadError; + spriteWidget.IsVisible = () => !isVideoLoaded && !isLoadError && currentSprites != null; } var playerWidget = panel.GetOrNull("PLAYER"); if (playerWidget != null) playerWidget.IsVisible = () => isVideoLoaded && !isLoadError; + var modelWidget = panel.GetOrNull("VOXEL"); + if (modelWidget != null) + { + modelWidget.GetVoxel = () => currentVoxel; + currentPalette = modelWidget.Palette; + modelWidget.GetPalette = () => currentPalette; + modelWidget.GetPlayerPalette = () => currentPalette; + modelWidget.GetRotation = () => modelOrientation; + modelWidget.IsVisible = () => !isVideoLoaded && !isLoadError && currentVoxel != null; + } + var errorLabelWidget = panel.GetOrNull("ERROR"); if (errorLabelWidget != null) errorLabelWidget.IsVisible = () => isLoadError; @@ -210,6 +223,46 @@ namespace OpenRA.Mods.Common.Widgets.Logic prevButton.IsVisible = () => !isVideoLoaded; } + var voxelContainer = panel.GetOrNull("VOXEL_SELECTOR"); + if (voxelContainer != null) + voxelContainer.IsVisible = () => currentVoxel != null; + + var rollSlider = panel.GetOrNull("ROLL_SLIDER"); + if (rollSlider != null) + { + rollSlider.OnChange += x => + { + var roll = (int)x; + modelOrientation = modelOrientation.WithRoll(new WAngle(roll)); + }; + + rollSlider.GetValue = () => modelOrientation.Roll.Angle; + } + + var pitchSlider = panel.GetOrNull("PITCH_SLIDER"); + if (pitchSlider != null) + { + pitchSlider.OnChange += x => + { + var pitch = (int)x; + modelOrientation = modelOrientation.WithPitch(new WAngle(pitch)); + }; + + pitchSlider.GetValue = () => modelOrientation.Pitch.Angle; + } + + var yawSlider = panel.GetOrNull("YAW_SLIDER"); + if (yawSlider != null) + { + yawSlider.OnChange += x => + { + var yaw = (int)x; + modelOrientation = modelOrientation.WithYaw(new WAngle(yaw)); + }; + + yawSlider.GetValue = () => modelOrientation.Yaw.Angle; + } + var assetBrowserModData = modData.Manifest.Get(); allowedExtensions = assetBrowserModData.SupportedExtensions; @@ -342,12 +395,24 @@ namespace OpenRA.Mods.Common.Widgets.Logic return true; } - currentSprites = world.Map.Rules.Sequences.SpriteCache[prefix + filename]; - currentFrame = 0; - if (frameSlider != null) + if (Path.GetExtension(filename.ToLowerInvariant()) == ".vxl") { - frameSlider.MaximumValue = (float)currentSprites.Length - 1; - frameSlider.Ticks = currentSprites.Length; + var voxelName = Path.GetFileNameWithoutExtension(filename); + currentVoxel = world.ModelCache.GetModel(voxelName); + currentSprites = null; + } + else + { + currentSprites = world.Map.Rules.Sequences.SpriteCache[prefix + filename]; + currentFrame = 0; + + if (frameSlider != null) + { + frameSlider.MaximumValue = (float)currentSprites.Length - 1; + frameSlider.Ticks = currentSprites.Length; + } + + currentVoxel = null; } } catch (Exception ex) diff --git a/OpenRA.Mods.Common/Widgets/ModelWidget.cs b/OpenRA.Mods.Common/Widgets/ModelWidget.cs new file mode 100644 index 0000000000..3e9a35eb6c --- /dev/null +++ b/OpenRA.Mods.Common/Widgets/ModelWidget.cs @@ -0,0 +1,217 @@ +#region Copyright & License Information +/* + * Copyright 2007-2020 The OpenRA Developers (see AUTHORS) + * This file is part of OpenRA, which is free software. It is made + * available to you under the terms of the GNU General Public License + * as published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. For more + * information, see COPYING. + */ +#endregion + +using System; +using System.Collections.Generic; +using OpenRA.Graphics; +using OpenRA.Mods.Common.Graphics; +using OpenRA.Widgets; + +namespace OpenRA.Mods.Common.Widgets +{ + public class ModelWidget : Widget + { + public string Palette = "terrain"; + public string PlayerPalette = "player"; + public string NormalsPalette = "normals"; + public string ShadowPalette = "shadow"; + public float Scale = 12f; + public int LightPitch = 142; + public int LightYaw = 682; + public float[] LightAmbientColor = new float[] { 0.6f, 0.6f, 0.6f }; + public float[] LightDiffuseColor = new float[] { 0.4f, 0.4f, 0.4f }; + public WRot Rotation = WRot.None; + public WAngle CameraAngle = WAngle.FromDegrees(40); + + public Func GetPalette; + public Func GetPlayerPalette; + public Func GetNormalsPalette; + public Func GetShadowPalette; + public Func GetLightAmbientColor; + public Func GetLightDiffuseColor; + public Func GetScale; + public Func GetLightPitch; + public Func GetLightYaw; + public Func GetVoxel; + public Func GetRotation; + public Func GetCameraAngle; + public int2 IdealPreviewSize { get; private set; } + + protected readonly WorldRenderer WorldRenderer; + + IFinalizedRenderable renderable; + + [ObjectCreator.UseCtor] + public ModelWidget(WorldRenderer worldRenderer) + { + GetPalette = () => Palette; + GetPlayerPalette = () => PlayerPalette; + GetNormalsPalette = () => NormalsPalette; + GetShadowPalette = () => ShadowPalette; + GetLightAmbientColor = () => LightAmbientColor; + GetLightDiffuseColor = () => LightDiffuseColor; + GetScale = () => Scale; + GetRotation = () => Rotation; + GetLightPitch = () => LightPitch; + GetLightYaw = () => LightYaw; + GetCameraAngle = () => CameraAngle; + WorldRenderer = worldRenderer; + } + + protected ModelWidget(ModelWidget other) + : base(other) + { + Palette = other.Palette; + GetPalette = other.GetPalette; + GetVoxel = other.GetVoxel; + + WorldRenderer = other.WorldRenderer; + } + + public override Widget Clone() + { + return new ModelWidget(this); + } + + IModel cachedVoxel; + string cachedPalette; + string cachedPlayerPalette; + string cachedNormalsPalette; + string cachedShadowPalette; + float cachedScale; + WRot cachedRotation; + float[] cachedLightAmbientColor = new float[] { 0, 0, 0 }; + float[] cachedLightDiffuseColor = new float[] { 0, 0, 0 }; + int cachedLightPitch; + int cachedLightYaw; + WAngle cachedCameraAngle; + PaletteReference paletteReference; + PaletteReference paletteReferencePlayer; + PaletteReference paletteReferenceNormals; + PaletteReference paletteReferenceShadow; + + public override void Draw() + { + if (renderable == null) + return; + + renderable.Render(WorldRenderer); + } + + public override void PrepareRenderables() + { + var voxel = GetVoxel(); + var palette = GetPalette(); + var playerPalette = GetPlayerPalette(); + var normalsPalette = GetNormalsPalette(); + var shadowPalette = GetShadowPalette(); + var scale = GetScale(); + var rotation = GetRotation(); + var lightAmbientColor = GetLightAmbientColor(); + var lightDiffuseColor = GetLightDiffuseColor(); + var lightPitch = GetLightPitch(); + var lightYaw = GetLightYaw(); + var cameraAngle = GetCameraAngle(); + + if (voxel == null || palette == null) + return; + + if (voxel != cachedVoxel) + cachedVoxel = voxel; + + if (palette != cachedPalette) + { + if (string.IsNullOrEmpty(palette) && string.IsNullOrEmpty(playerPalette)) + return; + + var paletteName = string.IsNullOrEmpty(palette) ? playerPalette : palette; + paletteReference = WorldRenderer.Palette(paletteName); + cachedPalette = paletteName; + } + + if (playerPalette != cachedPlayerPalette) + { + paletteReferencePlayer = WorldRenderer.Palette(playerPalette); + cachedPlayerPalette = playerPalette; + } + + if (normalsPalette != cachedNormalsPalette) + { + paletteReferenceNormals = WorldRenderer.Palette(normalsPalette); + cachedNormalsPalette = normalsPalette; + } + + if (shadowPalette != cachedShadowPalette) + { + paletteReferenceShadow = WorldRenderer.Palette(shadowPalette); + cachedShadowPalette = shadowPalette; + } + + if (scale != cachedScale) + cachedScale = scale; + + if (rotation != cachedRotation) + cachedRotation = rotation; + + if (lightPitch != cachedLightPitch) + cachedLightPitch = lightPitch; + + if (lightYaw != cachedLightYaw) + cachedLightYaw = lightYaw; + + if (cachedLightAmbientColor[0] != lightAmbientColor[0] || cachedLightAmbientColor[1] != lightAmbientColor[1] || cachedLightAmbientColor[2] != lightAmbientColor[2]) + cachedLightAmbientColor = lightAmbientColor; + + if (cachedLightDiffuseColor[0] != lightDiffuseColor[0] || cachedLightDiffuseColor[1] != lightDiffuseColor[1] || cachedLightDiffuseColor[2] != lightDiffuseColor[2]) + cachedLightDiffuseColor = lightDiffuseColor; + + if (cameraAngle != cachedCameraAngle) + cachedCameraAngle = cameraAngle; + + if (cachedVoxel == null) + return; + + var animation = new ModelAnimation( + cachedVoxel, + () => WVec.Zero, + () => cachedRotation, + () => false, + () => 0, + true); + + var animations = new ModelAnimation[] { animation }; + + ModelPreview preview = new ModelPreview( + new ModelAnimation[] { animation }, WVec.Zero, 0, + cachedScale, + new WAngle(cachedLightPitch), + new WAngle(cachedLightYaw), + cachedLightAmbientColor, + cachedLightDiffuseColor, + cachedCameraAngle, + paletteReference, + paletteReferenceNormals, + paletteReferenceShadow); + + var screenBounds = animation.ScreenBounds(WPos.Zero, WorldRenderer, scale); + IdealPreviewSize = new int2(screenBounds.Width, screenBounds.Height); + var origin = RenderOrigin + new int2(RenderBounds.Size.Width / 2, RenderBounds.Size.Height / 2); + + var camera = new WRot(WAngle.Zero, cachedCameraAngle - new WAngle(256), new WAngle(256)); + var modelRenderable = new UIModelRenderable( + animations, WPos.Zero, origin, 0, camera, scale, + WRot.None, cachedLightAmbientColor, cachedLightDiffuseColor, + paletteReferencePlayer, paletteReferenceNormals, paletteReferenceShadow); + + renderable = modelRenderable.PrepareRender(WorldRenderer); + } + } +} diff --git a/mods/common/chrome/assetbrowser.yaml b/mods/common/chrome/assetbrowser.yaml index 86fb0cbc54..7ee0150139 100644 --- a/mods/common/chrome/assetbrowser.yaml +++ b/mods/common/chrome/assetbrowser.yaml @@ -103,6 +103,11 @@ Background@ASSETBROWSER_PANEL: Width: PARENT_RIGHT Height: PARENT_BOTTOM AspectRatio: 1 + Model@VOXEL: + Width: PARENT_RIGHT + Height: PARENT_BOTTOM + Palette: colorpicker + PlayerPalette: colorpicker Label@ERROR: Y: 1 X: 5 @@ -189,6 +194,55 @@ Background@ASSETBROWSER_PANEL: Height: 25 Font: TinyBold Align: Left + Container@VOXEL_SELECTOR: + X: 60 + Y: 425 + Children: + Label@ROLL: + X: 140 + Y: 1 + Width: 40 + Height: 25 + Font: TinyBold + Align: Left + Text: Roll + Slider@ROLL_SLIDER: + X: 165 + Y: 3 + Width: 100 + Height: 20 + MinimumValue: 1 + MaximumValue: 1023 + Label@PITCH: + X: 310 + Y: 1 + Width: 40 + Height: 25 + Font: TinyBold + Align: Left + Text: Pitch + Slider@PITCH_SLIDER: + X: 335 + Y: 3 + Width: 100 + Height: 20 + MinimumValue: 1 + MaximumValue: 1023 + Label@YAW: + X: 480 + Y: 1 + Width: 40 + Height: 25 + Font: TinyBold + Align: Left + Text: Yaw + Slider@YAW_SLIDER: + X: 505 + Y: 3 + Width: 100 + Height: 20 + MinimumValue: 1 + MaximumValue: 1023 Button@CLOSE_BUTTON: Key: escape X: PARENT_RIGHT - 180 diff --git a/mods/ts/mod.yaml b/mods/ts/mod.yaml index 6933dfce58..3fb73234d7 100644 --- a/mods/ts/mod.yaml +++ b/mods/ts/mod.yaml @@ -261,7 +261,7 @@ SpriteSequenceFormat: TilesetSpecificSpriteSequence ModelSequenceFormat: VoxelModelSequence AssetBrowser: - SupportedExtensions: .shp, .tem, .sno, .vqa + SupportedExtensions: .shp, .tem, .sno, .vqa, .vxl GameSpeeds: slowest: