Sparse morph target and VRM expressions (#536)
This commit is contained in:
@@ -34,6 +34,7 @@ set (SOURCE_FILES
|
||||
SoftBodyWindow.cpp
|
||||
ColliderWindow.cpp
|
||||
HierarchyWindow.cpp
|
||||
ExpressionWindow.cpp
|
||||
OptionsWindow.cpp
|
||||
ComponentsWindow.cpp
|
||||
TerrainGenerator.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);
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
};
|
||||
|
||||
@@ -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)
|
||||
{
|
||||
|
||||
@@ -23,6 +23,7 @@
|
||||
<ClCompile Include="$(MSBuildThisFileDirectory)Editor.cpp" />
|
||||
<ClCompile Include="$(MSBuildThisFileDirectory)EmitterWindow.cpp" />
|
||||
<ClCompile Include="$(MSBuildThisFileDirectory)EnvProbeWindow.cpp" />
|
||||
<ClCompile Include="$(MSBuildThisFileDirectory)ExpressionWindow.cpp" />
|
||||
<ClCompile Include="$(MSBuildThisFileDirectory)ForceFieldWindow.cpp" />
|
||||
<ClCompile Include="$(MSBuildThisFileDirectory)HairParticleWindow.cpp" />
|
||||
<ClCompile Include="$(MSBuildThisFileDirectory)HierarchyWindow.cpp" />
|
||||
@@ -139,6 +140,7 @@
|
||||
<ClInclude Include="$(MSBuildThisFileDirectory)Editor.h" />
|
||||
<ClInclude Include="$(MSBuildThisFileDirectory)EmitterWindow.h" />
|
||||
<ClInclude Include="$(MSBuildThisFileDirectory)EnvProbeWindow.h" />
|
||||
<ClInclude Include="$(MSBuildThisFileDirectory)ExpressionWindow.h" />
|
||||
<ClInclude Include="$(MSBuildThisFileDirectory)FontAwesomeV6.h" />
|
||||
<ClInclude Include="$(MSBuildThisFileDirectory)ForceFieldWindow.h" />
|
||||
<ClInclude Include="$(MSBuildThisFileDirectory)HairParticleWindow.h" />
|
||||
|
||||
@@ -82,6 +82,7 @@
|
||||
<ClCompile Include="$(MSBuildThisFileDirectory)ColliderWindow.cpp" />
|
||||
<ClCompile Include="$(MSBuildThisFileDirectory)HierarchyWindow.cpp" />
|
||||
<ClCompile Include="$(MSBuildThisFileDirectory)CameraComponentWindow.cpp" />
|
||||
<ClCompile Include="$(MSBuildThisFileDirectory)ExpressionWindow.cpp" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ClInclude Include="$(MSBuildThisFileDirectory)AnimationWindow.h" />
|
||||
@@ -128,6 +129,7 @@
|
||||
<ClInclude Include="$(MSBuildThisFileDirectory)ColliderWindow.h" />
|
||||
<ClInclude Include="$(MSBuildThisFileDirectory)HierarchyWindow.h" />
|
||||
<ClInclude Include="$(MSBuildThisFileDirectory)CameraComponentWindow.h" />
|
||||
<ClInclude Include="$(MSBuildThisFileDirectory)ExpressionWindow.h" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<None Include="$(MSBuildThisFileDirectory)startup.lua" />
|
||||
|
||||
@@ -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);
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
};
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
+426
-85
File diff suppressed because it is too large
Load Diff
@@ -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();
|
||||
}
|
||||
|
||||
@@ -55,6 +55,7 @@ public:
|
||||
Armature = 1 << 15,
|
||||
Collider = 1 << 16,
|
||||
Script = 1 << 17,
|
||||
Expression = 1 << 18,
|
||||
|
||||
All = ~0ull,
|
||||
} filter = Filter::All;
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
+245
-14
@@ -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<arraysize(looks); ++idx)
|
||||
{
|
||||
int look = looks[idx];
|
||||
const float weight = expression_mastering.look_weights[idx];
|
||||
if (look >= 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<float>::max(), std::numeric_limits<float>::max(), std::numeric_limits<float>::max());
|
||||
XMFLOAT3 _max = XMFLOAT3(std::numeric_limits<float>::lowest(), std::numeric_limits<float>::lowest(), std::numeric_limits<float>::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);
|
||||
|
||||
+112
-2
@@ -372,7 +372,8 @@ namespace wi::scene
|
||||
{
|
||||
wi::vector<XMFLOAT3> vertex_positions;
|
||||
wi::vector<XMFLOAT3> vertex_normals;
|
||||
float weight;
|
||||
wi::vector<uint32_t> sparse_indices; // optional, these can be used to target vertices indirectly
|
||||
float weight = 0;
|
||||
};
|
||||
wi::vector<MorphTarget> morph_targets;
|
||||
|
||||
@@ -608,6 +609,8 @@ namespace wi::scene
|
||||
|
||||
// Non serialized attributes:
|
||||
wi::vector<Vertex_POS> vertex_positions_morphed;
|
||||
wi::vector<XMFLOAT3> morph_temp_pos;
|
||||
wi::vector<XMFLOAT3> 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<MorphTargetBinding> 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<Expression> 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<TransformComponent>& transforms = componentLibrary.Register<TransformComponent>("wi::scene::Scene::transforms");
|
||||
wi::ecs::ComponentManager<HierarchyComponent>& hierarchy = componentLibrary.Register<HierarchyComponent>("wi::scene::Scene::hierarchy");
|
||||
wi::ecs::ComponentManager<MaterialComponent>& materials = componentLibrary.Register<MaterialComponent>("wi::scene::Scene::materials", 1); // version = 1
|
||||
wi::ecs::ComponentManager<MeshComponent>& meshes = componentLibrary.Register<MeshComponent>("wi::scene::Scene::meshes");
|
||||
wi::ecs::ComponentManager<MeshComponent>& meshes = componentLibrary.Register<MeshComponent>("wi::scene::Scene::meshes", 1); // version = 1
|
||||
wi::ecs::ComponentManager<ImpostorComponent>& impostors = componentLibrary.Register<ImpostorComponent>("wi::scene::Scene::impostors");
|
||||
wi::ecs::ComponentManager<ObjectComponent>& objects = componentLibrary.Register<ObjectComponent>("wi::scene::Scene::objects");
|
||||
wi::ecs::ComponentManager<wi::primitive::AABB>& aabb_objects = componentLibrary.Register<wi::primitive::AABB>("wi::scene::Scene::aabb_objects");
|
||||
@@ -1459,6 +1567,7 @@ namespace wi::scene
|
||||
wi::ecs::ComponentManager<SpringComponent>& springs = componentLibrary.Register<SpringComponent>("wi::scene::Scene::springs", 1); // version = 1
|
||||
wi::ecs::ComponentManager<ColliderComponent>& colliders = componentLibrary.Register<ColliderComponent>("wi::scene::Scene::colliders", 1); // version = 1
|
||||
wi::ecs::ComponentManager<ScriptComponent>& scripts = componentLibrary.Register<ScriptComponent>("wi::scene::Scene::scripts");
|
||||
wi::ecs::ComponentManager<ExpressionComponent>& expressions = componentLibrary.Register<ExpressionComponent>("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);
|
||||
|
||||
@@ -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)
|
||||
{
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
+12
-3
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user