Chriche/optional (#36)
Use a utility Optional type for class member variables that are not required and have no default value specified. The following classes have been modified: - BufferView: byteStride & target members - Perspective: zfar & aspectRatio members - Sampler: magFilter & minFilter members - TextureTransform: texCoord member
This commit is contained in:
Родитель
fc96348a60
Коммит
bf524fe3e9
|
@ -60,6 +60,7 @@
|
|||
<ClInclude Include="$(MSBuildThisFileDirectory)..\GLTFSDK\Inc\GLTFSDK\Math.h" />
|
||||
<ClInclude Include="$(MSBuildThisFileDirectory)..\GLTFSDK\Inc\GLTFSDK\MeshPrimitiveUtils.h" />
|
||||
<ClInclude Include="$(MSBuildThisFileDirectory)..\GLTFSDK\Inc\GLTFSDK\MicrosoftGeneratorVersion.h" />
|
||||
<ClInclude Include="$(MSBuildThisFileDirectory)..\GLTFSDK\Inc\GLTFSDK\Optional.h" />
|
||||
<ClInclude Include="$(MSBuildThisFileDirectory)..\GLTFSDK\Inc\GLTFSDK\PBRUtils.h" />
|
||||
<ClInclude Include="$(MSBuildThisFileDirectory)..\GLTFSDK\Inc\GLTFSDK\RapidJsonUtils.h" />
|
||||
<ClInclude Include="$(MSBuildThisFileDirectory)..\GLTFSDK\Inc\GLTFSDK\ResourceReaderUtils.h" />
|
||||
|
|
|
@ -197,6 +197,9 @@
|
|||
<ClInclude Include="$(MSBuildThisFileDirectory)..\GLTFSDK\Inc\GLTFSDK\SchemaValidation.h">
|
||||
<Filter>Header Files\GLTFSDK</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="$(MSBuildThisFileDirectory)..\GLTFSDK\Inc\GLTFSDK\Optional.h">
|
||||
<Filter>Header Files\GLTFSDK</Filter>
|
||||
</ClInclude>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<None Include="$(MSBuildThisFileDirectory)..\GLTFSDK\schema\accessor.schema.json">
|
||||
|
|
|
@ -105,6 +105,7 @@
|
|||
<ClCompile Include="Source\IndexedContainerTests.cpp" />
|
||||
<ClCompile Include="Source\MeshPrimitiveUtilsTests.cpp" />
|
||||
<ClCompile Include="Source\MicrosoftGeneratorVersionTests.cpp" />
|
||||
<ClCompile Include="Source\OptionalTests.cpp" />
|
||||
<ClCompile Include="Source\PBRUtilsTests.cpp" />
|
||||
<ClCompile Include="Source\ResourceReaderUtilsTests.cpp" />
|
||||
<ClCompile Include="Source\SerializeTests.cpp" />
|
||||
|
|
|
@ -92,6 +92,9 @@
|
|||
<ClCompile Include="Source\GLBResourceWriterTests.cpp">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="Source\OptionalTests.cpp">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<None Include="Resources\gltf\ReciprocatingSaw.gltf">
|
||||
|
@ -255,5 +258,8 @@
|
|||
<None Include="Resources\glTF-Asset-Generator\Mesh_PrimitiveMode\Mesh_PrimitiveMode_15.bin">
|
||||
<Filter>Resource Files</Filter>
|
||||
</None>
|
||||
<None Include="Resources\gltf\TextureTransformTest.gltf">
|
||||
<Filter>Resource Files</Filter>
|
||||
</None>
|
||||
</ItemGroup>
|
||||
</Project>
|
|
@ -429,7 +429,7 @@ namespace Microsoft
|
|||
|
||||
auto checkTextureInfo = [](
|
||||
const Material& material,
|
||||
const Vector2& offset, float rotation, const Vector2& scale, std::unique_ptr<size_t> texCoord)
|
||||
const Vector2& offset, float rotation, const Vector2& scale, Optional<size_t> texCoord = {})
|
||||
{
|
||||
auto& textureInfo = material.metallicRoughness.baseColorTexture;
|
||||
|
||||
|
@ -441,19 +441,19 @@ namespace Microsoft
|
|||
expectedTextureTransform.offset = offset;
|
||||
expectedTextureTransform.scale = scale;
|
||||
expectedTextureTransform.rotation = rotation;
|
||||
expectedTextureTransform.texCoord = std::move(texCoord);
|
||||
expectedTextureTransform.texCoord = texCoord;
|
||||
|
||||
Assert::IsTrue(textureTransform == expectedTextureTransform);
|
||||
};
|
||||
|
||||
Assert::IsTrue(doc.materials.Size() == 9);
|
||||
|
||||
checkTextureInfo(doc.materials[0], Vector2(0.5f, 0.0f), 0.0f, Vector2(1.0f, 1.0f), {}); // Note: texCoord not specified
|
||||
checkTextureInfo(doc.materials[1], Vector2(0.0f, 0.5f), 0.0f, Vector2(1.0f, 1.0f), {});
|
||||
checkTextureInfo(doc.materials[2], Vector2(0.5f, 0.5f), 0.0f, Vector2(1.0f, 1.0f), {});
|
||||
checkTextureInfo(doc.materials[3], Vector2(0.0f, 0.0f), 0.39269908169872415480783042290994f, Vector2(1.0f, 1.0f), {});
|
||||
checkTextureInfo(doc.materials[4], Vector2(0.0f, 0.0f), 0.0f, Vector2(1.5f, 1.5f), {});
|
||||
checkTextureInfo(doc.materials[5], Vector2(-0.2f, -0.1f), 0.3f, Vector2(1.5f, 1.5f), {});
|
||||
checkTextureInfo(doc.materials[0], Vector2(0.5f, 0.0f), 0.0f, Vector2(1.0f, 1.0f)); // Note: texCoord not specified
|
||||
checkTextureInfo(doc.materials[1], Vector2(0.0f, 0.5f), 0.0f, Vector2(1.0f, 1.0f));
|
||||
checkTextureInfo(doc.materials[2], Vector2(0.5f, 0.5f), 0.0f, Vector2(1.0f, 1.0f));
|
||||
checkTextureInfo(doc.materials[3], Vector2(0.0f, 0.0f), 0.39269908169872415480783042290994f, Vector2(1.0f, 1.0f));
|
||||
checkTextureInfo(doc.materials[4], Vector2(0.0f, 0.0f), 0.0f, Vector2(1.5f, 1.5f));
|
||||
checkTextureInfo(doc.materials[5], Vector2(-0.2f, -0.1f), 0.3f, Vector2(1.5f, 1.5f));
|
||||
}
|
||||
|
||||
GLTFSDK_TEST_METHOD(ExtensionsTests, Extensions_Test_HasTextureTransformExtension_TexCoord)
|
||||
|
@ -464,7 +464,7 @@ namespace Microsoft
|
|||
|
||||
auto checkTextureInfo = [](
|
||||
const Material& material,
|
||||
const Vector2& offset, float rotation, const Vector2& scale, std::unique_ptr<size_t> texCoord)
|
||||
const Vector2& offset, float rotation, const Vector2& scale, Optional<size_t> texCoord = {})
|
||||
{
|
||||
auto& textureInfo = material.metallicRoughness.baseColorTexture;
|
||||
|
||||
|
@ -476,15 +476,15 @@ namespace Microsoft
|
|||
expectedTextureTransform.offset = offset;
|
||||
expectedTextureTransform.scale = scale;
|
||||
expectedTextureTransform.rotation = rotation;
|
||||
expectedTextureTransform.texCoord = std::move(texCoord);
|
||||
expectedTextureTransform.texCoord = texCoord;
|
||||
|
||||
Assert::IsTrue(textureTransform == expectedTextureTransform);
|
||||
};
|
||||
|
||||
Assert::IsTrue(doc.materials.Size() == 2);
|
||||
|
||||
checkTextureInfo(doc.materials[0], Vector2(-0.2f, -0.1f), 0.3f, Vector2(1.5f, 1.5f), std::make_unique<size_t>(1234));
|
||||
checkTextureInfo(doc.materials[1], Vector2(-0.2f, -0.1f), 0.3f, Vector2(1.5f, 1.5f), {});
|
||||
checkTextureInfo(doc.materials[0], Vector2(-0.2f, -0.1f), 0.3f, Vector2(1.5f, 1.5f), 1234);
|
||||
checkTextureInfo(doc.materials[1], Vector2(-0.2f, -0.1f), 0.3f, Vector2(1.5f, 1.5f));
|
||||
|
||||
const auto extensionSerializer = KHR::GetKHRExtensionSerializer();
|
||||
auto tt = Serialize(doc, extensionSerializer);
|
||||
|
|
|
@ -254,7 +254,6 @@ namespace Microsoft
|
|||
bufferView.bufferId = "0";
|
||||
bufferView.byteOffset = 0;
|
||||
bufferView.byteLength = data.size() * sizeof(uint32_t);
|
||||
bufferView.target = BufferViewTarget::UNKNOWN_BUFFER;
|
||||
|
||||
writer.Write(bufferView, data.data());
|
||||
|
||||
|
@ -280,7 +279,6 @@ namespace Microsoft
|
|||
bufferView.bufferId = "0";
|
||||
bufferView.byteOffset = 0;
|
||||
bufferView.byteLength = data.size() * sizeof(uint32_t);
|
||||
bufferView.target = BufferViewTarget::UNKNOWN_BUFFER;
|
||||
|
||||
writer.Write(bufferView, data.data());
|
||||
|
||||
|
@ -306,7 +304,6 @@ namespace Microsoft
|
|||
bufferView.bufferId = "0";
|
||||
bufferView.byteOffset = 0;
|
||||
bufferView.byteLength = data.size() * sizeof(uint32_t);
|
||||
bufferView.target = BufferViewTarget::UNKNOWN_BUFFER;
|
||||
|
||||
writer.Write(bufferView, data.data());
|
||||
|
||||
|
@ -332,7 +329,6 @@ namespace Microsoft
|
|||
bufferView.bufferId = "0";
|
||||
bufferView.byteOffset = 0;
|
||||
bufferView.byteLength = data1.size() * sizeof(uint32_t);
|
||||
bufferView.target = BufferViewTarget::UNKNOWN_BUFFER;
|
||||
|
||||
writer.Write(bufferView, data1.data());
|
||||
|
||||
|
@ -361,7 +357,6 @@ namespace Microsoft
|
|||
bufferView.bufferId = "0";
|
||||
bufferView.byteOffset = 0;
|
||||
bufferView.byteLength = data.size() * sizeof(float);
|
||||
bufferView.target = BufferViewTarget::UNKNOWN_BUFFER;
|
||||
|
||||
Accessor accessor;
|
||||
accessor.id = "0";
|
||||
|
@ -399,7 +394,6 @@ namespace Microsoft
|
|||
bufferView.bufferId = "0";
|
||||
bufferView.byteOffset = 0;
|
||||
bufferView.byteLength = data.size() * sizeof(float);
|
||||
bufferView.target = BufferViewTarget::UNKNOWN_BUFFER;
|
||||
|
||||
Accessor accessor;
|
||||
accessor.id = "0";
|
||||
|
@ -435,7 +429,6 @@ namespace Microsoft
|
|||
bufferView.bufferId = "0";
|
||||
bufferView.byteOffset = 0;
|
||||
bufferView.byteLength = data1.size() * sizeof(uint8_t);
|
||||
bufferView.target = BufferViewTarget::UNKNOWN_BUFFER;
|
||||
|
||||
Accessor accessor;
|
||||
accessor.id = "0";
|
||||
|
@ -488,7 +481,6 @@ namespace Microsoft
|
|||
bufferView.bufferId = "0";
|
||||
bufferView.byteOffset = 0;
|
||||
bufferView.byteLength = data1.size() * sizeof(uint8_t);
|
||||
bufferView.target = BufferViewTarget::UNKNOWN_BUFFER;
|
||||
|
||||
Accessor accessor;
|
||||
accessor.id = "0";
|
||||
|
@ -535,7 +527,6 @@ namespace Microsoft
|
|||
bufferView.bufferId = "0";
|
||||
bufferView.byteOffset = 0;
|
||||
bufferView.byteLength = data.size() * sizeof(uint32_t) + 1U;// Add an additional byte as the accessor's byteOffset is 1;
|
||||
bufferView.target = BufferViewTarget::UNKNOWN_BUFFER;
|
||||
|
||||
Accessor accessor;
|
||||
accessor.id = "0";
|
||||
|
@ -563,7 +554,6 @@ namespace Microsoft
|
|||
bufferView.bufferId = "0";
|
||||
bufferView.byteOffset = 1U;
|
||||
bufferView.byteLength = data.size() * sizeof(uint32_t) + 5U;// Add an additional 5 bytes as the bufferView and accessor's byteOffsets are 1 and 4 respectively;
|
||||
bufferView.target = BufferViewTarget::UNKNOWN_BUFFER;
|
||||
|
||||
Accessor accessor;
|
||||
accessor.id = "0";
|
||||
|
|
|
@ -18,26 +18,26 @@ namespace
|
|||
void TestBadGLTFSerializeToJson(const Document& doc)
|
||||
{
|
||||
Assert::ExpectException<GLTFException>([&doc]
|
||||
{
|
||||
Serialize(doc);
|
||||
}, L"Expected exception was not thrown");
|
||||
{
|
||||
Serialize(doc);
|
||||
}, L"Expected exception was not thrown");
|
||||
}
|
||||
|
||||
void TestBadGLTFDeserializeToDocument(const char* data)
|
||||
{
|
||||
Assert::ExpectException<GLTFException>([&data]
|
||||
{
|
||||
Deserialize(data);
|
||||
}, L"Expected exception was not thrown");
|
||||
{
|
||||
Deserialize(data);
|
||||
}, L"Expected exception was not thrown");
|
||||
}
|
||||
|
||||
void TestDocumentValidationFail(const char* data)
|
||||
{
|
||||
Assert::ExpectException<ValidationException>([&data]
|
||||
{
|
||||
auto doc = Deserialize(data);
|
||||
Validation::Validate(doc);
|
||||
}, L"Expected exception was not thrown");
|
||||
{
|
||||
auto doc = Deserialize(data);
|
||||
Validation::Validate(doc);
|
||||
}, L"Expected exception was not thrown");
|
||||
}
|
||||
|
||||
const char* c_invalidPrimitiveAccessorComponentType = R"({
|
||||
|
@ -294,12 +294,77 @@ namespace
|
|||
],
|
||||
"scene": 0
|
||||
})";
|
||||
|
||||
const char* c_validSamplerDocument = R"({
|
||||
"asset": {
|
||||
"version": "2.0"
|
||||
},
|
||||
"samplers": [
|
||||
{
|
||||
"minFilter": 9728,
|
||||
"magFilter": 9729
|
||||
},
|
||||
{
|
||||
"wrapS": 33648,
|
||||
"wrapT": 33071
|
||||
}
|
||||
]
|
||||
})";
|
||||
}
|
||||
|
||||
namespace Microsoft
|
||||
{
|
||||
namespace glTF
|
||||
{
|
||||
std::wstring ToString(WrapMode wrapMode)
|
||||
{
|
||||
switch (wrapMode)
|
||||
{
|
||||
case Wrap_REPEAT:
|
||||
return L"REPEAT";
|
||||
case Wrap_CLAMP_TO_EDGE:
|
||||
return L"CLAMP_TO_EDGE";
|
||||
case Wrap_MIRRORED_REPEAT:
|
||||
return L"MIRRORED_REPEAT";
|
||||
}
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
std::wstring ToString(MinFilterMode minFilterMode)
|
||||
{
|
||||
switch (minFilterMode)
|
||||
{
|
||||
case MinFilter_NEAREST:
|
||||
return L"NEAREST";
|
||||
case MinFilter_NEAREST_MIPMAP_LINEAR:
|
||||
return L"NEAREST_MIPMAP_LINEAR";
|
||||
case MinFilter_NEAREST_MIPMAP_NEAREST:
|
||||
return L"NEAREST_MIPMAP_NEAREST";
|
||||
case MinFilter_LINEAR:
|
||||
return L"LINEAR";
|
||||
case MinFilter_LINEAR_MIPMAP_LINEAR:
|
||||
return L"LINEAR_MIPMAP_LINEAR";
|
||||
case MinFilter_LINEAR_MIPMAP_NEAREST:
|
||||
return L"LINEAR_MIPMAP_NEAREST";
|
||||
}
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
std::wstring ToString(MagFilterMode magFilterMode)
|
||||
{
|
||||
switch (magFilterMode)
|
||||
{
|
||||
case MagFilter_NEAREST:
|
||||
return L"NEAREST";
|
||||
case MagFilter_LINEAR:
|
||||
return L"LINEAR";
|
||||
}
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
namespace Test
|
||||
{
|
||||
GLTFSDK_TEST_CLASS(SerializerGLTFTests)
|
||||
|
@ -423,6 +488,25 @@ namespace Microsoft
|
|||
|
||||
TestBadGLTFSerializeToJson(doc);
|
||||
}
|
||||
|
||||
GLTFSDK_TEST_METHOD(SerializerGLTFTests, SerializerGLTFTests_DeserializeSampler)
|
||||
{
|
||||
auto doc = Deserialize(c_validSamplerDocument);
|
||||
|
||||
Assert::AreEqual(doc.samplers.Size(), size_t(2U), L"Unexpected number of samplers after deserializing manifest");
|
||||
|
||||
Assert::AreEqual(doc.samplers[0].minFilter.Get(), MinFilter_NEAREST, L"Sampler minification filter was not deserialized correctly");
|
||||
Assert::AreEqual(doc.samplers[0].magFilter.Get(), MagFilter_LINEAR, L"Sampler magnification filter was not deserialized correctly");
|
||||
|
||||
Assert::AreEqual(doc.samplers[0].wrapS, Wrap_REPEAT, L"Sampler default wrapS property was not deserialized correctly");
|
||||
Assert::AreEqual(doc.samplers[0].wrapT, Wrap_REPEAT, L"Sampler default wrapT property was not deserialized correctly");
|
||||
|
||||
Assert::IsFalse(doc.samplers[1].minFilter.HasValue(), L"Sampler default minification filter was not unspecified");
|
||||
Assert::IsFalse(doc.samplers[1].magFilter.HasValue(), L"Sampler default magnification filter was not unspecified");
|
||||
|
||||
Assert::AreEqual(doc.samplers[1].wrapS, Wrap_MIRRORED_REPEAT, L"Sampler wrapS property was not deserialized correctly");
|
||||
Assert::AreEqual(doc.samplers[1].wrapT, Wrap_CLAMP_TO_EDGE, L"Sampler wrapT property was not deserialized correctly");
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,480 @@
|
|||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
// Licensed under the MIT License.
|
||||
|
||||
#include "stdafx.h"
|
||||
|
||||
#include <GLTFSDK/Optional.h>
|
||||
|
||||
using namespace glTF::UnitTest;
|
||||
|
||||
namespace Microsoft
|
||||
{
|
||||
namespace glTF
|
||||
{
|
||||
namespace Test
|
||||
{
|
||||
GLTFSDK_TEST_CLASS(OptionalTests)
|
||||
{
|
||||
GLTFSDK_TEST_METHOD(OptionalTests, ConstructorDefault)
|
||||
{
|
||||
Optional<int> optional;
|
||||
|
||||
Assert::IsFalse(static_cast<bool>(optional));
|
||||
Assert::IsFalse(optional.HasValue());
|
||||
|
||||
Assert::ExpectException<GLTFException>([&optional]{ optional.Get(); });
|
||||
}
|
||||
|
||||
GLTFSDK_TEST_METHOD(OptionalTests, ConstructorValueCopy)
|
||||
{
|
||||
Optional<double> optional(1.0);
|
||||
|
||||
Assert::IsTrue(static_cast<bool>(optional));
|
||||
Assert::IsTrue(optional.HasValue());
|
||||
|
||||
Assert::AreEqual(1.0, optional.Get());
|
||||
}
|
||||
|
||||
GLTFSDK_TEST_METHOD(OptionalTests, ConstructorValueMove)
|
||||
{
|
||||
Optional<std::unique_ptr<int>> optional(std::make_unique<int>(1));
|
||||
|
||||
Assert::IsTrue(static_cast<bool>(optional));
|
||||
Assert::IsTrue(optional.HasValue());
|
||||
|
||||
Assert::AreEqual(1, *optional.Get().get());
|
||||
}
|
||||
|
||||
GLTFSDK_TEST_METHOD(OptionalTests, ConstructorOptionalCopy)
|
||||
{
|
||||
Optional<unsigned int> opt1;
|
||||
Optional<unsigned int> opt2(opt1);
|
||||
Optional<unsigned int> opt3(3U);
|
||||
Optional<unsigned int> opt4(opt3);
|
||||
|
||||
Assert::IsFalse(static_cast<bool>(opt1));
|
||||
Assert::IsFalse(opt1.HasValue());
|
||||
|
||||
Assert::IsFalse(static_cast<bool>(opt2));
|
||||
Assert::IsFalse(opt2.HasValue());
|
||||
|
||||
Assert::IsTrue(static_cast<bool>(opt3));
|
||||
Assert::IsTrue(opt3.HasValue());
|
||||
|
||||
Assert::IsTrue(static_cast<bool>(opt4));
|
||||
Assert::IsTrue(opt4.HasValue());
|
||||
|
||||
Assert::ExpectException<GLTFException>([&opt1] { opt1.Get(); });
|
||||
Assert::ExpectException<GLTFException>([&opt2] { opt2.Get(); });
|
||||
|
||||
Assert::AreEqual(3U, opt3.Get());
|
||||
Assert::AreEqual(3U, opt4.Get());
|
||||
}
|
||||
|
||||
GLTFSDK_TEST_METHOD(OptionalTests, ConstructorOptionalMove)
|
||||
{
|
||||
Optional<std::unique_ptr<int>> opt1;
|
||||
Optional<std::unique_ptr<int>> opt2(std::move(opt1));
|
||||
Optional<std::unique_ptr<int>> opt3(std::make_unique<int>(3));
|
||||
Optional<std::unique_ptr<int>> opt4(std::move(opt3));
|
||||
|
||||
Assert::IsFalse(static_cast<bool>(opt1));
|
||||
Assert::IsFalse(opt1.HasValue());
|
||||
|
||||
Assert::IsFalse(static_cast<bool>(opt2));
|
||||
Assert::IsFalse(opt2.HasValue());
|
||||
|
||||
Assert::IsFalse(static_cast<bool>(opt3));
|
||||
Assert::IsFalse(opt3.HasValue());
|
||||
|
||||
Assert::IsTrue(static_cast<bool>(opt4));
|
||||
Assert::IsTrue(opt4.HasValue());
|
||||
|
||||
Assert::ExpectException<GLTFException>([&opt1] { opt1.Get(); });
|
||||
Assert::ExpectException<GLTFException>([&opt2] { opt2.Get(); });
|
||||
Assert::ExpectException<GLTFException>([&opt3] { opt3.Get(); });
|
||||
|
||||
Assert::AreEqual(3, *opt4.Get().get());
|
||||
}
|
||||
|
||||
GLTFSDK_TEST_METHOD(OptionalTests, DestructorReset)
|
||||
{
|
||||
unsigned int count = 0U;
|
||||
|
||||
struct Counter
|
||||
{
|
||||
Counter(unsigned int* count_ptr) : count_ptr(count_ptr)
|
||||
{
|
||||
++(*count_ptr);
|
||||
}
|
||||
|
||||
Counter(Counter&& other) : count_ptr(other.count_ptr)
|
||||
{
|
||||
++(*count_ptr);
|
||||
}
|
||||
|
||||
Counter(const Counter& other) : count_ptr(other.count_ptr)
|
||||
{
|
||||
++(*count_ptr);
|
||||
}
|
||||
|
||||
~Counter()
|
||||
{
|
||||
--(*count_ptr);
|
||||
}
|
||||
|
||||
unsigned int* count_ptr;
|
||||
};
|
||||
|
||||
{
|
||||
Optional<Counter> opt1(&count);
|
||||
|
||||
{
|
||||
Assert::AreEqual(1U, count);
|
||||
Optional<Counter> opt2(&count);
|
||||
Assert::AreEqual(2U, count);
|
||||
}
|
||||
|
||||
Assert::AreEqual(1U, count);
|
||||
opt1.Reset();
|
||||
Assert::AreEqual(0U, count);
|
||||
}
|
||||
|
||||
Assert::AreEqual(0U, count);
|
||||
}
|
||||
|
||||
GLTFSDK_TEST_METHOD(OptionalTests, Swap)
|
||||
{
|
||||
// Both lhs and rhs Optionals have values
|
||||
{
|
||||
Optional<char> optA('A');
|
||||
Optional<char> optB('B');
|
||||
|
||||
Assert::AreEqual('A', optA.Get());
|
||||
Assert::AreEqual('B', optB.Get());
|
||||
|
||||
Optional<char>::Swap(optA, optB); // After swapping optA should contain 'B' and optB should contain 'A'
|
||||
|
||||
Assert::AreEqual('B', optA.Get());
|
||||
Assert::AreEqual('A', optB.Get());
|
||||
}
|
||||
|
||||
// Only lhs Optional has a value
|
||||
{
|
||||
Optional<char> optA('A');
|
||||
Optional<char> optB;
|
||||
|
||||
Assert::AreEqual('A', optA.Get());
|
||||
Assert::IsFalse(optB.HasValue());
|
||||
|
||||
Optional<char>::Swap(optA, optB); // After swapping optA should be empty and optB should contain 'A'
|
||||
|
||||
Assert::IsFalse(optA.HasValue());
|
||||
Assert::AreEqual('A', optB.Get());
|
||||
}
|
||||
|
||||
// Only rhs Optional has a value
|
||||
{
|
||||
Optional<char> optA;
|
||||
Optional<char> optB('B');
|
||||
|
||||
Assert::IsFalse(optA.HasValue());
|
||||
Assert::AreEqual('B', optB.Get());
|
||||
|
||||
Optional<char>::Swap(optA, optB); // After swapping optA should contain 'B' and optB should be empty
|
||||
|
||||
Assert::AreEqual('B', optA.Get());
|
||||
Assert::IsFalse(optB.HasValue());
|
||||
}
|
||||
}
|
||||
|
||||
GLTFSDK_TEST_METHOD(OptionalTests, AssignmentValueCopy)
|
||||
{
|
||||
const std::string assignValue("Assign");
|
||||
|
||||
// Test assignment when Optional has no existing value
|
||||
{
|
||||
Optional<std::string> opt;
|
||||
|
||||
Assert::IsFalse(opt.HasValue());
|
||||
opt = assignValue;
|
||||
Assert::IsTrue(opt.HasValue());
|
||||
|
||||
Assert::AreEqual("Assign", opt.Get().c_str());
|
||||
}
|
||||
|
||||
// Test assignment when Optional has an existing value
|
||||
{
|
||||
Optional<std::string> opt("Init");
|
||||
|
||||
Assert::IsTrue(opt.HasValue());
|
||||
opt = assignValue;
|
||||
Assert::IsTrue(opt.HasValue());
|
||||
|
||||
Assert::AreEqual("Assign", opt.Get().c_str());
|
||||
}
|
||||
}
|
||||
|
||||
GLTFSDK_TEST_METHOD(OptionalTests, AssignmentValueMove)
|
||||
{
|
||||
// Test assignment when Optional has no existing value
|
||||
{
|
||||
Optional<std::string> opt;
|
||||
|
||||
Assert::IsFalse(opt.HasValue());
|
||||
opt = std::string("Assign");
|
||||
Assert::IsTrue(opt.HasValue());
|
||||
|
||||
Assert::AreEqual("Assign", opt.Get().c_str());
|
||||
}
|
||||
|
||||
// Test assignment when Optional has an existing value
|
||||
{
|
||||
Optional<std::string> opt("Init");
|
||||
|
||||
Assert::IsTrue(opt.HasValue());
|
||||
opt = std::string("Assign");
|
||||
Assert::IsTrue(opt.HasValue());
|
||||
|
||||
Assert::AreEqual("Assign", opt.Get().c_str());
|
||||
}
|
||||
}
|
||||
|
||||
GLTFSDK_TEST_METHOD(OptionalTests, AssignmentOptionalCopy)
|
||||
{
|
||||
// Test assignment when Optional has no existing value
|
||||
{
|
||||
Optional<std::string> opt1;
|
||||
Optional<std::string> opt2("Assign");
|
||||
|
||||
Assert::IsFalse(opt1.HasValue());
|
||||
Assert::IsTrue(opt2.HasValue());
|
||||
|
||||
opt1 = opt2;
|
||||
|
||||
Assert::IsTrue(opt1.HasValue());
|
||||
Assert::IsTrue(opt2.HasValue());
|
||||
|
||||
Assert::AreEqual("Assign", opt1.Get().c_str());
|
||||
Assert::AreEqual("Assign", opt2.Get().c_str());
|
||||
}
|
||||
|
||||
// Test assignment when Optional has an existing value - assign no value
|
||||
{
|
||||
Optional<std::string> opt1("Init");
|
||||
Optional<std::string> opt2;
|
||||
|
||||
Assert::IsTrue(opt1.HasValue());
|
||||
Assert::IsFalse(opt2.HasValue());
|
||||
|
||||
opt1 = opt2;
|
||||
|
||||
Assert::IsFalse(opt1.HasValue());
|
||||
Assert::IsFalse(opt2.HasValue());
|
||||
}
|
||||
|
||||
// Test assignment when Optional has an existing value
|
||||
{
|
||||
Optional<std::string> opt1("Init");
|
||||
Optional<std::string> opt2("Assign");
|
||||
|
||||
Assert::IsTrue(opt1.HasValue());
|
||||
Assert::IsTrue(opt2.HasValue());
|
||||
|
||||
opt1 = opt2;
|
||||
|
||||
Assert::IsTrue(opt1.HasValue());
|
||||
Assert::IsTrue(opt2.HasValue());
|
||||
|
||||
Assert::AreEqual("Assign", opt1.Get().c_str());
|
||||
Assert::AreEqual("Assign", opt2.Get().c_str());
|
||||
}
|
||||
|
||||
// Test self-assignment with no existing value
|
||||
{
|
||||
Optional<std::string> opt;
|
||||
|
||||
Assert::IsFalse(opt.HasValue());
|
||||
opt = opt;
|
||||
Assert::IsFalse(opt.HasValue());
|
||||
}
|
||||
|
||||
// Test self-assignment with an existing value
|
||||
{
|
||||
Optional<std::string> opt("Init");
|
||||
|
||||
Assert::IsTrue(opt.HasValue());
|
||||
opt = opt;
|
||||
Assert::IsTrue(opt.HasValue());
|
||||
|
||||
Assert::AreEqual("Init", opt.Get().c_str());
|
||||
}
|
||||
}
|
||||
|
||||
GLTFSDK_TEST_METHOD(OptionalTests, AssignmentOptionalMove)
|
||||
{
|
||||
// Test assignment when Optional has no existing value
|
||||
{
|
||||
Optional<std::string> opt1;
|
||||
Optional<std::string> opt2("Assign");
|
||||
|
||||
Assert::IsFalse(opt1.HasValue());
|
||||
Assert::IsTrue(opt2.HasValue());
|
||||
|
||||
opt1 = std::move(opt2);
|
||||
|
||||
Assert::IsTrue(opt1.HasValue());
|
||||
Assert::IsFalse(opt2.HasValue());
|
||||
|
||||
Assert::AreEqual("Assign", opt1.Get().c_str());
|
||||
}
|
||||
|
||||
// Test assignment when Optional has an existing value - assign no value
|
||||
{
|
||||
Optional<std::string> opt1("Init");
|
||||
Optional<std::string> opt2;
|
||||
|
||||
Assert::IsTrue(opt1.HasValue());
|
||||
Assert::IsFalse(opt2.HasValue());
|
||||
|
||||
opt1 = std::move(opt2);
|
||||
|
||||
Assert::IsFalse(opt1.HasValue());
|
||||
Assert::IsFalse(opt2.HasValue());
|
||||
}
|
||||
|
||||
// Test assignment when Optional has an existing value
|
||||
{
|
||||
Optional<std::string> opt1("Init");
|
||||
Optional<std::string> opt2("Assign");
|
||||
|
||||
Assert::IsTrue(opt1.HasValue());
|
||||
Assert::IsTrue(opt2.HasValue());
|
||||
|
||||
opt1 = std::move(opt2);
|
||||
|
||||
Assert::IsTrue(opt1.HasValue());
|
||||
Assert::IsFalse(opt2.HasValue());
|
||||
|
||||
Assert::AreEqual("Assign", opt1.Get().c_str());
|
||||
}
|
||||
|
||||
|
||||
// Disable warnings (clang only) about self move-assignment so it is possible to test that the Optional type works as expected in this situation
|
||||
#ifdef __clang__
|
||||
#pragma clang diagnostic push
|
||||
#pragma clang diagnostic ignored "-Wself-move"
|
||||
#endif
|
||||
// Test self-assignment with no existing value
|
||||
{
|
||||
Optional<std::string> opt;
|
||||
|
||||
Assert::IsFalse(opt.HasValue());
|
||||
opt = std::move(opt);
|
||||
Assert::IsFalse(opt.HasValue());
|
||||
}
|
||||
|
||||
// Test self-assignment with an existing value
|
||||
{
|
||||
Optional<std::string> opt("Init");
|
||||
|
||||
Assert::IsTrue(opt.HasValue());
|
||||
opt = std::move(opt);
|
||||
Assert::IsTrue(opt.HasValue());
|
||||
|
||||
Assert::AreEqual("Init", opt.Get().c_str());
|
||||
}
|
||||
#ifdef __clang__
|
||||
#pragma clang diagnostic pop
|
||||
#endif
|
||||
}
|
||||
|
||||
GLTFSDK_TEST_METHOD(OptionalTests, EqualTo)
|
||||
{
|
||||
// lhs and rhs have no value
|
||||
{
|
||||
Optional<long> opt1;
|
||||
Optional<long> opt2;
|
||||
|
||||
Assert::IsTrue(opt1 == opt2);
|
||||
}
|
||||
|
||||
// only lhs has a value
|
||||
{
|
||||
Optional<long> opt1(1L);
|
||||
Optional<long> opt2;
|
||||
|
||||
Assert::IsFalse(opt1 == opt2);
|
||||
}
|
||||
|
||||
// only rhs has a value
|
||||
{
|
||||
Optional<long> opt1;
|
||||
Optional<long> opt2(1L);
|
||||
|
||||
Assert::IsFalse(opt1 == opt2);
|
||||
}
|
||||
|
||||
// lhs and rhs have the same value
|
||||
{
|
||||
Optional<long> opt1(1L);
|
||||
Optional<long> opt2(1L);
|
||||
|
||||
Assert::IsTrue(opt1 == opt2);
|
||||
}
|
||||
|
||||
// lhs and rhs have different values
|
||||
{
|
||||
Optional<long> opt1(1L);
|
||||
Optional<long> opt2(2L);
|
||||
|
||||
Assert::IsFalse(opt1 == opt2);
|
||||
}
|
||||
}
|
||||
|
||||
GLTFSDK_TEST_METHOD(OptionalTests, NotEqualTo)
|
||||
{
|
||||
// lhs and rhs have no value
|
||||
{
|
||||
Optional<long> opt1;
|
||||
Optional<long> opt2;
|
||||
|
||||
Assert::IsFalse(opt1 != opt2);
|
||||
}
|
||||
|
||||
// only lhs has a value
|
||||
{
|
||||
Optional<long> opt1(1L);
|
||||
Optional<long> opt2;
|
||||
|
||||
Assert::IsTrue(opt1 != opt2);
|
||||
}
|
||||
|
||||
// only rhs has a value
|
||||
{
|
||||
Optional<long> opt1;
|
||||
Optional<long> opt2(1L);
|
||||
|
||||
Assert::IsTrue(opt1 != opt2);
|
||||
}
|
||||
|
||||
// lhs and rhs have the same value
|
||||
{
|
||||
Optional<long> opt1(1L);
|
||||
Optional<long> opt2(1L);
|
||||
|
||||
Assert::IsFalse(opt1 != opt2);
|
||||
}
|
||||
|
||||
// lhs and rhs have different values
|
||||
{
|
||||
Optional<long> opt1(1L);
|
||||
Optional<long> opt2(2L);
|
||||
|
||||
Assert::IsTrue(opt1 != opt2);
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
|
@ -46,11 +46,11 @@ namespace Microsoft
|
|||
|
||||
const Buffer& AddBuffer(const char* bufferId = nullptr);
|
||||
|
||||
const BufferView& AddBufferView(BufferViewTarget target);
|
||||
const BufferView& AddBufferView(const void* data, size_t byteLength, size_t byteStride = 0, BufferViewTarget target = BufferViewTarget::UNKNOWN_BUFFER);
|
||||
const BufferView& AddBufferView(Optional<BufferViewTarget> target);
|
||||
const BufferView& AddBufferView(const void* data, size_t byteLength, Optional<size_t> byteStride = {}, Optional<BufferViewTarget> target = {});
|
||||
|
||||
template<typename T>
|
||||
const BufferView& AddBufferView(const std::vector<T>& data, size_t byteStride = 0, BufferViewTarget target = BufferViewTarget::UNKNOWN_BUFFER)
|
||||
const BufferView& AddBufferView(const std::vector<T>& data, Optional<size_t> byteStride = {}, Optional<BufferViewTarget> target = {})
|
||||
{
|
||||
return AddBufferView(data.data(), data.size() * sizeof(T), byteStride, target);
|
||||
}
|
||||
|
|
|
@ -4,6 +4,7 @@
|
|||
#pragma once
|
||||
|
||||
#include <GLTFSDK/ExtensionHandlers.h>
|
||||
#include <GLTFSDK/Optional.h>
|
||||
|
||||
#include <memory>
|
||||
#include <string>
|
||||
|
@ -84,8 +85,7 @@ namespace Microsoft
|
|||
Vector2 offset;
|
||||
float rotation;
|
||||
Vector2 scale;
|
||||
// TexCoord is optional
|
||||
std::unique_ptr<size_t> texCoord;
|
||||
Optional<size_t> texCoord; // TexCoord is an optional property
|
||||
|
||||
std::unique_ptr<Extension> Clone() const override;
|
||||
bool IsEqual(const Extension& rhs) const override;
|
||||
|
|
|
@ -9,6 +9,7 @@
|
|||
#include <GLTFSDK/Extension.h>
|
||||
#include <GLTFSDK/IndexedContainer.h>
|
||||
#include <GLTFSDK/Math.h>
|
||||
#include <GLTFSDK/Optional.h>
|
||||
|
||||
#include <memory>
|
||||
#include <string>
|
||||
|
@ -23,7 +24,6 @@ namespace Microsoft
|
|||
{
|
||||
enum BufferViewTarget
|
||||
{
|
||||
UNKNOWN_BUFFER = 0,
|
||||
ARRAY_BUFFER = 34962,
|
||||
ELEMENT_ARRAY_BUFFER = 34963
|
||||
};
|
||||
|
@ -332,8 +332,8 @@ namespace Microsoft
|
|||
std::string bufferId;
|
||||
size_t byteOffset = 0U;
|
||||
size_t byteLength = 0U;
|
||||
size_t byteStride = 0U; // 0 = tight packing
|
||||
BufferViewTarget target = UNKNOWN_BUFFER;
|
||||
Optional<size_t> byteStride;
|
||||
Optional<BufferViewTarget> target;
|
||||
|
||||
bool operator==(const BufferView& rhs) const
|
||||
{
|
||||
|
@ -880,16 +880,12 @@ namespace Microsoft
|
|||
|
||||
struct Projection : glTFProperty
|
||||
{
|
||||
float zfar;
|
||||
float znear;
|
||||
|
||||
virtual ProjectionType GetProjectionType() const = 0;
|
||||
virtual std::unique_ptr<Projection> Clone() const = 0;
|
||||
|
||||
virtual bool IsValid() const
|
||||
{
|
||||
return zfar > znear;
|
||||
}
|
||||
virtual bool IsValid() const = 0;
|
||||
|
||||
bool operator==(const Projection& rhs) const
|
||||
{
|
||||
|
@ -902,16 +898,13 @@ namespace Microsoft
|
|||
}
|
||||
|
||||
protected:
|
||||
Projection(float zfar, float znear) :
|
||||
zfar(zfar),
|
||||
znear(znear)
|
||||
Projection(float znear) : znear(znear)
|
||||
{
|
||||
}
|
||||
|
||||
virtual bool IsEqual(const Projection& rhs) const
|
||||
{
|
||||
return glTFProperty::Equals(*this, rhs)
|
||||
&& this->zfar == rhs.zfar
|
||||
&& this->znear == rhs.znear;
|
||||
}
|
||||
};
|
||||
|
@ -920,37 +913,41 @@ namespace Microsoft
|
|||
{
|
||||
float xmag;
|
||||
float ymag;
|
||||
float zfar;
|
||||
|
||||
Orthographic(float zfar, float znear, float xmag, float ymag) :
|
||||
Projection(zfar, znear),
|
||||
Projection(znear),
|
||||
xmag(xmag),
|
||||
ymag(ymag)
|
||||
ymag(ymag),
|
||||
zfar(zfar)
|
||||
{
|
||||
}
|
||||
|
||||
virtual bool IsValid() const
|
||||
bool IsValid() const override
|
||||
{
|
||||
return Projection::IsValid()
|
||||
&& ymag != 0.0
|
||||
&& xmag != 0.0;
|
||||
return (zfar > znear)
|
||||
&& (ymag != 0.0)
|
||||
&& (xmag != 0.0);
|
||||
}
|
||||
|
||||
virtual ProjectionType GetProjectionType() const
|
||||
ProjectionType GetProjectionType() const override
|
||||
{
|
||||
return ProjectionType::ORTHOGRAPHIC;
|
||||
}
|
||||
|
||||
virtual std::unique_ptr<Projection> Clone() const
|
||||
std::unique_ptr<Projection> Clone() const override
|
||||
{
|
||||
return std::make_unique<Orthographic>(*this);
|
||||
}
|
||||
|
||||
virtual bool IsEqual(const Projection& rhs) const
|
||||
bool IsEqual(const Projection& rhs) const override
|
||||
{
|
||||
if (const auto other = dynamic_cast<const Orthographic*>(&rhs))
|
||||
{
|
||||
return Projection::IsEqual(rhs)
|
||||
&& xmag == other->xmag
|
||||
&& ymag == other->ymag;
|
||||
&& ymag == other->ymag
|
||||
&& zfar == other->zfar;
|
||||
}
|
||||
|
||||
return false;
|
||||
|
@ -959,43 +956,59 @@ namespace Microsoft
|
|||
|
||||
struct Perspective : Projection
|
||||
{
|
||||
float aspectRatio;
|
||||
Optional<float> aspectRatio;
|
||||
float yfov;
|
||||
Optional<float> zfar;
|
||||
|
||||
Perspective(float znear, float yfov) :
|
||||
Projection(znear),
|
||||
aspectRatio(),
|
||||
yfov(yfov),
|
||||
zfar()
|
||||
{
|
||||
}
|
||||
|
||||
Perspective(float zfar, float znear, float aspectRatio, float yfov) :
|
||||
Projection(zfar, znear),
|
||||
Projection(znear),
|
||||
aspectRatio(aspectRatio),
|
||||
yfov(yfov)
|
||||
yfov(yfov),
|
||||
zfar(zfar)
|
||||
{
|
||||
}
|
||||
|
||||
bool IsValid() const override
|
||||
{
|
||||
return !zfar.HasValue() || (zfar.Get() > znear);
|
||||
}
|
||||
|
||||
bool IsFinite() const
|
||||
{
|
||||
return zfar != std::numeric_limits<float>::infinity();
|
||||
return zfar.HasValue(); // If zfar is undefined the runtime must use an infinite projection matrix
|
||||
}
|
||||
|
||||
bool HasCustomAspectRatio() const
|
||||
{
|
||||
return aspectRatio != 0.0f;
|
||||
return aspectRatio.HasValue(); // When aspectRatio is undefined the aspect ratio of the 'canvas' should be used
|
||||
}
|
||||
|
||||
virtual ProjectionType GetProjectionType() const
|
||||
ProjectionType GetProjectionType() const override
|
||||
{
|
||||
return ProjectionType::PERSPECTIVE;
|
||||
}
|
||||
|
||||
virtual std::unique_ptr<Projection> Clone() const
|
||||
std::unique_ptr<Projection> Clone() const override
|
||||
{
|
||||
return std::make_unique<Perspective>(*this);
|
||||
}
|
||||
|
||||
virtual bool IsEqual(const Projection& rhs) const
|
||||
bool IsEqual(const Projection& rhs) const override
|
||||
{
|
||||
if (const auto other = dynamic_cast<const Perspective*>(&rhs))
|
||||
{
|
||||
return Projection::IsEqual(rhs)
|
||||
&& aspectRatio == other->aspectRatio
|
||||
&& yfov == other->yfov;
|
||||
&& yfov == other->yfov
|
||||
&& zfar == other->zfar;
|
||||
}
|
||||
|
||||
return false;
|
||||
|
@ -1038,6 +1051,7 @@ namespace Microsoft
|
|||
{
|
||||
return *ret;
|
||||
}
|
||||
|
||||
throw GLTFException("Failed to cast projection to orthographic");
|
||||
}
|
||||
|
||||
|
@ -1047,6 +1061,7 @@ namespace Microsoft
|
|||
{
|
||||
return *ret;
|
||||
}
|
||||
|
||||
throw GLTFException("Failed to cast projection to orthographic");
|
||||
}
|
||||
|
||||
|
@ -1061,6 +1076,7 @@ namespace Microsoft
|
|||
{
|
||||
return !projection && !rhs.projection;
|
||||
}
|
||||
|
||||
return *projection == *(rhs.projection);
|
||||
}
|
||||
|
||||
|
@ -1178,15 +1194,12 @@ namespace Microsoft
|
|||
|
||||
struct Sampler : glTFChildOfRootProperty
|
||||
{
|
||||
static const MagFilterMode kMagFilterDefault = MagFilter_LINEAR;
|
||||
static const MinFilterMode kMinFilterDefault = MinFilter_NEAREST_MIPMAP_LINEAR;
|
||||
static const WrapMode kWrapSDefault = Wrap_REPEAT;
|
||||
static const WrapMode kWrapTDefault = Wrap_REPEAT;
|
||||
|
||||
MagFilterMode magFilter = kMagFilterDefault;
|
||||
MinFilterMode minFilter = kMinFilterDefault;
|
||||
WrapMode wrapS = kWrapSDefault;
|
||||
WrapMode wrapT = kWrapTDefault;
|
||||
// The glTF spec doesn't define default values for magFilter and minFilter members. When
|
||||
// filtering options are not defined implementations are free to select a suitable value
|
||||
Optional<MagFilterMode> magFilter;
|
||||
Optional<MinFilterMode> minFilter;
|
||||
WrapMode wrapS = Wrap_REPEAT;
|
||||
WrapMode wrapT = Wrap_REPEAT;
|
||||
|
||||
static MinFilterMode GetSamplerMinFilterMode(size_t readValue)
|
||||
{
|
||||
|
|
|
@ -137,14 +137,13 @@ namespace Microsoft
|
|||
|
||||
const size_t offset = accessor.byteOffset + bufferView.byteOffset;
|
||||
|
||||
if (bufferView.byteStride == 0U ||
|
||||
bufferView.byteStride == elementSize)
|
||||
if (!bufferView.byteStride || bufferView.byteStride.Get() == elementSize)
|
||||
{
|
||||
data = ReadBinaryData<T>(buffer, offset, accessor.count * typeCount);
|
||||
}
|
||||
else
|
||||
{
|
||||
data = ReadBinaryDataInterleaved<T>(buffer, offset, accessor.count, typeCount, bufferView.byteStride);
|
||||
data = ReadBinaryDataInterleaved<T>(buffer, offset, accessor.count, typeCount, bufferView.byteStride.Get());
|
||||
}
|
||||
|
||||
return data;
|
||||
|
@ -169,14 +168,13 @@ namespace Microsoft
|
|||
|
||||
const size_t offset = accessor.byteOffset + bufferView.byteOffset;
|
||||
|
||||
if (bufferView.byteStride == 0U ||
|
||||
bufferView.byteStride == elementSize)
|
||||
if (!bufferView.byteStride || bufferView.byteStride.Get() == elementSize)
|
||||
{
|
||||
baseData = ReadBinaryData<T>(buffer, offset, accessor.count * typeCount);
|
||||
}
|
||||
else
|
||||
{
|
||||
baseData = ReadBinaryDataInterleaved<T>(buffer, offset, accessor.count, typeCount, bufferView.byteStride);
|
||||
baseData = ReadBinaryDataInterleaved<T>(buffer, offset, accessor.count, typeCount, bufferView.byteStride.Get());
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -367,26 +365,24 @@ namespace Microsoft
|
|||
|
||||
std::vector<I> indices;
|
||||
|
||||
if (indicesBufferView.byteStride == 0U ||
|
||||
indicesBufferView.byteStride == sizeof(I))
|
||||
if (!indicesBufferView.byteStride || indicesBufferView.byteStride.Get() == sizeof(I))
|
||||
{
|
||||
indices = ReadBinaryData<I>(indicesBuffer, indicesOffset, count);
|
||||
}
|
||||
else
|
||||
{
|
||||
indices = ReadBinaryDataInterleaved<I>(indicesBuffer, indicesOffset, count, 1U, indicesBufferView.byteStride);
|
||||
indices = ReadBinaryDataInterleaved<I>(indicesBuffer, indicesOffset, count, 1U, indicesBufferView.byteStride.Get());
|
||||
}
|
||||
|
||||
std::vector<T> values;
|
||||
|
||||
if (valuesBufferView.byteStride == 0U ||
|
||||
valuesBufferView.byteStride == elementSize)
|
||||
if (!valuesBufferView.byteStride || valuesBufferView.byteStride.Get() == elementSize)
|
||||
{
|
||||
values = ReadBinaryData<T>(valuesBuffer, valuesOffset, count * typeCount);
|
||||
}
|
||||
else
|
||||
{
|
||||
values = ReadBinaryDataInterleaved<T>(valuesBuffer, valuesOffset, count, typeCount, valuesBufferView.byteStride);
|
||||
values = ReadBinaryDataInterleaved<T>(valuesBuffer, valuesOffset, count, typeCount, valuesBufferView.byteStride.Get());
|
||||
}
|
||||
|
||||
for (size_t i = 0; i < indices.size(); i++)
|
||||
|
|
|
@ -0,0 +1,198 @@
|
|||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
// Licensed under the MIT License.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "Exceptions.h"
|
||||
|
||||
namespace Microsoft
|
||||
{
|
||||
namespace glTF
|
||||
{
|
||||
template<typename T>
|
||||
class Optional final
|
||||
{
|
||||
public:
|
||||
Optional() : isConstructed(false)
|
||||
{
|
||||
}
|
||||
|
||||
Optional(T&& t) : isConstructed(true)
|
||||
{
|
||||
new (this->GetStorage()) T(std::move(t)); // In-place new;
|
||||
}
|
||||
|
||||
Optional(const T& t) : isConstructed(true)
|
||||
{
|
||||
new (this->GetStorage()) T(t); // In-place new;
|
||||
}
|
||||
|
||||
Optional(Optional<T>&& other) : isConstructed(false)
|
||||
{
|
||||
if (other.isConstructed)
|
||||
{
|
||||
Swap(other);
|
||||
}
|
||||
}
|
||||
|
||||
Optional(const Optional& other) : isConstructed(other.isConstructed)
|
||||
{
|
||||
if (other.isConstructed)
|
||||
{
|
||||
new (this->GetStorage()) T(other.Get()); // In-place new;
|
||||
}
|
||||
}
|
||||
|
||||
~Optional()
|
||||
{
|
||||
Reset();
|
||||
}
|
||||
|
||||
T& Get()
|
||||
{
|
||||
if (isConstructed)
|
||||
{
|
||||
return *static_cast<T*>(this->GetStorage());
|
||||
}
|
||||
else
|
||||
{
|
||||
throw GLTFException("Optional has no value");
|
||||
}
|
||||
}
|
||||
|
||||
const T& Get() const
|
||||
{
|
||||
if (isConstructed)
|
||||
{
|
||||
return *static_cast<const T*>(this->GetStorage());
|
||||
}
|
||||
else
|
||||
{
|
||||
throw GLTFException("Optional has no value");
|
||||
}
|
||||
}
|
||||
|
||||
bool HasValue() const noexcept
|
||||
{
|
||||
return isConstructed;
|
||||
}
|
||||
|
||||
void Reset()
|
||||
{
|
||||
if (isConstructed)
|
||||
{
|
||||
Get().~T(); // In-place delete;
|
||||
}
|
||||
|
||||
isConstructed = false;
|
||||
}
|
||||
|
||||
void Swap(Optional& other)
|
||||
{
|
||||
Swap(*this, other);
|
||||
}
|
||||
|
||||
static void Swap(Optional& lhs, Optional& rhs)
|
||||
{
|
||||
if (lhs && rhs)
|
||||
{
|
||||
std::swap(lhs.Get(), rhs.Get());
|
||||
}
|
||||
else if (lhs)
|
||||
{
|
||||
new (rhs.GetStorage()) T(std::move(lhs.Get()));
|
||||
rhs.isConstructed = true;
|
||||
lhs.Reset();
|
||||
}
|
||||
else if (rhs)
|
||||
{
|
||||
new (lhs.GetStorage()) T(std::move(rhs.Get()));
|
||||
lhs.isConstructed = true;
|
||||
rhs.Reset();
|
||||
}
|
||||
}
|
||||
|
||||
Optional& operator=(Optional&& other)
|
||||
{
|
||||
if (this != &other)
|
||||
{
|
||||
Optional(std::move(other)).Swap(*this);
|
||||
}
|
||||
|
||||
return *this;
|
||||
}
|
||||
|
||||
Optional& operator=(const Optional& other)
|
||||
{
|
||||
if (this != &other)
|
||||
{
|
||||
Optional(other).Swap(*this);
|
||||
}
|
||||
|
||||
return *this;
|
||||
}
|
||||
|
||||
Optional& operator=(T&& t)
|
||||
{
|
||||
if (isConstructed)
|
||||
{
|
||||
Get() = std::move(t);
|
||||
}
|
||||
else
|
||||
{
|
||||
new (this->GetStorage()) T(std::move(t)); // In-place new;
|
||||
isConstructed = true;
|
||||
}
|
||||
|
||||
return *this;
|
||||
}
|
||||
|
||||
Optional& operator=(const T& t)
|
||||
{
|
||||
if (isConstructed)
|
||||
{
|
||||
Get() = t;
|
||||
}
|
||||
else
|
||||
{
|
||||
new (this->GetStorage()) T(t); // In-place new;
|
||||
isConstructed = true;
|
||||
}
|
||||
|
||||
return *this;
|
||||
}
|
||||
|
||||
explicit operator bool() const noexcept
|
||||
{
|
||||
return HasValue();
|
||||
}
|
||||
|
||||
private:
|
||||
void* GetStorage()
|
||||
{
|
||||
return storage;
|
||||
}
|
||||
|
||||
const void* GetStorage() const
|
||||
{
|
||||
return storage;
|
||||
}
|
||||
|
||||
alignas(alignof(T)) unsigned char storage[sizeof(T)];
|
||||
|
||||
bool isConstructed;
|
||||
};
|
||||
|
||||
template<typename T>
|
||||
bool operator==(const Optional<T>& lhs, const Optional<T>& rhs)
|
||||
{
|
||||
return (!lhs && !rhs) || ((lhs && rhs) && (lhs.Get() == rhs.Get()));
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
bool operator!=(const Optional<T>& lhs, const Optional<T>& rhs)
|
||||
{
|
||||
return !(lhs == rhs);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -62,7 +62,7 @@ const Buffer& BufferBuilder::AddBuffer(const char* bufferId)
|
|||
return bufferRef;
|
||||
}
|
||||
|
||||
const BufferView& BufferBuilder::AddBufferView(BufferViewTarget target)
|
||||
const BufferView& BufferBuilder::AddBufferView(Optional<BufferViewTarget> target)
|
||||
{
|
||||
Buffer& buffer = m_buffers.Back();
|
||||
BufferView bufferView;
|
||||
|
@ -80,7 +80,7 @@ const BufferView& BufferBuilder::AddBufferView(BufferViewTarget target)
|
|||
return m_bufferViews.Append(std::move(bufferView), AppendIdPolicy::GenerateOnEmpty);
|
||||
}
|
||||
|
||||
const BufferView& BufferBuilder::AddBufferView(const void* data, size_t byteLength, size_t byteStride, BufferViewTarget target)
|
||||
const BufferView& BufferBuilder::AddBufferView(const void* data, size_t byteLength, Optional<size_t> byteStride, Optional<BufferViewTarget> target)
|
||||
{
|
||||
Buffer& buffer = m_buffers.Back();
|
||||
BufferView bufferView;
|
||||
|
@ -170,7 +170,8 @@ void BufferBuilder::AddAccessors(const void* data, size_t count, size_t byteStri
|
|||
extent = count * byteStride;
|
||||
|
||||
// Ensure all accessors fit within the buffer view's extent.
|
||||
const size_t lastElement = (count - 1) * bufferView.byteStride;
|
||||
const size_t lastElement = (count - 1) * (bufferView.byteStride ? bufferView.byteStride.Get() : 0U);
|
||||
|
||||
for (size_t i = 0; i < descCount; ++i)
|
||||
{
|
||||
const size_t accessorSize = Accessor::GetTypeCount(pDescs[i].accessorType) * Accessor::GetComponentTypeSize(pDescs[i].componentType);
|
||||
|
|
|
@ -181,18 +181,18 @@ namespace
|
|||
bv.bufferId = std::to_string(FindRequiredMember("buffer", v)->value.GetUint());
|
||||
bv.byteOffset = GetMemberValueOrDefault<size_t>(v, "byteOffset");
|
||||
bv.byteLength = GetValue<size_t>(FindRequiredMember("byteLength", v)->value);
|
||||
bv.byteStride = GetMemberValueOrDefault<size_t>(v, "byteStride", 0U);
|
||||
|
||||
auto itByteStride = v.FindMember("byteStride");
|
||||
if (itByteStride != v.MemberEnd())
|
||||
{
|
||||
bv.byteStride = itByteStride->value.GetUint();
|
||||
}
|
||||
|
||||
// When target is not provided, the bufferView contains animation or skin data
|
||||
auto it = v.FindMember("target");
|
||||
|
||||
if (it != v.MemberEnd())
|
||||
auto itTarget = v.FindMember("target");
|
||||
if (itTarget != v.MemberEnd())
|
||||
{
|
||||
bv.target = static_cast<BufferViewTarget>(it->value.GetUint());
|
||||
}
|
||||
else
|
||||
{
|
||||
bv.target = BufferViewTarget::UNKNOWN_BUFFER;
|
||||
bv.target = static_cast<BufferViewTarget>(itTarget->value.GetUint());
|
||||
}
|
||||
|
||||
ParseProperty(v, bv, extensionDeserializer);
|
||||
|
@ -413,13 +413,33 @@ namespace
|
|||
throw InvalidGLTFException("Camera perspective projection undefined");
|
||||
}
|
||||
|
||||
float aspectRatio = GetMemberValueOrDefault<float>(perspectiveIt->value, "aspectRatio", 0.0f);
|
||||
Optional<float> aspectRatio;
|
||||
|
||||
auto itAspectRatio = perspectiveIt->value.FindMember("target");
|
||||
if (itAspectRatio != perspectiveIt->value.MemberEnd())
|
||||
{
|
||||
aspectRatio = itAspectRatio->value.GetFloat();
|
||||
}
|
||||
|
||||
float yfov = GetValue<float>(FindRequiredMember("yfov", perspectiveIt->value)->value);
|
||||
float znear = GetValue<float>(FindRequiredMember("znear", perspectiveIt->value)->value);
|
||||
float zfar = GetMemberValueOrDefault<float>(perspectiveIt->value, "zfar", std::numeric_limits<float>::infinity());
|
||||
projection = std::make_unique<Perspective>(zfar, znear, aspectRatio, yfov);
|
||||
|
||||
ParseProperty(perspectiveIt->value, *projection, extensionDeserializer);
|
||||
Optional<float> zfar;
|
||||
|
||||
auto itZFar = perspectiveIt->value.FindMember("zfar");
|
||||
if (itZFar != perspectiveIt->value.MemberEnd())
|
||||
{
|
||||
zfar = itZFar->value.GetFloat();
|
||||
}
|
||||
|
||||
auto perspective = std::make_unique<Perspective>(znear, yfov);
|
||||
|
||||
perspective->zfar = zfar;
|
||||
perspective->aspectRatio = aspectRatio;
|
||||
|
||||
ParseProperty(perspectiveIt->value, *perspective, extensionDeserializer);
|
||||
|
||||
projection = std::move(perspective);
|
||||
}
|
||||
else if (projectionType == "orthographic")
|
||||
{
|
||||
|
@ -485,10 +505,20 @@ namespace
|
|||
Sampler sampler;
|
||||
|
||||
sampler.name = GetMemberValueOrDefault<std::string>(v, "name");
|
||||
sampler.minFilter = Sampler::GetSamplerMinFilterMode(GetMemberValueOrDefault<int>(v, "minFilter", static_cast<int>(Sampler::kMinFilterDefault)));
|
||||
sampler.magFilter = Sampler::GetSamplerMagFilterMode(GetMemberValueOrDefault<int>(v, "magFilter", static_cast<int>(Sampler::kMagFilterDefault)));
|
||||
sampler.wrapT = Sampler::GetSamplerWrapMode(GetMemberValueOrDefault<int>(v, "wrapT", static_cast<int>(Sampler::kWrapTDefault)));
|
||||
sampler.wrapS = Sampler::GetSamplerWrapMode(GetMemberValueOrDefault<int>(v, "wrapS", static_cast<int>(Sampler::kWrapSDefault)));
|
||||
sampler.wrapT = Sampler::GetSamplerWrapMode(GetMemberValueOrDefault<unsigned int>(v, "wrapT", static_cast<unsigned int>(WrapMode::Wrap_REPEAT)));
|
||||
sampler.wrapS = Sampler::GetSamplerWrapMode(GetMemberValueOrDefault<unsigned int>(v, "wrapS", static_cast<unsigned int>(WrapMode::Wrap_REPEAT)));
|
||||
|
||||
auto itMin = v.FindMember("minFilter");
|
||||
if (itMin != v.MemberEnd())
|
||||
{
|
||||
sampler.minFilter = Sampler::GetSamplerMinFilterMode(itMin->value.GetUint());
|
||||
}
|
||||
|
||||
auto itMag = v.FindMember("magFilter");
|
||||
if (itMag != v.MemberEnd())
|
||||
{
|
||||
sampler.magFilter = Sampler::GetSamplerMagFilterMode(itMag->value.GetUint());
|
||||
}
|
||||
|
||||
ParseProperty(v, sampler, extensionDeserializer);
|
||||
|
||||
|
|
|
@ -406,19 +406,17 @@ std::unique_ptr<Extension> KHR::MeshPrimitives::DeserializeDracoMeshCompression(
|
|||
KHR::TextureInfos::TextureTransform::TextureTransform() :
|
||||
offset(Vector2::ZERO),
|
||||
rotation(0.0f),
|
||||
scale(Vector2::ONE)
|
||||
scale(Vector2::ONE),
|
||||
texCoord()
|
||||
{
|
||||
}
|
||||
|
||||
KHR::TextureInfos::TextureTransform::TextureTransform(const TextureTransform& textureTransform) :
|
||||
offset(textureTransform.offset),
|
||||
rotation(textureTransform.rotation),
|
||||
scale(textureTransform.scale)
|
||||
scale(textureTransform.scale),
|
||||
texCoord(textureTransform.texCoord)
|
||||
{
|
||||
if (textureTransform.texCoord)
|
||||
{
|
||||
texCoord = std::make_unique<size_t>(*textureTransform.texCoord);
|
||||
}
|
||||
}
|
||||
|
||||
std::unique_ptr<Extension> KHR::TextureInfos::TextureTransform::Clone() const
|
||||
|
@ -435,9 +433,7 @@ bool KHR::TextureInfos::TextureTransform::IsEqual(const Extension& rhs) const
|
|||
&& this->offset == other->offset
|
||||
&& this->rotation == other->rotation
|
||||
&& this->scale == other->scale
|
||||
&& ((!this->texCoord && !other->texCoord) ||
|
||||
((this->texCoord && other->texCoord) &&
|
||||
*this->texCoord == *other->texCoord));
|
||||
&& this->texCoord == other->texCoord;
|
||||
}
|
||||
|
||||
std::string KHR::TextureInfos::SerializeTextureTransform(const TextureTransform& textureTransform, const Document& gltfDocument, const ExtensionSerializer& extensionSerializer)
|
||||
|
@ -463,7 +459,7 @@ std::string KHR::TextureInfos::SerializeTextureTransform(const TextureTransform&
|
|||
|
||||
if (textureTransform.texCoord)
|
||||
{
|
||||
KHR_textureTransform.AddMember("texCoord", ToKnownSizeType(*textureTransform.texCoord), a);
|
||||
KHR_textureTransform.AddMember("texCoord", ToKnownSizeType(textureTransform.texCoord.Get()), a);
|
||||
}
|
||||
|
||||
SerializeProperty(gltfDocument, textureTransform, KHR_textureTransform, a, extensionSerializer);
|
||||
|
@ -526,7 +522,7 @@ std::unique_ptr<Extension> KHR::TextureInfos::DeserializeTextureTransform(const
|
|||
auto texCoordIt = sit.FindMember("texCoord");
|
||||
if (texCoordIt != sit.MemberEnd())
|
||||
{
|
||||
textureTransform.texCoord = std::make_unique<size_t>(static_cast<size_t>(texCoordIt->value.GetUint()));
|
||||
textureTransform.texCoord = static_cast<size_t>(texCoordIt->value.GetUint());
|
||||
}
|
||||
|
||||
ParseProperty(sit, textureTransform, extensionDeserializer);
|
||||
|
|
|
@ -336,16 +336,14 @@ namespace
|
|||
bufferViewValue.AddMember("byteOffset", ToKnownSizeType(bufferView.byteOffset), a);
|
||||
bufferViewValue.AddMember("byteLength", ToKnownSizeType(bufferView.byteLength), a);
|
||||
|
||||
// byteStride of 0U (default) means that data is tightly packed, so omit byteStride
|
||||
if (bufferView.byteStride != 0U)
|
||||
if (bufferView.byteStride)
|
||||
{
|
||||
bufferViewValue.AddMember("byteStride", ToKnownSizeType(bufferView.byteStride), a);
|
||||
bufferViewValue.AddMember("byteStride", ToKnownSizeType(bufferView.byteStride.Get()), a);
|
||||
}
|
||||
|
||||
// Do not write default/invalid values
|
||||
if (bufferView.target != UNKNOWN_BUFFER)
|
||||
if (bufferView.target)
|
||||
{
|
||||
bufferViewValue.AddMember("target", bufferView.target, a);
|
||||
bufferViewValue.AddMember("target", bufferView.target.Get(), a);
|
||||
}
|
||||
|
||||
SerializeProperty(gltfDocument, bufferView, bufferViewValue, a, extensionSerializer);
|
||||
|
@ -636,33 +634,39 @@ namespace
|
|||
|
||||
if (camera.projection->GetProjectionType() == ProjectionType::PERSPECTIVE)
|
||||
{
|
||||
projectionValue.AddMember("znear", RapidJsonUtils::ToFloatValue(camera.GetPerspective().znear), a);
|
||||
projectionValue.AddMember("yfov", RapidJsonUtils::ToFloatValue(camera.GetPerspective().yfov), a);
|
||||
if (camera.GetPerspective().zfar != std::numeric_limits<float>::infinity())
|
||||
const auto& perspective = camera.GetPerspective();
|
||||
|
||||
projectionValue.AddMember("znear", RapidJsonUtils::ToFloatValue(perspective.znear), a);
|
||||
projectionValue.AddMember("yfov", RapidJsonUtils::ToFloatValue(perspective.yfov), a);
|
||||
|
||||
if (perspective.zfar)
|
||||
{
|
||||
projectionValue.AddMember("zfar", RapidJsonUtils::ToFloatValue(camera.projection->zfar), a);
|
||||
}
|
||||
if (camera.GetPerspective().HasCustomAspectRatio())
|
||||
{
|
||||
projectionValue.AddMember("aspectRatio", RapidJsonUtils::ToFloatValue(camera.GetPerspective().aspectRatio), a);
|
||||
projectionValue.AddMember("zfar", RapidJsonUtils::ToFloatValue(perspective.zfar.Get()), a);
|
||||
}
|
||||
|
||||
SerializeProperty(gltfDocument, *camera.projection, projectionValue, a, extensionSerializer);
|
||||
if (perspective.aspectRatio)
|
||||
{
|
||||
projectionValue.AddMember("aspectRatio", RapidJsonUtils::ToFloatValue(perspective.aspectRatio.Get()), a);
|
||||
}
|
||||
|
||||
SerializeProperty(gltfDocument, perspective, projectionValue, a, extensionSerializer);
|
||||
|
||||
cameraValue.AddMember("perspective", projectionValue, a);
|
||||
cameraValue.AddMember("type", RapidJsonUtils::ToStringValue("perspective", a), a);
|
||||
}
|
||||
else if (camera.projection->GetProjectionType() == ProjectionType::ORTHOGRAPHIC)
|
||||
{
|
||||
projectionValue.AddMember("xmag", RapidJsonUtils::ToFloatValue(camera.GetOrthographic().xmag), a);
|
||||
projectionValue.AddMember("ymag", RapidJsonUtils::ToFloatValue(camera.GetOrthographic().ymag), a);
|
||||
projectionValue.AddMember("znear", RapidJsonUtils::ToFloatValue(camera.GetOrthographic().znear), a);
|
||||
projectionValue.AddMember("zfar", RapidJsonUtils::ToFloatValue(camera.GetOrthographic().zfar), a);
|
||||
const auto& orthographic = camera.GetOrthographic();
|
||||
|
||||
SerializeProperty(gltfDocument, *camera.projection, projectionValue, a, extensionSerializer);
|
||||
projectionValue.AddMember("xmag", RapidJsonUtils::ToFloatValue(orthographic.xmag), a);
|
||||
projectionValue.AddMember("ymag", RapidJsonUtils::ToFloatValue(orthographic.ymag), a);
|
||||
projectionValue.AddMember("znear", RapidJsonUtils::ToFloatValue(orthographic.znear), a);
|
||||
projectionValue.AddMember("zfar", RapidJsonUtils::ToFloatValue(orthographic.zfar), a);
|
||||
|
||||
SerializeProperty(gltfDocument, orthographic, projectionValue, a, extensionSerializer);
|
||||
|
||||
cameraValue.AddMember("type", RapidJsonUtils::ToStringValue("orthographic", a), a);
|
||||
cameraValue.AddMember("orthographic", projectionValue, a);
|
||||
cameraValue.AddMember("type", RapidJsonUtils::ToStringValue("orthographic", a), a);
|
||||
}
|
||||
|
||||
SerializeProperty(gltfDocument, camera, cameraValue, a, extensionSerializer);
|
||||
|
@ -676,28 +680,33 @@ namespace
|
|||
{
|
||||
rapidjson::Document::AllocatorType& a = document.GetAllocator();
|
||||
rapidjson::Value samplerValue(rapidjson::kObjectType);
|
||||
|
||||
{
|
||||
RapidJsonUtils::AddOptionalMember("name", samplerValue, sampler.name, a);
|
||||
|
||||
if (sampler.magFilter != Sampler::kMagFilterDefault)
|
||||
if (sampler.magFilter)
|
||||
{
|
||||
samplerValue.AddMember("magFilter", sampler.magFilter, a);
|
||||
samplerValue.AddMember("magFilter", sampler.magFilter.Get(), a);
|
||||
}
|
||||
if (sampler.minFilter != Sampler::kMinFilterDefault)
|
||||
|
||||
if (sampler.minFilter)
|
||||
{
|
||||
samplerValue.AddMember("minFilter", sampler.minFilter, a);
|
||||
samplerValue.AddMember("minFilter", sampler.minFilter.Get(), a);
|
||||
}
|
||||
if (sampler.wrapS != Sampler::kWrapSDefault)
|
||||
|
||||
if (sampler.wrapS != WrapMode::Wrap_REPEAT)
|
||||
{
|
||||
samplerValue.AddMember("wrapS", sampler.wrapS, a);
|
||||
}
|
||||
if (sampler.wrapT != Sampler::kWrapTDefault)
|
||||
|
||||
if (sampler.wrapT != WrapMode::Wrap_REPEAT)
|
||||
{
|
||||
samplerValue.AddMember("wrapT", sampler.wrapT, a);
|
||||
}
|
||||
|
||||
SerializeProperty(gltfDocument, sampler, samplerValue, a, extensionSerializer);
|
||||
}
|
||||
|
||||
return samplerValue;
|
||||
}
|
||||
|
||||
|
|
Загрузка…
Ссылка в новой задаче