diff --git a/Editor/CMakeLists.txt b/Editor/CMakeLists.txt
index d048851d7..ee3d51f96 100644
--- a/Editor/CMakeLists.txt
+++ b/Editor/CMakeLists.txt
@@ -34,6 +34,7 @@ set (SOURCE_FILES
SoftBodyWindow.cpp
ColliderWindow.cpp
HierarchyWindow.cpp
+ ExpressionWindow.cpp
OptionsWindow.cpp
ComponentsWindow.cpp
TerrainGenerator.cpp
diff --git a/Editor/CameraWindow.cpp b/Editor/CameraWindow.cpp
index 96b15a9db..fea03d046 100644
--- a/Editor/CameraWindow.cpp
+++ b/Editor/CameraWindow.cpp
@@ -361,6 +361,7 @@ void CameraWindow::ResizeLayout()
y += padding;
};
+ add_fullwidth(resetButton);
add(farPlaneSlider);
add(nearPlaneSlider);
add(fovSlider);
@@ -371,12 +372,11 @@ void CameraWindow::ResizeLayout()
add(movespeedSlider);
add(rotationspeedSlider);
add(accelerationSlider);
- add(resetButton);
add_right(fpsCheckBox);
y += 20;
- add(proxyButton);
+ add_fullwidth(proxyButton);
add_right(followCheckBox);
add(followSlider);
diff --git a/Editor/ComponentsWindow.cpp b/Editor/ComponentsWindow.cpp
index 4f71ace64..a91ca318b 100644
--- a/Editor/ComponentsWindow.cpp
+++ b/Editor/ComponentsWindow.cpp
@@ -38,6 +38,7 @@ void ComponentsWindow::Create(EditorComponent* _editor)
colliderWnd.Create(editor);
hierarchyWnd.Create(editor);
cameraComponentWnd.Create(editor);
+ expressionWnd.Create(editor);
newComponentCombo.Create("Add: ");
@@ -46,12 +47,12 @@ void ComponentsWindow::Create(EditorComponent* _editor)
newComponentCombo.selected_font.anim.typewriter.character_start = 1;
newComponentCombo.SetTooltip("Add a component to the last selected entity.");
newComponentCombo.SetInvalidSelectionText("...");
- newComponentCombo.AddItem("Name", 0);
+ newComponentCombo.AddItem("Name " ICON_NAME, 0);
newComponentCombo.AddItem("Layer " ICON_LAYER, 1);
newComponentCombo.AddItem("Hierarchy " ICON_HIERARCHY, 19);
newComponentCombo.AddItem("Transform " ICON_TRANSFORM, 2);
newComponentCombo.AddItem("Light " ICON_POINTLIGHT, 3);
- newComponentCombo.AddItem("Matetial " ICON_MATERIAL, 4);
+ newComponentCombo.AddItem("Material " ICON_MATERIAL, 4);
newComponentCombo.AddItem("Spring", 5);
newComponentCombo.AddItem("Inverse Kinematics " ICON_IK, 6);
newComponentCombo.AddItem("Sound " ICON_SOUND, 7);
@@ -297,6 +298,7 @@ void ComponentsWindow::Create(EditorComponent* _editor)
AddWidget(&colliderWnd);
AddWidget(&hierarchyWnd);
AddWidget(&cameraComponentWnd);
+ AddWidget(&expressionWnd);
materialWnd.SetVisible(false);
weatherWnd.SetVisible(false);
@@ -321,6 +323,7 @@ void ComponentsWindow::Create(EditorComponent* _editor)
colliderWnd.SetVisible(false);
hierarchyWnd.SetVisible(false);
cameraComponentWnd.SetVisible(false);
+ expressionWnd.SetVisible(false);
SetSize(editor->optionsWnd.GetSize());
@@ -663,4 +666,17 @@ void ComponentsWindow::ResizeLayout()
{
scriptWnd.SetVisible(false);
}
+
+ if (scene.expressions.Contains(expressionWnd.entity))
+ {
+ expressionWnd.SetVisible(true);
+ expressionWnd.SetPos(pos);
+ expressionWnd.SetSize(XMFLOAT2(width, expressionWnd.GetScale().y));
+ pos.y += expressionWnd.GetSize().y;
+ pos.y += padding;
+ }
+ else
+ {
+ expressionWnd.SetVisible(false);
+ }
}
diff --git a/Editor/ComponentsWindow.h b/Editor/ComponentsWindow.h
index f840dfa67..3d399d694 100644
--- a/Editor/ComponentsWindow.h
+++ b/Editor/ComponentsWindow.h
@@ -23,6 +23,7 @@
#include "ColliderWindow.h"
#include "HierarchyWindow.h"
#include "CameraComponentWindow.h"
+#include "ExpressionWindow.h"
class EditorComponent;
@@ -59,4 +60,5 @@ public:
ColliderWindow colliderWnd;
HierarchyWindow hierarchyWnd;
CameraComponentWindow cameraComponentWnd;
+ ExpressionWindow expressionWnd;
};
diff --git a/Editor/Editor.cpp b/Editor/Editor.cpp
index 421e68dbb..7c9db43dd 100644
--- a/Editor/Editor.cpp
+++ b/Editor/Editor.cpp
@@ -347,6 +347,7 @@ void EditorComponent::Load()
componentsWnd.colliderWnd.SetEntity(INVALID_ENTITY);
componentsWnd.hierarchyWnd.SetEntity(INVALID_ENTITY);
componentsWnd.cameraComponentWnd.SetEntity(INVALID_ENTITY);
+ componentsWnd.expressionWnd.SetEntity(INVALID_ENTITY);
optionsWnd.RefreshEntityTree();
ResetHistory();
@@ -1301,6 +1302,7 @@ void EditorComponent::Update(float dt)
componentsWnd.colliderWnd.SetEntity(INVALID_ENTITY);
componentsWnd.hierarchyWnd.SetEntity(INVALID_ENTITY);
componentsWnd.cameraComponentWnd.SetEntity(INVALID_ENTITY);
+ componentsWnd.expressionWnd.SetEntity(INVALID_ENTITY);
}
else
{
@@ -1338,6 +1340,7 @@ void EditorComponent::Update(float dt)
componentsWnd.colliderWnd.SetEntity(picked.entity);
componentsWnd.hierarchyWnd.SetEntity(picked.entity);
componentsWnd.cameraComponentWnd.SetEntity(picked.entity);
+ componentsWnd.expressionWnd.SetEntity(picked.entity);
if (picked.subsetIndex >= 0)
{
diff --git a/Editor/Editor_SOURCE.vcxitems b/Editor/Editor_SOURCE.vcxitems
index 5a272a35d..304630c49 100644
--- a/Editor/Editor_SOURCE.vcxitems
+++ b/Editor/Editor_SOURCE.vcxitems
@@ -23,6 +23,7 @@
+
@@ -139,6 +140,7 @@
+
diff --git a/Editor/Editor_SOURCE.vcxitems.filters b/Editor/Editor_SOURCE.vcxitems.filters
index 5bad7a874..cde3855c3 100644
--- a/Editor/Editor_SOURCE.vcxitems.filters
+++ b/Editor/Editor_SOURCE.vcxitems.filters
@@ -82,6 +82,7 @@
+
@@ -128,6 +129,7 @@
+
diff --git a/Editor/ExpressionWindow.cpp b/Editor/ExpressionWindow.cpp
new file mode 100644
index 000000000..8a1bc895d
--- /dev/null
+++ b/Editor/ExpressionWindow.cpp
@@ -0,0 +1,331 @@
+#include "stdafx.h"
+#include "ExpressionWindow.h"
+#include "Editor.h"
+
+using namespace wi::ecs;
+using namespace wi::scene;
+
+void ExpressionWindow::Create(EditorComponent* _editor)
+{
+ editor = _editor;
+
+ wi::gui::Window::Create(ICON_EXPRESSION " Expression", wi::gui::Window::WindowControls::COLLAPSE | wi::gui::Window::WindowControls::CLOSE);
+ SetSize(XMFLOAT2(670, 500));
+
+ closeButton.SetTooltip("Delete ExpressionComponent");
+ OnClose([=](wi::gui::EventArgs args) {
+
+ wi::Archive& archive = editor->AdvanceHistory();
+ archive << EditorComponent::HISTORYOP_COMPONENT_DATA;
+ editor->RecordEntity(archive, entity);
+
+ editor->GetCurrentScene().expressions.Remove(entity);
+
+ editor->RecordEntity(archive, entity);
+
+ editor->optionsWnd.RefreshEntityTree();
+ });
+
+ float x = 60;
+ float y = 4;
+ float hei = 20;
+ float step = hei + 2;
+ float wid = 220;
+
+ blinkFrequencySlider.Create(0, 1, 0, 1000, "Blinks: ");
+ blinkFrequencySlider.SetTooltip("Specifies the number of blinks per second.");
+ blinkFrequencySlider.SetSize(XMFLOAT2(wid, hei));
+ blinkFrequencySlider.OnSlide([=](wi::gui::EventArgs args) {
+ wi::scene::Scene& scene = editor->GetCurrentScene();
+ ExpressionComponent* expression_mastering = scene.expressions.GetComponent(entity);
+ if (expression_mastering == nullptr)
+ return;
+ expression_mastering->blink_frequency = args.fValue;
+ });
+ AddWidget(&blinkFrequencySlider);
+
+ blinkLengthSlider.Create(0, 1, 0, 1000, "Blink Length: ");
+ blinkLengthSlider.SetSize(XMFLOAT2(wid, hei));
+ blinkLengthSlider.OnSlide([=](wi::gui::EventArgs args) {
+ wi::scene::Scene& scene = editor->GetCurrentScene();
+ ExpressionComponent* expression_mastering = scene.expressions.GetComponent(entity);
+ if (expression_mastering == nullptr)
+ return;
+ expression_mastering->blink_length = args.fValue;
+ });
+ AddWidget(&blinkLengthSlider);
+
+ blinkCountSlider.Create(1, 4, 2, 3, "Blink Count: ");
+ blinkCountSlider.SetSize(XMFLOAT2(wid, hei));
+ blinkCountSlider.OnSlide([=](wi::gui::EventArgs args) {
+ wi::scene::Scene& scene = editor->GetCurrentScene();
+ ExpressionComponent* expression_mastering = scene.expressions.GetComponent(entity);
+ if (expression_mastering == nullptr)
+ return;
+ expression_mastering->blink_count = args.iValue;
+ });
+ AddWidget(&blinkCountSlider);
+
+ lookFrequencySlider.Create(0, 1, 0, 1000, "Looks: ");
+ lookFrequencySlider.SetTooltip("Specifies the number of look-aways per second.");
+ lookFrequencySlider.SetSize(XMFLOAT2(wid, hei));
+ lookFrequencySlider.OnSlide([=](wi::gui::EventArgs args) {
+ wi::scene::Scene& scene = editor->GetCurrentScene();
+ ExpressionComponent* expression_mastering = scene.expressions.GetComponent(entity);
+ if (expression_mastering == nullptr)
+ return;
+ expression_mastering->look_frequency = args.fValue;
+ });
+ AddWidget(&lookFrequencySlider);
+
+ lookLengthSlider.Create(0, 1, 0, 1000, "Look Length: ");
+ lookLengthSlider.SetSize(XMFLOAT2(wid, hei));
+ lookLengthSlider.OnSlide([=](wi::gui::EventArgs args) {
+ wi::scene::Scene& scene = editor->GetCurrentScene();
+ ExpressionComponent* expression_mastering = scene.expressions.GetComponent(entity);
+ if (expression_mastering == nullptr)
+ return;
+ expression_mastering->look_length = args.fValue;
+ });
+ AddWidget(&lookLengthSlider);
+
+ expressionList.Create("Expressions: ");
+ expressionList.SetSize(XMFLOAT2(wid, 200));
+ expressionList.SetPos(XMFLOAT2(4, y += step));
+ expressionList.OnSelect([=](wi::gui::EventArgs args) {
+ wi::scene::Scene& scene = editor->GetCurrentScene();
+ ExpressionComponent* expression_mastering = scene.expressions.GetComponent(entity);
+ if (expression_mastering == nullptr)
+ return;
+ if (args.iValue >= expression_mastering->expressions.size())
+ return;
+
+ const ExpressionComponent::Expression& expression = expression_mastering->expressions[args.iValue];
+ binaryCheckBox.SetCheck(expression.IsBinary());
+ weightSlider.SetValue(expression.weight);
+ overrideMouthCombo.SetSelectedByUserdataWithoutCallback((uint64_t)expression.override_mouth);
+ overrideBlinkCombo.SetSelectedByUserdataWithoutCallback((uint64_t)expression.override_blink);
+ overrideLookCombo.SetSelectedByUserdataWithoutCallback((uint64_t)expression.override_look);
+ });
+ AddWidget(&expressionList);
+
+ binaryCheckBox.Create("Binary: ");
+ binaryCheckBox.SetSize(XMFLOAT2(hei, hei));
+ binaryCheckBox.OnClick([=](wi::gui::EventArgs args) {
+ wi::scene::Scene& scene = editor->GetCurrentScene();
+ ExpressionComponent* expression_mastering = scene.expressions.GetComponent(entity);
+ if (expression_mastering == nullptr)
+ return;
+
+ int count = expressionList.GetItemCount();
+ for (int i = 0; i < count; ++i)
+ {
+ if (!expressionList.GetItem(i).selected)
+ continue;
+ if (i >= expression_mastering->expressions.size())
+ return;
+
+ ExpressionComponent::Expression& expression = expression_mastering->expressions[i];
+ expression.SetBinary(args.bValue);
+ expression.SetDirty();
+ }
+ });
+ AddWidget(&binaryCheckBox);
+
+ weightSlider.Create(0, 1, 0, 100000, "Weight: ");
+ weightSlider.SetSize(XMFLOAT2(wid, hei));
+ weightSlider.SetPos(XMFLOAT2(x, y += step));
+ weightSlider.OnSlide([&](wi::gui::EventArgs args) {
+ wi::scene::Scene& scene = editor->GetCurrentScene();
+ ExpressionComponent* expression_mastering = scene.expressions.GetComponent(entity);
+ if (expression_mastering == nullptr)
+ return;
+ if (args.iValue >= expression_mastering->expressions.size())
+ return;
+
+ int count = expressionList.GetItemCount();
+ for (int i = 0; i < count; ++i)
+ {
+ if (!expressionList.GetItem(i).selected)
+ continue;
+ if (i >= expression_mastering->expressions.size())
+ return;
+
+ ExpressionComponent::Expression& expression = expression_mastering->expressions[i];
+ expression.weight = args.fValue;
+ expression.SetDirty();
+ }
+ });
+ AddWidget(&weightSlider);
+
+ overrideMouthCombo.Create("Override Mouth: ");
+ overrideMouthCombo.SetTooltip("Lip sync override control");
+ overrideMouthCombo.SetSize(XMFLOAT2(wid, hei));
+ overrideMouthCombo.AddItem("None", (uint64_t)ExpressionComponent::Override::None);
+ overrideMouthCombo.AddItem("Block", (uint64_t)ExpressionComponent::Override::Block);
+ overrideMouthCombo.AddItem("Blend", (uint64_t)ExpressionComponent::Override::Blend);
+ overrideMouthCombo.OnSelect([=](wi::gui::EventArgs args) {
+ wi::scene::Scene& scene = editor->GetCurrentScene();
+ ExpressionComponent* expression_mastering = scene.expressions.GetComponent(entity);
+ if (expression_mastering == nullptr)
+ return;
+ if (args.iValue >= expression_mastering->expressions.size())
+ return;
+
+ int count = expressionList.GetItemCount();
+ for (int i = 0; i < count; ++i)
+ {
+ if (!expressionList.GetItem(i).selected)
+ continue;
+ if (i >= expression_mastering->expressions.size())
+ return;
+
+ ExpressionComponent::Expression& expression = expression_mastering->expressions[i];
+ expression.override_mouth = (ExpressionComponent::Override)args.userdata;
+ expression.SetDirty();
+ }
+ });
+ AddWidget(&overrideMouthCombo);
+
+ overrideBlinkCombo.Create("Override Blink: ");
+ overrideBlinkCombo.SetTooltip("Blink override control");
+ overrideBlinkCombo.SetSize(XMFLOAT2(wid, hei));
+ overrideBlinkCombo.AddItem("None", (uint64_t)ExpressionComponent::Override::None);
+ overrideBlinkCombo.AddItem("Block", (uint64_t)ExpressionComponent::Override::Block);
+ overrideBlinkCombo.AddItem("Blend", (uint64_t)ExpressionComponent::Override::Blend);
+ overrideBlinkCombo.OnSelect([=](wi::gui::EventArgs args) {
+ wi::scene::Scene& scene = editor->GetCurrentScene();
+ ExpressionComponent* expression_mastering = scene.expressions.GetComponent(entity);
+ if (expression_mastering == nullptr)
+ return;
+ if (args.iValue >= expression_mastering->expressions.size())
+ return;
+
+ int count = expressionList.GetItemCount();
+ for (int i = 0; i < count; ++i)
+ {
+ if (!expressionList.GetItem(i).selected)
+ continue;
+ if (i >= expression_mastering->expressions.size())
+ return;
+
+ ExpressionComponent::Expression& expression = expression_mastering->expressions[i];
+ expression.override_blink = (ExpressionComponent::Override)args.userdata;
+ expression.SetDirty();
+ }
+ });
+ AddWidget(&overrideBlinkCombo);
+
+ overrideLookCombo.Create("Override Look: ");
+ overrideLookCombo.SetTooltip("Look-away override control");
+ overrideLookCombo.SetSize(XMFLOAT2(wid, hei));
+ overrideLookCombo.AddItem("None", (uint64_t)ExpressionComponent::Override::None);
+ overrideLookCombo.AddItem("Block", (uint64_t)ExpressionComponent::Override::Block);
+ overrideLookCombo.AddItem("Blend", (uint64_t)ExpressionComponent::Override::Blend);
+ overrideLookCombo.OnSelect([=](wi::gui::EventArgs args) {
+ wi::scene::Scene& scene = editor->GetCurrentScene();
+ ExpressionComponent* expression_mastering = scene.expressions.GetComponent(entity);
+ if (expression_mastering == nullptr)
+ return;
+ if (args.iValue >= expression_mastering->expressions.size())
+ return;
+
+ int count = expressionList.GetItemCount();
+ for (int i = 0; i < count; ++i)
+ {
+ if (!expressionList.GetItem(i).selected)
+ continue;
+ if (i >= expression_mastering->expressions.size())
+ return;
+
+ ExpressionComponent::Expression& expression = expression_mastering->expressions[i];
+ expression.override_look = (ExpressionComponent::Override)args.userdata;
+ expression.SetDirty();
+ }
+ });
+ AddWidget(&overrideLookCombo);
+
+
+ SetMinimized(true);
+ SetVisible(false);
+
+ SetEntity(INVALID_ENTITY);
+}
+
+void ExpressionWindow::SetEntity(Entity entity)
+{
+ if (this->entity == entity)
+ return;
+
+ this->entity = entity;
+
+ Scene& scene = editor->GetCurrentScene();
+
+ const ExpressionComponent* expression_mastering = scene.expressions.GetComponent(entity);
+
+ if (expression_mastering != nullptr)
+ {
+ blinkFrequencySlider.SetValue(expression_mastering->blink_frequency);
+ blinkLengthSlider.SetValue(expression_mastering->blink_length);
+ blinkCountSlider.SetValue(expression_mastering->blink_count);
+ lookFrequencySlider.SetValue(expression_mastering->look_frequency);
+ lookLengthSlider.SetValue(expression_mastering->look_length);
+
+ expressionList.ClearItems();
+ for (const ExpressionComponent::Expression& expression : expression_mastering->expressions)
+ {
+ expressionList.AddItem(expression.name);
+ }
+ }
+}
+
+void ExpressionWindow::ResizeLayout()
+{
+ wi::gui::Window::ResizeLayout();
+ const float padding = 4;
+ const float width = GetWidgetAreaSize().x;
+ float y = padding;
+ float jump = 20;
+
+ const float margin_left = 110;
+ const float margin_right = 45;
+
+ auto add = [&](wi::gui::Widget& widget) {
+ if (!widget.IsVisible())
+ return;
+ widget.SetPos(XMFLOAT2(margin_left, y));
+ widget.SetSize(XMFLOAT2(width - margin_left - margin_right, widget.GetScale().y));
+ y += widget.GetSize().y;
+ y += padding;
+ };
+ auto add_right = [&](wi::gui::Widget& widget) {
+ if (!widget.IsVisible())
+ return;
+ widget.SetPos(XMFLOAT2(width - margin_right - widget.GetSize().x, y));
+ y += widget.GetSize().y;
+ y += padding;
+ };
+ auto add_fullwidth = [&](wi::gui::Widget& widget) {
+ if (!widget.IsVisible())
+ return;
+ const float margin_left = padding;
+ const float margin_right = padding;
+ widget.SetPos(XMFLOAT2(margin_left, y));
+ widget.SetSize(XMFLOAT2(width - margin_left - margin_right, widget.GetScale().y));
+ y += widget.GetSize().y;
+ y += padding;
+ };
+
+ add(blinkFrequencySlider);
+ add(blinkLengthSlider);
+ add(blinkCountSlider);
+ add(lookFrequencySlider);
+ add(lookLengthSlider);
+ add_fullwidth(expressionList);
+ add_right(binaryCheckBox);
+ add(weightSlider);
+ add(overrideMouthCombo);
+ add(overrideBlinkCombo);
+ add(overrideLookCombo);
+
+}
diff --git a/Editor/ExpressionWindow.h b/Editor/ExpressionWindow.h
new file mode 100644
index 000000000..aa21345e1
--- /dev/null
+++ b/Editor/ExpressionWindow.h
@@ -0,0 +1,29 @@
+#pragma once
+#include "WickedEngine.h"
+
+class EditorComponent;
+
+class ExpressionWindow : public wi::gui::Window
+{
+public:
+ void Create(EditorComponent* editor);
+
+ EditorComponent* editor = nullptr;
+ wi::ecs::Entity entity = wi::ecs::INVALID_ENTITY;
+ void SetEntity(wi::ecs::Entity entity);
+
+ wi::gui::Slider blinkFrequencySlider;
+ wi::gui::Slider blinkLengthSlider;
+ wi::gui::Slider blinkCountSlider;
+ wi::gui::Slider lookFrequencySlider;
+ wi::gui::Slider lookLengthSlider;
+ wi::gui::TreeList expressionList;
+ wi::gui::Slider weightSlider;
+ wi::gui::CheckBox binaryCheckBox;
+ wi::gui::ComboBox overrideMouthCombo;
+ wi::gui::ComboBox overrideBlinkCombo;
+ wi::gui::ComboBox overrideLookCombo;
+
+ void ResizeLayout() override;
+};
+
diff --git a/Editor/IconDefinitions.h b/Editor/IconDefinitions.h
index 57ae45eb7..c98db66bd 100644
--- a/Editor/IconDefinitions.h
+++ b/Editor/IconDefinitions.h
@@ -29,6 +29,7 @@
#define ICON_COLLIDER ICON_FA_CAPSULES
#define ICON_SCRIPT ICON_FA_SCROLL
#define ICON_HIERARCHY ICON_FA_ARROWS_DOWN_TO_PEOPLE
+#define ICON_EXPRESSION ICON_FA_MASKS_THEATER
#define ICON_TERRAIN ICON_FA_MOUNTAIN_SUN
diff --git a/Editor/ModelImporter_GLTF.cpp b/Editor/ModelImporter_GLTF.cpp
index f10dc7ba6..c9e6779b8 100644
--- a/Editor/ModelImporter_GLTF.cpp
+++ b/Editor/ModelImporter_GLTF.cpp
@@ -140,6 +140,7 @@ namespace tinygltf
struct LoaderState
{
+ std::string name;
tinygltf::Model gltfModel;
Scene* scene;
wi::unordered_map entityMap; // node -> entity
@@ -321,6 +322,7 @@ void ImportModel_GLTF(const std::string& fileName, Scene& scene)
state.rootEntity = CreateEntity();
scene.transforms.Create(state.rootEntity);
scene.names.Create(state.rootEntity) = name;
+ state.name = name;
// Create materials:
for (auto& x : state.gltfModel.materials)
@@ -775,12 +777,6 @@ void ImportModel_GLTF(const std::string& fileName, Scene& scene)
scene.Component_Attach(meshEntity, state.rootEntity);
MeshComponent& mesh = *scene.meshes.GetComponent(meshEntity);
- mesh.morph_targets.resize(x.weights.size());
- for (size_t i = 0; i < mesh.morph_targets.size(); i++)
- {
- mesh.morph_targets[i].weight = static_cast(x.weights[i]);
- }
-
for (auto& prim : x.primitives)
{
assert(prim.indices >= 0);
@@ -1216,109 +1212,143 @@ void ImportModel_GLTF(const std::string& fileName, Scene& scene)
}
}
}
+ }
- for (size_t i = 0; i < mesh.morph_targets.size(); i++)
+
+ mesh.morph_targets.resize(prim.targets.size());
+ for (size_t i = 0; i < prim.targets.size(); i++)
+ {
+ MeshComponent::MorphTarget& morph_target = mesh.morph_targets[i];
+ for (auto& attr : prim.targets[i])
{
- for (auto& attr : prim.targets[i])
+ const std::string& attr_name = attr.first;
+ int attr_data = attr.second;
+
+ const tinygltf::Accessor& accessor = state.gltfModel.accessors[attr_data];
+
+ if (!attr_name.compare("POSITION"))
{
- const std::string& attr_name = attr.first;
- int attr_data = attr.second;
-
- const tinygltf::Accessor& accessor = state.gltfModel.accessors[attr_data];
- const tinygltf::BufferView& bufferView = state.gltfModel.bufferViews[accessor.bufferView];
- const tinygltf::Buffer& buffer = state.gltfModel.buffers[bufferView.buffer];
-
- int stride = accessor.ByteStride(bufferView);
- size_t vertexCount = accessor.count;
-
- const unsigned char* data = buffer.data.data() + accessor.byteOffset + bufferView.byteOffset;
-
- if (!attr_name.compare("POSITION"))
+ if (accessor.sparse.isSparse)
{
- mesh.morph_targets[i].vertex_positions.resize(vertexOffset + vertexCount);
- for (size_t j = 0; j < vertexCount; ++j)
- {
- mesh.morph_targets[i].vertex_positions[vertexOffset + j] = ((XMFLOAT3*)data)[j];
- }
+ auto& sparse = accessor.sparse;
+ const tinygltf::BufferView& sparse_indices_view = state.gltfModel.bufferViews[sparse.indices.bufferView];
+ const tinygltf::BufferView& sparse_values_view = state.gltfModel.bufferViews[sparse.values.bufferView];
+ const tinygltf::Buffer& sparse_indices_buffer = state.gltfModel.buffers[sparse_indices_view.buffer];
+ const tinygltf::Buffer& sparse_values_buffer = state.gltfModel.buffers[sparse_values_view.buffer];
+ const uint8_t* sparse_indices_data = sparse_indices_buffer.data.data() + sparse.indices.byteOffset + sparse_indices_view.byteOffset;
+ const uint8_t* sparse_values_data = sparse_values_buffer.data.data() + sparse.values.byteOffset + sparse_values_view.byteOffset;
+ const size_t sparseOffset = morph_target.sparse_indices.size();
+ morph_target.vertex_positions.resize(sparseOffset + sparse.count);
+ morph_target.sparse_indices.resize(sparseOffset + sparse.count);
- if (accessor.sparse.isSparse)
+ switch (sparse.indices.componentType)
{
- auto& sparse = accessor.sparse;
- const tinygltf::BufferView& sparse_indices_view = state.gltfModel.bufferViews[sparse.indices.bufferView];
- const tinygltf::BufferView& sparse_values_view = state.gltfModel.bufferViews[sparse.values.bufferView];
- const tinygltf::Buffer& sparse_indices_buffer = state.gltfModel.buffers[sparse_indices_view.buffer];
- const tinygltf::Buffer& sparse_values_buffer = state.gltfModel.buffers[sparse_values_view.buffer];
- const uint8_t* sparse_indices_data = sparse_indices_buffer.data.data() + sparse.indices.byteOffset + sparse_indices_view.byteOffset;
- const uint8_t* sparse_values_data = sparse_values_buffer.data.data() + sparse.values.byteOffset + sparse_values_view.byteOffset;
- switch (sparse.indices.componentType)
+ default:
+ case TINYGLTF_COMPONENT_TYPE_UNSIGNED_BYTE:
+ for (int s = 0; s < sparse.count; ++s)
{
- default:
- case TINYGLTF_COMPONENT_TYPE_UNSIGNED_BYTE:
- for (int s = 0; s < sparse.count; ++s)
- {
- mesh.morph_targets[i].vertex_positions[sparse_indices_data[s]] = ((const XMFLOAT3*)sparse_values_data)[s];
- }
- break;
- case TINYGLTF_COMPONENT_TYPE_UNSIGNED_SHORT:
- for (int s = 0; s < sparse.count; ++s)
- {
- mesh.morph_targets[i].vertex_positions[((const uint16_t*)sparse_indices_data)[s]] = ((const XMFLOAT3*)sparse_values_data)[s];
- }
- break;
- case TINYGLTF_COMPONENT_TYPE_UNSIGNED_INT:
- for (int s = 0; s < sparse.count; ++s)
- {
- mesh.morph_targets[i].vertex_positions[((const uint32_t*)sparse_indices_data)[s]] = ((const XMFLOAT3*)sparse_values_data)[s];
- }
- break;
+ morph_target.sparse_indices[sparseOffset + s] = vertexOffset + sparse_indices_data[s];
+ morph_target.vertex_positions[sparseOffset + s] = ((const XMFLOAT3*)sparse_values_data)[s];
}
+ break;
+ case TINYGLTF_COMPONENT_TYPE_UNSIGNED_SHORT:
+ for (int s = 0; s < sparse.count; ++s)
+ {
+ morph_target.sparse_indices[sparseOffset + s] = vertexOffset + ((const uint16_t*)sparse_indices_data)[s];
+ morph_target.vertex_positions[sparseOffset + s] = ((const XMFLOAT3*)sparse_values_data)[s];
+ }
+ break;
+ case TINYGLTF_COMPONENT_TYPE_UNSIGNED_INT:
+ for (int s = 0; s < sparse.count; ++s)
+ {
+ morph_target.sparse_indices[sparseOffset + s] = vertexOffset + ((const uint32_t*)sparse_indices_data)[s];
+ morph_target.vertex_positions[sparseOffset + s] = ((const XMFLOAT3*)sparse_values_data)[s];
+ }
+ break;
}
}
- else if (!attr_name.compare("NORMAL"))
+ else
{
- mesh.morph_targets[i].vertex_normals.resize(vertexOffset + vertexCount);
+ const tinygltf::BufferView& bufferView = state.gltfModel.bufferViews[accessor.bufferView];
+ const tinygltf::Buffer& buffer = state.gltfModel.buffers[bufferView.buffer];
+
+ int stride = accessor.ByteStride(bufferView);
+ size_t vertexCount = accessor.count;
+
+ const unsigned char* data = buffer.data.data() + accessor.byteOffset + bufferView.byteOffset;
+
+ morph_target.vertex_positions.resize(vertexOffset + vertexCount);
for (size_t j = 0; j < vertexCount; ++j)
{
- mesh.morph_targets[i].vertex_normals[vertexOffset + j] = ((XMFLOAT3*)data)[j];
+ morph_target.vertex_positions[vertexOffset + j] = ((XMFLOAT3*)data)[j];
}
+ }
+ }
+ else if (!attr_name.compare("NORMAL"))
+ {
+ if (accessor.sparse.isSparse)
+ {
+ auto& sparse = accessor.sparse;
+ const tinygltf::BufferView& sparse_indices_view = state.gltfModel.bufferViews[sparse.indices.bufferView];
+ const tinygltf::BufferView& sparse_values_view = state.gltfModel.bufferViews[sparse.values.bufferView];
+ const tinygltf::Buffer& sparse_indices_buffer = state.gltfModel.buffers[sparse_indices_view.buffer];
+ const tinygltf::Buffer& sparse_values_buffer = state.gltfModel.buffers[sparse_values_view.buffer];
+ const uint8_t* sparse_indices_data = sparse_indices_buffer.data.data() + sparse.indices.byteOffset + sparse_indices_view.byteOffset;
+ const uint8_t* sparse_values_data = sparse_values_buffer.data.data() + sparse.values.byteOffset + sparse_values_view.byteOffset;
+ const size_t sparseOffset = morph_target.sparse_indices.size();
+ morph_target.vertex_normals.resize(sparseOffset + sparse.count);
+ morph_target.sparse_indices.resize(sparseOffset + sparse.count);
- if (accessor.sparse.isSparse)
+ switch (sparse.indices.componentType)
{
- auto& sparse = accessor.sparse;
- const tinygltf::BufferView& sparse_indices_view = state.gltfModel.bufferViews[sparse.indices.bufferView];
- const tinygltf::BufferView& sparse_values_view = state.gltfModel.bufferViews[sparse.values.bufferView];
- const tinygltf::Buffer& sparse_indices_buffer = state.gltfModel.buffers[sparse_indices_view.buffer];
- const tinygltf::Buffer& sparse_values_buffer = state.gltfModel.buffers[sparse_values_view.buffer];
- const uint8_t* sparse_indices_data = sparse_indices_buffer.data.data() + sparse.indices.byteOffset + sparse_indices_view.byteOffset;
- const uint8_t* sparse_values_data = sparse_values_buffer.data.data() + sparse.values.byteOffset + sparse_values_view.byteOffset;
- switch (sparse.indices.componentType)
+ default:
+ case TINYGLTF_COMPONENT_TYPE_UNSIGNED_BYTE:
+ for (int s = 0; s < sparse.count; ++s)
{
- default:
- case TINYGLTF_COMPONENT_TYPE_UNSIGNED_BYTE:
- for (int s = 0; s < sparse.count; ++s)
- {
- mesh.morph_targets[i].vertex_normals[sparse_indices_data[s]] = ((const XMFLOAT3*)sparse_values_data)[s];
- }
- break;
- case TINYGLTF_COMPONENT_TYPE_UNSIGNED_SHORT:
- for (int s = 0; s < sparse.count; ++s)
- {
- mesh.morph_targets[i].vertex_normals[((const uint16_t*)sparse_indices_data)[s]] = ((const XMFLOAT3*)sparse_values_data)[s];
- }
- break;
- case TINYGLTF_COMPONENT_TYPE_UNSIGNED_INT:
- for (int s = 0; s < sparse.count; ++s)
- {
- mesh.morph_targets[i].vertex_normals[((const uint32_t*)sparse_indices_data)[s]] = ((const XMFLOAT3*)sparse_values_data)[s];
- }
- break;
+ morph_target.sparse_indices[sparseOffset + s] = vertexOffset + sparse_indices_data[s];
+ morph_target.vertex_normals[sparseOffset + s] = ((const XMFLOAT3*)sparse_values_data)[s];
}
+ break;
+ case TINYGLTF_COMPONENT_TYPE_UNSIGNED_SHORT:
+ for (int s = 0; s < sparse.count; ++s)
+ {
+ morph_target.sparse_indices[sparseOffset + s] = vertexOffset + ((const uint16_t*)sparse_indices_data)[s];
+ morph_target.vertex_normals[sparseOffset + s] = ((const XMFLOAT3*)sparse_values_data)[s];
+ }
+ break;
+ case TINYGLTF_COMPONENT_TYPE_UNSIGNED_INT:
+ for (int s = 0; s < sparse.count; ++s)
+ {
+ morph_target.sparse_indices[sparseOffset + s] = vertexOffset + ((const uint32_t*)sparse_indices_data)[s];
+ morph_target.vertex_normals[sparseOffset + s] = ((const XMFLOAT3*)sparse_values_data)[s];
+ }
+ break;
+ }
+ }
+ else
+ {
+ const tinygltf::BufferView& bufferView = state.gltfModel.bufferViews[accessor.bufferView];
+ const tinygltf::Buffer& buffer = state.gltfModel.buffers[bufferView.buffer];
+
+ int stride = accessor.ByteStride(bufferView);
+ size_t vertexCount = accessor.count;
+
+ const unsigned char* data = buffer.data.data() + accessor.byteOffset + bufferView.byteOffset;
+
+ morph_target.vertex_normals.resize(vertexOffset + vertexCount);
+ for (size_t j = 0; j < vertexCount; ++j)
+ {
+ morph_target.vertex_normals[vertexOffset + j] = ((XMFLOAT3*)data)[j];
}
}
}
}
}
+ }
+ for (size_t i = 0; i < x.weights.size(); i++)
+ {
+ mesh.morph_targets[i].weight = static_cast(x.weights[i]);
}
mesh.CreateRenderData();
@@ -1587,8 +1617,146 @@ void Import_Extension_VRM(LoaderState& state)
TransformComponent& transform = *state.scene->transforms.GetComponent(state.rootEntity);
transform.RotateRollPitchYaw(XMFLOAT3(0, XM_PI, 0));
+ if (ext_vrm->second.Has("blendShapeMaster"))
+ {
+ // https://github.com/vrm-c/vrm-specification/tree/master/specification/0.0#vrm-extension-morph-setting-jsonextensionsvrmblendshapemaster
+ Entity entity = CreateEntity();
+ ExpressionComponent& component = state.scene->expressions.Create(entity);
+ state.scene->Component_Attach(entity, state.rootEntity);
+ state.scene->names.Create(entity) = state.name + "_blendShapeMaster";
+
+ const auto& blendShapeMaster = ext_vrm->second.Get("blendShapeMaster");
+ if (blendShapeMaster.Has("blendShapeGroups"))
+ {
+ const auto& blendShapeGroups = blendShapeMaster.Get("blendShapeGroups");
+ for (size_t blendShapeGroup_index = 0; blendShapeGroup_index < blendShapeGroups.ArrayLen(); ++blendShapeGroup_index)
+ {
+ const auto& blendShapeGroup = blendShapeGroups.Get(int(blendShapeGroup_index));
+ ExpressionComponent::Expression& expression = component.expressions.emplace_back();
+
+ if (blendShapeGroup.Has("name"))
+ {
+ const auto& value = blendShapeGroup.Get("name");
+ expression.name = value.Get();
+ }
+ if (blendShapeGroup.Has("presetName"))
+ {
+ const auto& value = blendShapeGroup.Get("presetName");
+ std::string presetName = wi::helper::toUpper(value.Get());
+
+ if (!presetName.compare("JOY"))
+ {
+ expression.preset = ExpressionComponent::Preset::Happy;
+ }
+ else if (!presetName.compare("ANGRY"))
+ {
+ expression.preset = ExpressionComponent::Preset::Angry;
+ }
+ else if (!presetName.compare("SORROW"))
+ {
+ expression.preset = ExpressionComponent::Preset::Sad;
+ }
+ else if (!presetName.compare("FUN"))
+ {
+ expression.preset = ExpressionComponent::Preset::Relaxed;
+ }
+ else if (!presetName.compare("A"))
+ {
+ expression.preset = ExpressionComponent::Preset::Aa;
+ }
+ else if (!presetName.compare("I"))
+ {
+ expression.preset = ExpressionComponent::Preset::Ih;
+ }
+ else if (!presetName.compare("U"))
+ {
+ expression.preset = ExpressionComponent::Preset::Ou;
+ }
+ else if (!presetName.compare("E"))
+ {
+ expression.preset = ExpressionComponent::Preset::Ee;
+ }
+ else if (!presetName.compare("O"))
+ {
+ expression.preset = ExpressionComponent::Preset::Oh;
+ }
+ else if (!presetName.compare("BLINK"))
+ {
+ expression.preset = ExpressionComponent::Preset::Blink;
+ }
+ else if (!presetName.compare("BLINK_L"))
+ {
+ expression.preset = ExpressionComponent::Preset::BlinkLeft;
+ }
+ else if (!presetName.compare("BLINK_R"))
+ {
+ expression.preset = ExpressionComponent::Preset::BlinkRight;
+ }
+ else if (!presetName.compare("LOOKUP"))
+ {
+ expression.preset = ExpressionComponent::Preset::LookUp;
+ }
+ else if (!presetName.compare("LOOKDOWN"))
+ {
+ expression.preset = ExpressionComponent::Preset::LookDown;
+ }
+ else if (!presetName.compare("LOOKLEFT"))
+ {
+ expression.preset = ExpressionComponent::Preset::LookLeft;
+ }
+ else if (!presetName.compare("LOOKRIGHT"))
+ {
+ expression.preset = ExpressionComponent::Preset::LookRight;
+ }
+ else if (!presetName.compare("NEUTRAL"))
+ {
+ expression.preset = ExpressionComponent::Preset::Neutral;
+ }
+
+ const size_t preset_index = (size_t)expression.preset;
+ if (preset_index < arraysize(component.presets))
+ {
+ component.presets[preset_index] = (int)component.expressions.size() - 1;
+ }
+ }
+ if (blendShapeGroup.Has("isBinary"))
+ {
+ const auto& value = blendShapeGroup.Get("isBinary");
+ expression.SetBinary(value.Get());
+ }
+ if (blendShapeGroup.Has("binds"))
+ {
+ const auto& binds = blendShapeGroup.Get("binds");
+ for (size_t bind_index = 0; bind_index < binds.ArrayLen(); ++bind_index)
+ {
+ const auto& bind = binds.Get(int(bind_index));
+ ExpressionComponent::Expression::MorphTargetBinding& morph_target_binding = expression.morph_target_bindings.emplace_back();
+
+ if (bind.Has("mesh"))
+ {
+ const auto& value = bind.Get("mesh");
+ morph_target_binding.meshID = state.scene->meshes.GetEntity(value.GetNumberAsInt());
+ }
+ if (bind.Has("index"))
+ {
+ const auto& value = bind.Get("index");
+ morph_target_binding.index = value.GetNumberAsInt();
+ }
+ if (bind.Has("weight"))
+ {
+ const auto& value = bind.Get("weight");
+ morph_target_binding.weight = float(value.GetNumberAsInt()) / 100.0f;
+ }
+ }
+ }
+ }
+ }
+ }
+
if (ext_vrm->second.Has("secondaryAnimation"))
{
+ // https://github.com/vrm-c/vrm-specification/tree/master/specification/0.0#vrm-extension-spring-bone-settings-jsonextensionsvrmsecondaryanimation
+
const auto& secondaryAnimation = ext_vrm->second.Get("secondaryAnimation");
if (secondaryAnimation.Has("boneGroups"))
{
@@ -1721,9 +1889,182 @@ void Import_Extension_VRM(LoaderState& state)
void Import_Extension_VRMC(LoaderState& state)
{
+ auto ext_vrm = state.gltfModel.extensions.find("VRMC_vrm");
+ if (ext_vrm != state.gltfModel.extensions.end())
+ {
+ if (ext_vrm->second.Has("expressions"))
+ {
+ // https://github.com/vrm-c/vrm-specification/blob/master/specification/VRMC_vrm-1.0-beta/expressions.md#vrmc_vrmexpressions
+ Entity entity = CreateEntity();
+ ExpressionComponent& component = state.scene->expressions.Create(entity);
+ state.scene->Component_Attach(entity, state.rootEntity);
+ state.scene->names.Create(entity) = state.name + "_expressions";
+
+ const auto& expressions = ext_vrm->second.Get("expressions");
+ static const char* expression_types[] = {
+ "preset",
+ "custom",
+ };
+
+ for (auto& expression_type : expression_types)
+ {
+ if (expressions.Has(expression_type))
+ {
+ const auto& names = expressions.Get(expression_type);
+ for (auto& name : names.Keys())
+ {
+ const auto& vrm_expression = names.Get(name);
+ ExpressionComponent::Expression& expression = component.expressions.emplace_back();
+
+ if (!strcmp(expression_type, "preset"))
+ {
+ std::string presetName = wi::helper::toUpper(name);
+ if (!presetName.compare("HAPPY"))
+ {
+ expression.preset = ExpressionComponent::Preset::Happy;
+ }
+ else if (!presetName.compare("ANGRY"))
+ {
+ expression.preset = ExpressionComponent::Preset::Angry;
+ }
+ else if (!presetName.compare("SAD"))
+ {
+ expression.preset = ExpressionComponent::Preset::Sad;
+ }
+ else if (!presetName.compare("RELAXED"))
+ {
+ expression.preset = ExpressionComponent::Preset::Relaxed;
+ }
+ else if (!presetName.compare("SURPRISED"))
+ {
+ expression.preset = ExpressionComponent::Preset::Surprised;
+ }
+ else if (!presetName.compare("AA"))
+ {
+ expression.preset = ExpressionComponent::Preset::Aa;
+ }
+ else if (!presetName.compare("IH"))
+ {
+ expression.preset = ExpressionComponent::Preset::Ih;
+ }
+ else if (!presetName.compare("OU"))
+ {
+ expression.preset = ExpressionComponent::Preset::Ou;
+ }
+ else if (!presetName.compare("EE"))
+ {
+ expression.preset = ExpressionComponent::Preset::Ee;
+ }
+ else if (!presetName.compare("OH"))
+ {
+ expression.preset = ExpressionComponent::Preset::Oh;
+ }
+ else if (!presetName.compare("BLINK"))
+ {
+ expression.preset = ExpressionComponent::Preset::Blink;
+ }
+ else if (!presetName.compare("BLINKLEFT"))
+ {
+ expression.preset = ExpressionComponent::Preset::BlinkLeft;
+ }
+ else if (!presetName.compare("BLINKRIGHT"))
+ {
+ expression.preset = ExpressionComponent::Preset::BlinkRight;
+ }
+ else if (!presetName.compare("LOOKUP"))
+ {
+ expression.preset = ExpressionComponent::Preset::LookUp;
+ }
+ else if (!presetName.compare("LOOKDOWN"))
+ {
+ expression.preset = ExpressionComponent::Preset::LookDown;
+ }
+ else if (!presetName.compare("LOOKLEFT"))
+ {
+ expression.preset = ExpressionComponent::Preset::LookLeft;
+ }
+ else if (!presetName.compare("LOOKRIGHT"))
+ {
+ expression.preset = ExpressionComponent::Preset::LookRight;
+ }
+ else if (!presetName.compare("NEUTRAL"))
+ {
+ expression.preset = ExpressionComponent::Preset::Neutral;
+ }
+
+ const size_t preset_index = (size_t)expression.preset;
+ if (preset_index < arraysize(component.presets))
+ {
+ component.presets[preset_index] = (int)component.expressions.size() - 1;
+ }
+ }
+ expression.name = name;
+
+ if (vrm_expression.Has("isBinary"))
+ {
+ const auto& value = vrm_expression.Get("isBinary");
+ expression.SetBinary(value.Get());
+ }
+ if (vrm_expression.Has("overrideMouth"))
+ {
+ const auto& value = vrm_expression.Get("overrideMouth");
+ const std::string& override_enum = value.Get();
+ if (!override_enum.compare("block"))
+ {
+ expression.override_mouth = ExpressionComponent::Override::Block;
+ }
+ if (!override_enum.compare("blend"))
+ {
+ expression.override_mouth = ExpressionComponent::Override::Blend;
+ }
+ }
+ if (vrm_expression.Has("morphTargetBinds"))
+ {
+ const auto& morpTargetBinds = vrm_expression.Get("morphTargetBinds");
+ for (size_t morphTargetBind_index = 0; morphTargetBind_index < morpTargetBinds.ArrayLen(); ++morphTargetBind_index)
+ {
+ const auto& morphTargetBind = morpTargetBinds.Get(int(morphTargetBind_index));
+ ExpressionComponent::Expression::MorphTargetBinding& morph_target_binding = expression.morph_target_bindings.emplace_back();
+
+ if (morphTargetBind.Has("node"))
+ {
+ const auto& value = morphTargetBind.Get("node");
+ morph_target_binding.meshID = state.scene->meshes.GetEntity(state.gltfModel.nodes[value.GetNumberAsInt()].mesh);
+ }
+ if (morphTargetBind.Has("index"))
+ {
+ const auto& value = morphTargetBind.Get("index");
+ morph_target_binding.index = value.GetNumberAsInt();
+ }
+ if (morphTargetBind.Has("weight"))
+ {
+ const auto& value = morphTargetBind.Get("weight");
+ morph_target_binding.weight = float(value.GetNumberAsDouble());
+ }
+ }
+ }
+ //if (vrm_expression.Has("materialColorBinds"))
+ //{
+ // const auto& materialColorBinds = vrm_expression.Get("materialColorBinds");
+ // // TODO: find example model and implement
+ //}
+ //if (vrm_expression.Has("textureTransformBinds "))
+ //{
+ // const auto& textureTransformBinds = vrm_expression.Get("textureTransformBinds");
+ // // TODO: find example model and implement
+ //}
+
+ }
+ }
+ }
+ }
+ }
+
auto ext_vrmc_springbone = state.gltfModel.extensions.find("VRMC_springBone");
if (ext_vrmc_springbone != state.gltfModel.extensions.end())
{
+ // https://github.com/vrm-c/vrm-specification/tree/master/specification/VRMC_springBone-1.0-beta
+
// Colliders:
if (ext_vrmc_springbone->second.Has("colliders"))
{
diff --git a/Editor/OptionsWindow.cpp b/Editor/OptionsWindow.cpp
index e06920a80..1ae883cbc 100644
--- a/Editor/OptionsWindow.cpp
+++ b/Editor/OptionsWindow.cpp
@@ -307,6 +307,7 @@ void OptionsWindow::Create(EditorComponent* _editor)
filterCombo.AddItem("Camera " ICON_CAMERA, (uint64_t)Filter::Camera);
filterCombo.AddItem("Armature " ICON_ARMATURE, (uint64_t)Filter::Armature);
filterCombo.AddItem("Script " ICON_SCRIPT, (uint64_t)Filter::Script);
+ filterCombo.AddItem("Expression " ICON_EXPRESSION, (uint64_t)Filter::Expression);
filterCombo.SetTooltip("Apply filtering to the Entities");
filterCombo.OnSelect([&](wi::gui::EventArgs args) {
filter = (Filter)args.userdata;
@@ -884,6 +885,10 @@ void OptionsWindow::PushToEntityTree(wi::ecs::Entity entity, int level)
{
item.name += ICON_SCRIPT " ";
}
+ if (scene.expressions.Contains(entity))
+ {
+ item.name += ICON_EXPRESSION " ";
+ }
if (entity == terragen.terrainEntity)
{
item.name += ICON_TERRAIN " ";
@@ -1134,6 +1139,14 @@ void OptionsWindow::RefreshEntityTree()
}
}
+ if (has_flag(filter, Filter::Expression))
+ {
+ for (size_t i = 0; i < scene.expressions.GetCount(); ++i)
+ {
+ PushToEntityTree(scene.expressions.GetEntity(i), 0);
+ }
+ }
+
entitytree_added_items.clear();
entitytree_opened_items.clear();
}
diff --git a/Editor/OptionsWindow.h b/Editor/OptionsWindow.h
index 437054c50..7178eeab9 100644
--- a/Editor/OptionsWindow.h
+++ b/Editor/OptionsWindow.h
@@ -55,6 +55,7 @@ public:
Armature = 1 << 15,
Collider = 1 << 16,
Script = 1 << 17,
+ Expression = 1 << 18,
All = ~0ull,
} filter = Filter::All;
diff --git a/WickedEngine/wiGUI.cpp b/WickedEngine/wiGUI.cpp
index ddc8ae766..0fde93dcd 100644
--- a/WickedEngine/wiGUI.cpp
+++ b/WickedEngine/wiGUI.cpp
@@ -1627,6 +1627,10 @@ namespace wi::gui
{
this->value = value;
}
+ void Slider::SetValue(int value)
+ {
+ this->value = float(value);
+ }
float Slider::GetValue() const
{
return value;
@@ -4435,6 +4439,12 @@ namespace wi::gui
{
items.push_back(item);
}
+ void TreeList::AddItem(const std::string& name)
+ {
+ Item item;
+ item.name = name;
+ AddItem(item);
+ }
void TreeList::ClearItems()
{
items.clear();
diff --git a/WickedEngine/wiGUI.h b/WickedEngine/wiGUI.h
index 9a31f7f51..3bb663ad7 100644
--- a/WickedEngine/wiGUI.h
+++ b/WickedEngine/wiGUI.h
@@ -452,6 +452,7 @@ namespace wi::gui
wi::Sprite sprites_knob[WIDGETSTATE_COUNT];
void SetValue(float value);
+ void SetValue(int value);
float GetValue() const;
void SetRange(float start, float end);
@@ -692,6 +693,7 @@ namespace wi::gui
void Create(const std::string& name);
void AddItem(const Item& item);
+ void AddItem(const std::string& name);
void ClearItems();
bool HasScrollbar() const;
diff --git a/WickedEngine/wiRenderer.cpp b/WickedEngine/wiRenderer.cpp
index 5c0bc1cc2..b4e6e1613 100644
--- a/WickedEngine/wiRenderer.cpp
+++ b/WickedEngine/wiRenderer.cpp
@@ -3839,7 +3839,7 @@ void UpdateRenderData(
Entity entity = vis.scene->meshes.GetEntity(i);
const MeshComponent& mesh = vis.scene->meshes[i];
- if (mesh.dirty_morph)
+ if (mesh.dirty_morph && !mesh.vertex_positions_morphed.empty())
{
mesh.dirty_morph = false;
GraphicsDevice::GPUAllocation allocation = device->AllocateGPU(mesh.vb_pos_nor_wind.size, cmd);
diff --git a/WickedEngine/wiScene.cpp b/WickedEngine/wiScene.cpp
index f13d8206e..d2e3d243c 100644
--- a/WickedEngine/wiScene.cpp
+++ b/WickedEngine/wiScene.cpp
@@ -1734,6 +1734,8 @@ namespace wi::scene
}
geometryArrayMapped = (ShaderGeometry*)geometryUploadBuffer[device->GetBufferIndex()].mapped_data;
+ RunExpressionUpdateSystem(ctx);
+
RunMeshUpdateSystem(ctx);
RunMaterialUpdateSystem(ctx);
@@ -3464,6 +3466,207 @@ namespace wi::scene
});
}
+ void Scene::RunExpressionUpdateSystem(wi::jobsystem::context& ctx)
+ {
+ for (size_t i = 0; i < expressions.GetCount(); ++i)
+ {
+ ExpressionComponent& expression_mastering = expressions[i];
+
+ // Procedural blink:
+ expression_mastering.blink_timer += expression_mastering.blink_frequency * dt;
+ if (expression_mastering.blink_timer >= 1)
+ {
+ int blink = expression_mastering.presets[(int)ExpressionComponent::Preset::Blink];
+ if (blink >= 0 && blink < expression_mastering.expressions.size())
+ {
+ ExpressionComponent::Expression& expression = expression_mastering.expressions[blink];
+ expression_mastering.blink_count = std::max(1, expression_mastering.blink_count);
+ float one_blink_length = expression_mastering.blink_length * expression_mastering.blink_frequency;
+ float all_blink_length = one_blink_length * (float)expression_mastering.blink_count;
+ float blink_index = std::floor(wi::math::Lerp(0, (float)expression_mastering.blink_count, (expression_mastering.blink_timer - 1) / all_blink_length));
+ float blink_trim = 1 + one_blink_length * blink_index;
+ float blink_state = wi::math::InverseLerp(0, one_blink_length, expression_mastering.blink_timer - blink_trim);
+ if (blink_state < 0.5f)
+ {
+ // closing
+ expression.weight = wi::math::Lerp(0, 1, wi::math::saturate(blink_state * 2));
+ }
+ else
+ {
+ // opening
+ expression.weight = wi::math::Lerp(1, 0, wi::math::saturate((blink_state - 0.5f) * 2));
+ }
+ if (expression_mastering.blink_timer >= 1 + all_blink_length)
+ {
+ expression.weight = 0;
+ expression_mastering.blink_timer = 0;
+ }
+ expression.SetDirty();
+ }
+ }
+
+ // Procedural look:
+ if (expression_mastering.look_timer == 0)
+ {
+ // Roll new random look direction for next look away event:
+ float vertical = wi::random::GetRandom(-1.0f, 1.0f);
+ float horizontal = wi::random::GetRandom(-1.0f, 1.0f);
+ expression_mastering.look_weights[0] = wi::math::saturate(vertical);
+ expression_mastering.look_weights[1] = wi::math::saturate(-vertical);
+ expression_mastering.look_weights[2] = wi::math::saturate(horizontal);
+ expression_mastering.look_weights[3] = wi::math::saturate(-horizontal);
+ }
+ expression_mastering.look_timer += expression_mastering.look_frequency * dt;
+ if (expression_mastering.look_timer >= 1)
+ {
+ int looks[] = {
+ expression_mastering.presets[(int)ExpressionComponent::Preset::LookDown],
+ expression_mastering.presets[(int)ExpressionComponent::Preset::LookUp],
+ expression_mastering.presets[(int)ExpressionComponent::Preset::LookLeft],
+ expression_mastering.presets[(int)ExpressionComponent::Preset::LookRight],
+ };
+ for (int idx = 0; idx= 0 && look < expression_mastering.expressions.size())
+ {
+ ExpressionComponent::Expression& expression = expression_mastering.expressions[look];
+ float look_state = wi::math::InverseLerp(0, expression_mastering.look_length * expression_mastering.look_frequency, expression_mastering.look_timer - 1);
+ if (look_state < 0.25f)
+ {
+ expression.weight = wi::math::Lerp(0, weight, wi::math::saturate(look_state * 4));
+ }
+ else
+ {
+ expression.weight = wi::math::Lerp(weight, 0, wi::math::saturate((look_state - 0.75f) * 4));
+ }
+ expression.SetDirty();
+ }
+ }
+ if (expression_mastering.look_timer >= 1 + expression_mastering.look_length * expression_mastering.look_frequency)
+ {
+ expression_mastering.look_timer = 0;
+ }
+ }
+
+ float overrideMouthBlend = 0;
+ float overrideBlinkBlend = 0;
+ float overrideLookBlend = 0;
+
+ // Pass 1: reset targets that will be modified by expressions:
+ // Also accumulate override weights
+ for(ExpressionComponent::Expression& expression : expression_mastering.expressions)
+ {
+ const float blend = expression.IsBinary() ? (expression.weight > 0 ? 1 : 0) : expression.weight;
+ if (expression.override_mouth == ExpressionComponent::Override::Block)
+ {
+ overrideMouthBlend += 1;
+ }
+ if (expression.override_mouth == ExpressionComponent::Override::Blend)
+ {
+ overrideMouthBlend += blend;
+ }
+ if (expression.override_blink == ExpressionComponent::Override::Block)
+ {
+ overrideBlinkBlend += 1;
+ }
+ if (expression.override_blink == ExpressionComponent::Override::Blend)
+ {
+ overrideBlinkBlend += blend;
+ }
+ if (expression.override_look == ExpressionComponent::Override::Block)
+ {
+ overrideLookBlend += 1;
+ }
+ if (expression.override_look == ExpressionComponent::Override::Blend)
+ {
+ overrideLookBlend += blend;
+ }
+
+ if (!expression.IsDirty())
+ continue;
+
+ for (const ExpressionComponent::Expression::MorphTargetBinding& morph_target_binding : expression.morph_target_bindings)
+ {
+ MeshComponent* mesh = meshes.GetComponent(morph_target_binding.meshID);
+ if (mesh != nullptr && (int)mesh->morph_targets.size() > morph_target_binding.index)
+ {
+ MeshComponent::MorphTarget& morph_target = mesh->morph_targets[morph_target_binding.index];
+ if (morph_target.weight > 0)
+ {
+ morph_target.weight = 0;
+ }
+ }
+ }
+ }
+
+ // Override weights are factored in:
+ const int mouths[] = {
+ expression_mastering.presets[(int)ExpressionComponent::Preset::Aa],
+ expression_mastering.presets[(int)ExpressionComponent::Preset::Ih],
+ expression_mastering.presets[(int)ExpressionComponent::Preset::Ou],
+ expression_mastering.presets[(int)ExpressionComponent::Preset::Ee],
+ expression_mastering.presets[(int)ExpressionComponent::Preset::Oh],
+ };
+ for (int mouth : mouths)
+ {
+ if (mouth >= 0 && mouth < expression_mastering.expressions.size())
+ {
+ ExpressionComponent::Expression& expression = expression_mastering.expressions[mouth];
+ expression.weight *= 1 - wi::math::saturate(overrideMouthBlend);
+ }
+ }
+ const int blinks[] = {
+ expression_mastering.presets[(int)ExpressionComponent::Preset::Blink],
+ expression_mastering.presets[(int)ExpressionComponent::Preset::BlinkLeft],
+ expression_mastering.presets[(int)ExpressionComponent::Preset::BlinkRight],
+ };
+ for (int blink : blinks)
+ {
+ if (blink >= 0 && blink < expression_mastering.expressions.size())
+ {
+ ExpressionComponent::Expression& expression = expression_mastering.expressions[blink];
+ expression.weight *= 1 - wi::math::saturate(overrideBlinkBlend);
+ }
+ }
+ const int looks[] = {
+ expression_mastering.presets[(int)ExpressionComponent::Preset::LookUp],
+ expression_mastering.presets[(int)ExpressionComponent::Preset::LookDown],
+ expression_mastering.presets[(int)ExpressionComponent::Preset::LookLeft],
+ expression_mastering.presets[(int)ExpressionComponent::Preset::LookRight],
+ };
+ for (int look : looks)
+ {
+ if (look >= 0 && look < expression_mastering.expressions.size())
+ {
+ ExpressionComponent::Expression& expression = expression_mastering.expressions[look];
+ expression.weight *= 1 - wi::math::saturate(overrideLookBlend);
+ }
+ }
+
+ // Pass 2: apply expressions:
+ for (ExpressionComponent::Expression& expression : expression_mastering.expressions)
+ {
+ if (!expression.IsDirty())
+ continue;
+
+ expression.SetDirty(false);
+ const float blend = expression.IsBinary() ? (expression.weight > 0 ? 1 : 0) : expression.weight;
+
+ for (const ExpressionComponent::Expression::MorphTargetBinding& morph_target_binding : expression.morph_target_bindings)
+ {
+ MeshComponent* mesh = meshes.GetComponent(morph_target_binding.meshID);
+ if (mesh != nullptr && (int)mesh->morph_targets.size() > morph_target_binding.index)
+ {
+ MeshComponent::MorphTarget& morph_target = mesh->morph_targets[morph_target_binding.index];
+ morph_target.weight = wi::math::Lerp(morph_target.weight, morph_target_binding.weight, blend);
+ mesh->dirty_morph = true;
+ }
+ }
+ }
+ }
+ }
void Scene::RunColliderUpdateSystem(wi::jobsystem::context& ctx)
{
for (size_t i = 0; i < colliders.GetCount(); ++i)
@@ -3925,25 +4128,53 @@ namespace wi::scene
XMFLOAT3 _min = XMFLOAT3(std::numeric_limits::max(), std::numeric_limits::max(), std::numeric_limits::max());
XMFLOAT3 _max = XMFLOAT3(std::numeric_limits::lowest(), std::numeric_limits::lowest(), std::numeric_limits::lowest());
- for (size_t i = 0; i < mesh.vertex_positions.size(); ++i)
- {
- XMFLOAT3 pos = mesh.vertex_positions[i];
- XMFLOAT3 nor = mesh.vertex_normals.empty() ? XMFLOAT3(1, 1, 1) : mesh.vertex_normals[i];
- const uint8_t wind = mesh.vertex_windweights.empty() ? 0xFF : mesh.vertex_windweights[i];
+ mesh.morph_temp_pos = mesh.vertex_positions;
+ mesh.morph_temp_nor = mesh.vertex_normals;
- for (const MeshComponent::MorphTarget& morph : mesh.morph_targets)
+ for (const MeshComponent::MorphTarget& morph : mesh.morph_targets)
+ {
+ if (morph.weight <= 0)
+ continue;
+ if (morph.sparse_indices.empty())
{
- pos.x += morph.weight * morph.vertex_positions[i].x;
- pos.y += morph.weight * morph.vertex_positions[i].y;
- pos.z += morph.weight * morph.vertex_positions[i].z;
-
- if (!morph.vertex_normals.empty())
+ for (size_t i = 0; i < morph.vertex_positions.size(); ++i)
{
- nor.x += morph.weight * morph.vertex_normals[i].x;
- nor.y += morph.weight * morph.vertex_normals[i].y;
- nor.z += morph.weight * morph.vertex_normals[i].z;
+ mesh.morph_temp_pos[i].x += morph.weight * morph.vertex_positions[i].x;
+ mesh.morph_temp_pos[i].y += morph.weight * morph.vertex_positions[i].y;
+ mesh.morph_temp_pos[i].z += morph.weight * morph.vertex_positions[i].z;
+
+ if (!morph.vertex_normals.empty())
+ {
+ mesh.morph_temp_nor[i].x += morph.weight * morph.vertex_normals[i].x;
+ mesh.morph_temp_nor[i].y += morph.weight * morph.vertex_normals[i].y;
+ mesh.morph_temp_nor[i].z += morph.weight * morph.vertex_normals[i].z;
+ }
}
}
+ else
+ {
+ for (size_t i = 0; i < morph.sparse_indices.size(); ++i)
+ {
+ const uint32_t ind = morph.sparse_indices[i];
+ mesh.morph_temp_pos[ind].x += morph.weight * morph.vertex_positions[i].x;
+ mesh.morph_temp_pos[ind].y += morph.weight * morph.vertex_positions[i].y;
+ mesh.morph_temp_pos[ind].z += morph.weight * morph.vertex_positions[i].z;
+
+ if (!morph.vertex_normals.empty())
+ {
+ mesh.morph_temp_nor[ind].x += morph.weight * morph.vertex_normals[i].x;
+ mesh.morph_temp_nor[ind].y += morph.weight * morph.vertex_normals[i].y;
+ mesh.morph_temp_nor[ind].z += morph.weight * morph.vertex_normals[i].z;
+ }
+ }
+ }
+ }
+
+ for (size_t i = 0; i < mesh.morph_temp_pos.size(); ++i)
+ {
+ XMFLOAT3 pos = mesh.morph_temp_pos[i];
+ XMFLOAT3 nor = mesh.morph_temp_nor.empty() ? XMFLOAT3(1, 1, 1) : mesh.morph_temp_nor[i];
+ const uint8_t wind = mesh.vertex_windweights.empty() ? 0xFF : mesh.vertex_windweights[i];
XMStoreFloat3(&nor, XMVector3Normalize(XMLoadFloat3(&nor)));
mesh.vertex_positions_morphed[i].FromFULL(pos, nor, wind);
diff --git a/WickedEngine/wiScene.h b/WickedEngine/wiScene.h
index ad9879e82..dad647c49 100644
--- a/WickedEngine/wiScene.h
+++ b/WickedEngine/wiScene.h
@@ -372,7 +372,8 @@ namespace wi::scene
{
wi::vector vertex_positions;
wi::vector vertex_normals;
- float weight;
+ wi::vector sparse_indices; // optional, these can be used to target vertices indirectly
+ float weight = 0;
};
wi::vector morph_targets;
@@ -608,6 +609,8 @@ namespace wi::scene
// Non serialized attributes:
wi::vector vertex_positions_morphed;
+ wi::vector morph_temp_pos;
+ wi::vector morph_temp_nor;
};
@@ -1425,6 +1428,111 @@ namespace wi::scene
void Serialize(wi::Archive& archive, wi::ecs::EntitySerializer& seri);
};
+ struct ExpressionComponent
+ {
+ enum FLAGS
+ {
+ EMPTY = 0,
+ };
+ uint32_t _flags = EMPTY;
+
+ // Preset expressions can have common behaviours assigned:
+ // https://github.com/vrm-c/vrm-specification/blob/bd205a6c3839993f2729e4e7c3a74af89877cfce/specification/VRMC_vrm-1.0-beta/expressions.md#preset-expressions
+ enum class Preset
+ {
+ // Emotions:
+ Happy, // Changed from joy
+ Angry, // anger
+ Sad, // Changed from sorrow
+ Relaxed, // Comfortable. Changed from fun
+ Surprised, // surprised. Added new in VRM 1.0
+
+ // Lip sync procedural:
+ // Procedural: A value that can be automatically generated by the system.
+ // - Analyze microphone input, generate from text, etc.
+ Aa, // aa
+ Ih, // i
+ Ou, // u
+ Ee, // eh
+ Oh, // oh
+
+ // Blink procedural:
+ // Procedural: A value that can be automatically generated by the system.
+ // - Randomly blink, etc.
+ Blink, // close both eyelids
+ BlinkLeft, // Close the left eyelid
+ BlinkRight, // Close right eyelid
+
+ // Gaze procedural:
+ // Procedural: A value that can be automatically generated by the system.
+ // - The VRM LookAt will generate a value for the gaze point from time to time (see LookAt Expression Type).
+ LookUp, // For models where the line of sight moves with Expression instead of bone. See eye control.
+ LookDown, // For models where the line of sight moves with Expression instead of bone. See eye control.
+ LookLeft, // For models whose line of sight moves with Expression instead of bone. See eye control.
+ LookRight, // For models where the line of sight moves with Expression instead of bone. See eye control.
+
+ // Other:
+ Neutral, // left for backwards compatibility.
+
+ Count,
+ };
+ int presets[size_t(Preset::Count)] = { -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 };
+
+ enum class Override
+ {
+ None,
+ Block,
+ Blend,
+ };
+
+ float blink_frequency = 0; // number of blinks per second
+ float blink_length = 0.1f; // blink's completion time in seconds
+ int blink_count = 2;
+ float look_frequency = 0; // number of lookAt changes per second
+ float look_length = 0.6f; // lookAt's completion time in seconds
+
+ struct Expression
+ {
+ enum FLAGS
+ {
+ EMPTY = 0,
+ DIRTY = 1 << 0,
+ BINARY = 1 << 1,
+ };
+ uint32_t _flags = EMPTY;
+
+ std::string name;
+ float weight = 0;
+
+ Preset preset = Preset::Count;
+ Override override_mouth = Override::None;
+ Override override_blink = Override::None;
+ Override override_look = Override::None;
+
+ struct MorphTargetBinding
+ {
+ wi::ecs::Entity meshID = wi::ecs::INVALID_ENTITY;
+ int index = 0;
+ float weight = 0;
+ };
+ wi::vector morph_target_bindings;
+
+ constexpr bool IsDirty() const { return _flags & DIRTY; }
+ constexpr bool IsBinary() const { return _flags & BINARY; }
+
+ constexpr void SetDirty(bool value = true) { if (value) { _flags |= DIRTY; } else { _flags &= ~DIRTY; } }
+ constexpr void SetBinary(bool value = true) { if (value) { _flags |= BINARY; } else { _flags &= ~BINARY; } }
+ };
+ wi::vector expressions;
+
+ // Non-serialized attributes:
+ float blink_timer = 0;
+ float look_timer = 0;
+ float look_weights[4] = {};
+
+ void Serialize(wi::Archive& archive, wi::ecs::EntitySerializer& seri);
+ };
+
struct Scene
{
wi::ecs::ComponentLibrary componentLibrary;
@@ -1434,7 +1542,7 @@ namespace wi::scene
wi::ecs::ComponentManager& transforms = componentLibrary.Register("wi::scene::Scene::transforms");
wi::ecs::ComponentManager& hierarchy = componentLibrary.Register("wi::scene::Scene::hierarchy");
wi::ecs::ComponentManager& materials = componentLibrary.Register("wi::scene::Scene::materials", 1); // version = 1
- wi::ecs::ComponentManager& meshes = componentLibrary.Register("wi::scene::Scene::meshes");
+ wi::ecs::ComponentManager& meshes = componentLibrary.Register("wi::scene::Scene::meshes", 1); // version = 1
wi::ecs::ComponentManager& impostors = componentLibrary.Register("wi::scene::Scene::impostors");
wi::ecs::ComponentManager& objects = componentLibrary.Register("wi::scene::Scene::objects");
wi::ecs::ComponentManager& aabb_objects = componentLibrary.Register("wi::scene::Scene::aabb_objects");
@@ -1459,6 +1567,7 @@ namespace wi::scene
wi::ecs::ComponentManager& springs = componentLibrary.Register("wi::scene::Scene::springs", 1); // version = 1
wi::ecs::ComponentManager& colliders = componentLibrary.Register("wi::scene::Scene::colliders", 1); // version = 1
wi::ecs::ComponentManager& scripts = componentLibrary.Register("wi::scene::Scene::scripts");
+ wi::ecs::ComponentManager& expressions = componentLibrary.Register("wi::scene::Scene::expressions");
// Non-serialized attributes:
float dt = 0;
@@ -1700,6 +1809,7 @@ namespace wi::scene
void RunAnimationUpdateSystem(wi::jobsystem::context& ctx);
void RunTransformUpdateSystem(wi::jobsystem::context& ctx);
void RunHierarchyUpdateSystem(wi::jobsystem::context& ctx);
+ void RunExpressionUpdateSystem(wi::jobsystem::context& ctx);
void RunColliderUpdateSystem(wi::jobsystem::context& ctx);
void RunSpringUpdateSystem(wi::jobsystem::context& ctx);
void RunInverseKinematicsUpdateSystem(wi::jobsystem::context& ctx);
diff --git a/WickedEngine/wiScene_Serializers.cpp b/WickedEngine/wiScene_Serializers.cpp
index 2e0bb96c9..297eaa08b 100644
--- a/WickedEngine/wiScene_Serializers.cpp
+++ b/WickedEngine/wiScene_Serializers.cpp
@@ -415,6 +415,10 @@ namespace wi::scene
archive >> morph_targets[i].vertex_positions;
archive >> morph_targets[i].vertex_normals;
archive >> morph_targets[i].weight;
+ if (seri.GetVersion() >= 1)
+ {
+ archive >> morph_targets[i].sparse_indices;
+ }
}
}
@@ -484,6 +488,10 @@ namespace wi::scene
archive << morph_targets[i].vertex_positions;
archive << morph_targets[i].vertex_normals;
archive << morph_targets[i].weight;
+ if (seri.GetVersion() >= 1)
+ {
+ archive << morph_targets[i].sparse_indices;
+ }
}
}
@@ -1461,6 +1469,89 @@ namespace wi::scene
archive << relative_filename;
}
}
+ void ExpressionComponent::Serialize(wi::Archive& archive, EntitySerializer& seri)
+ {
+ if (archive.IsReadMode())
+ {
+ archive >> _flags;
+ for (int& index : presets)
+ {
+ archive >> index;
+ }
+ archive >> blink_frequency;
+ archive >> blink_length;
+ archive >> blink_count;
+ archive >> look_frequency;
+ archive >> look_length;
+
+ size_t expression_count = 0;
+ archive >> expression_count;
+ expressions.resize(expression_count);
+ for (size_t expression_index = 0; expression_index < expression_count; ++expression_index)
+ {
+ Expression& expression = expressions[expression_index];
+ archive >> expression.name;
+ archive >> expression.weight;
+
+ uint32_t value = 0;
+ archive >> value;
+ expression.preset = (Preset)value;
+
+ archive >> value;
+ expression.override_mouth = (Override)value;
+ archive >> value;
+ expression.override_blink = (Override)value;
+ archive >> value;
+ expression.override_look = (Override)value;
+
+ size_t count = 0;
+ archive >> count;
+ expression.morph_target_bindings.resize(count);
+ for (size_t i = 0; i < count; ++i)
+ {
+ SerializeEntity(archive, expression.morph_target_bindings[i].meshID, seri);
+ archive >> expression.morph_target_bindings[i].index;
+ archive >> expression.morph_target_bindings[i].weight;
+ }
+
+ expression.SetDirty();
+ }
+ }
+ else
+ {
+ archive << _flags;
+ for (int index : presets)
+ {
+ archive << index;
+ }
+ archive << blink_frequency;
+ archive << blink_length;
+ archive << blink_count;
+ archive << look_frequency;
+ archive << look_length;
+
+ archive << expressions.size();
+ for (size_t expression_index = 0; expression_index < expressions.size(); ++expression_index)
+ {
+ Expression& expression = expressions[expression_index];
+ archive << expression.name;
+ archive << expression.weight;
+
+ archive << (uint32_t)expression.preset;
+ archive << (uint32_t)expression.override_mouth;
+ archive << (uint32_t)expression.override_blink;
+ archive << (uint32_t)expression.override_look;
+
+ archive << expression.morph_target_bindings.size();
+ for (size_t i = 0; i < expression.morph_target_bindings.size(); ++i)
+ {
+ SerializeEntity(archive, expression.morph_target_bindings[i].meshID, seri);
+ archive << expression.morph_target_bindings[i].index;
+ archive << expression.morph_target_bindings[i].weight;
+ }
+ }
+ }
+ }
void Scene::Serialize(wi::Archive& archive)
{
diff --git a/WickedEngine/wiVersion.cpp b/WickedEngine/wiVersion.cpp
index 9bc942566..57916b57f 100644
--- a/WickedEngine/wiVersion.cpp
+++ b/WickedEngine/wiVersion.cpp
@@ -9,7 +9,7 @@ namespace wi::version
// minor features, major updates, breaking compatibility changes
const int minor = 71;
// minor bug fixes, alterations, refactors, updates
- const int revision = 28;
+ const int revision = 29;
const std::string version_string = std::to_string(major) + "." + std::to_string(minor) + "." + std::to_string(revision);
diff --git a/credits.txt b/credits.txt
index 8327bfe69..a0cf01c3f 100644
--- a/credits.txt
+++ b/credits.txt
@@ -33,6 +33,7 @@ Contributions:
- Linux audio implementation
- Linux controller implementation
- Linux network implementation
+ - Lua property bindings support, improvements
- Fixes
- Maeve Garside https://github.com/MolassesLover
- Linux package files
diff --git a/features.txt b/features.txt
index 2c4471490..10af7e152 100644
--- a/features.txt
+++ b/features.txt
@@ -8,7 +8,7 @@ Font rendering (True Type)
Networking (UDP)
3D mesh rendering
Skeletal animation
-Morph target animation
+Morph target animation (with sparse accessor)
Physically based rendering
Animated texturing
Normal mapping
@@ -74,7 +74,7 @@ Entity-Component System (Data oriented design)
Lightmap baking (with GPU path tracing)
Job system
Inverse Kinematics
-Springs
+Springs, Colliders
Variable Rate Shading
Real time ray tracing: ambient occlusion, shadows, reflections (DXR and Vulkan raytracing)
Screen Space Contact Shadows
@@ -83,8 +83,9 @@ Surfel GI
HDR display output
Dynamic Diffuse Global Illumination (DDGI)
Procedural terrain generator
+Expressions
-GLTF 2.0 extensions supported:
+GLTF 2.0 - KHR extensions supported:
KHR_materials_unlit
KHR_materials_transmission
KHR_materials_pbrSpecularGlossiness
@@ -94,3 +95,11 @@ KHR_materials_specular
KHR_materials_ior
KHR_texture_basisu
KHR_lights_punctual
+
+GLTF 2.0 - VRM 0.0 extensions supported:
+VRM_secondaryAnimation
+VRM_blendShapeMaster
+
+GLTF 2.0 - VRM 1.0 extensions supported:
+VRMC_springBone
+VRMC_vrm_expressions