Sparse morph target and VRM expressions (#536)

This commit is contained in:
Turánszki János
2022-08-28 19:22:23 +02:00
committed by GitHub
parent b063c22cc3
commit ac4fc8540d
22 changed files with 1306 additions and 110 deletions
+1
View File
@@ -34,6 +34,7 @@ set (SOURCE_FILES
SoftBodyWindow.cpp
ColliderWindow.cpp
HierarchyWindow.cpp
ExpressionWindow.cpp
OptionsWindow.cpp
ComponentsWindow.cpp
TerrainGenerator.cpp
+2 -2
View File
@@ -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);
+18 -2
View File
@@ -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);
}
}
+2
View File
@@ -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;
};
+3
View File
@@ -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)
{
+2
View File
@@ -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" />
+2
View File
@@ -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" />
+331
View File
@@ -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);
}
+29
View File
@@ -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;
};
+1
View File
@@ -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
File diff suppressed because it is too large Load Diff
+13
View File
@@ -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();
}
+1
View File
@@ -55,6 +55,7 @@ public:
Armature = 1 << 15,
Collider = 1 << 16,
Script = 1 << 17,
Expression = 1 << 18,
All = ~0ull,
} filter = Filter::All;
+10
View File
@@ -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();
+2
View File
@@ -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;
+1 -1
View File
@@ -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
View File
@@ -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
View File
@@ -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);
+91
View File
@@ -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)
{
+1 -1
View File
@@ -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);
+1
View File
@@ -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
View File
@@ -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