Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions NEW_RELEASE_NOTES.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,3 +6,4 @@
appropriate header in [RELEASE_NOTES.md](./RELEASE_NOTES.md).

## Release notes for next branch cut
- engine: Support custom attributes morphing, and allow for omitting position and/or normal data. [⚠️ **Recompile Materials**]
16 changes: 16 additions & 0 deletions android/filament-android/src/main/cpp/Engine.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@

#include <filament/Camera.h>
#include <filament/Engine.h>
#include <filament/MorphTargetBuffer.h>

#include <utils/Entity.h>
#include <utils/EntityManager.h>
Expand Down Expand Up @@ -207,6 +208,14 @@ Java_com_google_android_filament_Engine_nDestroySkinningBuffer(JNIEnv*, jclass,
return engine->destroy(skinningBuffer);
}

extern "C" JNIEXPORT jboolean JNICALL
Java_com_google_android_filament_Engine_nDestroyMorphTargetBuffer(JNIEnv*, jclass,
jlong nativeEngine, jlong nativeMorphTargetBuffer) {
Engine* engine = (Engine*) nativeEngine;
MorphTargetBuffer* mtb = (MorphTargetBuffer*) nativeMorphTargetBuffer;
return engine->destroy(mtb);
}

extern "C" JNIEXPORT jboolean JNICALL
Java_com_google_android_filament_Engine_nDestroyIndirectLight(JNIEnv*, jclass,
jlong nativeEngine, jlong nativeIndirectLight) {
Expand Down Expand Up @@ -328,6 +337,13 @@ Java_com_google_android_filament_Engine_nIsValidSkinningBuffer(JNIEnv*, jclass,
return (jboolean)engine->isValid((SkinningBuffer*)nativeSkinningBuffer);
}

extern "C" JNIEXPORT jboolean JNICALL
Java_com_google_android_filament_Engine_nIsValidMorphTargetBuffer(JNIEnv*, jclass,
jlong nativeEngine, jlong nativeMorphTargetBuffer) {
Engine* engine = (Engine*) nativeEngine;
return (jboolean) engine->isValid((MorphTargetBuffer*) nativeMorphTargetBuffer);
}

extern "C" JNIEXPORT jboolean JNICALL
Java_com_google_android_filament_Engine_nIsValidIndirectLight(JNIEnv*, jclass,
jlong nativeEngine, jlong nativeIndirectLight) {
Expand Down
42 changes: 42 additions & 0 deletions android/filament-android/src/main/cpp/MorphTargetBuffer.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,27 @@ Java_com_google_android_filament_MorphTargetBuffer_nBuilderCount(JNIEnv*, jclass
builder->count((size_t) count);
}

extern "C" JNIEXPORT void JNICALL
Java_com_google_android_filament_MorphTargetBuffer_nBuilderWithPositions(JNIEnv*, jclass,
jlong nativeBuilder, jboolean enabled) {
MorphTargetBuffer::Builder* builder = (MorphTargetBuffer::Builder*) nativeBuilder;
builder->withPositions(enabled);
}

extern "C" JNIEXPORT void JNICALL
Java_com_google_android_filament_MorphTargetBuffer_nBuilderWithTangents(JNIEnv*, jclass,
jlong nativeBuilder, jboolean enabled) {
MorphTargetBuffer::Builder* builder = (MorphTargetBuffer::Builder*) nativeBuilder;
builder->withTangents(enabled);
}

extern "C" JNIEXPORT void JNICALL
Java_com_google_android_filament_MorphTargetBuffer_nBuilderEnableCustomMorphing(JNIEnv*,
jclass, jlong nativeBuilder, jboolean enabled) {
MorphTargetBuffer::Builder* builder = (MorphTargetBuffer::Builder*) nativeBuilder;
builder->enableCustomMorphing(enabled);
}

extern "C"
JNIEXPORT jlong JNICALL
Java_com_google_android_filament_MorphTargetBuffer_nBuilderBuild(JNIEnv*, jclass,
Expand Down Expand Up @@ -112,3 +133,24 @@ Java_com_google_android_filament_MorphTargetBuffer_nGetCount(JNIEnv*, jclass,
MorphTargetBuffer *morphTargetBuffer = (MorphTargetBuffer *) nativeObject;
return (jint)morphTargetBuffer->getCount();
}

extern "C" JNIEXPORT jboolean JNICALL
Java_com_google_android_filament_MorphTargetBuffer_nHasPositions(JNIEnv*, jclass,
jlong nativeObject) {
MorphTargetBuffer* morphTargetBuffer = (MorphTargetBuffer*) nativeObject;
return (jboolean) morphTargetBuffer->hasPositions();
}

extern "C" JNIEXPORT jboolean JNICALL
Java_com_google_android_filament_MorphTargetBuffer_nHasTangents(JNIEnv*, jclass,
jlong nativeObject) {
MorphTargetBuffer* morphTargetBuffer = (MorphTargetBuffer*) nativeObject;
return (jboolean) morphTargetBuffer->hasTangents();
}

extern "C" JNIEXPORT jboolean JNICALL
Java_com_google_android_filament_MorphTargetBuffer_nIsCustomMorphingEnabled(JNIEnv*, jclass,
jlong nativeObject) {
MorphTargetBuffer* morphTargetBuffer = (MorphTargetBuffer*) nativeObject;
return (jboolean) morphTargetBuffer->isCustomMorphingEnabled();
}
Original file line number Diff line number Diff line change
Expand Up @@ -939,6 +939,15 @@ public boolean isValidSkinningBuffer(@NonNull SkinningBuffer object) {
return nIsValidSkinningBuffer(getNativeObject(), object.getNativeObject());
}

/**
* Returns whether the object is valid.
* @param object Object to check for validity
* @return returns true if the specified object is valid.
*/
public boolean isValidMorphTargetBuffer(@NonNull MorphTargetBuffer object) {
return nIsValidMorphTargetBuffer(getNativeObject(), object.getNativeObject());
}

/**
* Returns whether the object is valid.
* @param object Object to check for validity
Expand Down Expand Up @@ -1192,6 +1201,15 @@ public void destroySkinningBuffer(@NonNull SkinningBuffer skinningBuffer) {
skinningBuffer.clearNativeObject();
}

/**
* Destroys a {@link MorphTargetBuffer} and frees all its associated resources.
* @param morphTargetBuffer the {@link MorphTargetBuffer} to destroy
*/
public void destroyMorphTargetBuffer(@NonNull MorphTargetBuffer morphTargetBuffer) {
assertDestroy(nDestroyMorphTargetBuffer(getNativeObject(), morphTargetBuffer.getNativeObject()));
morphTargetBuffer.clearNativeObject();
}

/**
* Destroys a {@link IndirectLight} and frees all its associated resources.
* @param ibl the {@link IndirectLight} to destroy
Expand Down Expand Up @@ -1483,6 +1501,7 @@ private static void assertDestroy(boolean success) {
private static native boolean nDestroyIndexBuffer(long nativeEngine, long nativeIndexBuffer);
private static native boolean nDestroyVertexBuffer(long nativeEngine, long nativeVertexBuffer);
private static native boolean nDestroySkinningBuffer(long nativeEngine, long nativeSkinningBuffer);
private static native boolean nDestroyMorphTargetBuffer(long nativeEngine, long nativeMorphTargetBuffer);
private static native boolean nDestroyIndirectLight(long nativeEngine, long nativeIndirectLight);
private static native boolean nDestroyMaterial(long nativeEngine, long nativeMaterial);
private static native boolean nDestroyMaterialInstance(long nativeEngine, long nativeMaterialInstance);
Expand All @@ -1499,6 +1518,7 @@ private static void assertDestroy(boolean success) {
private static native boolean nIsValidIndexBuffer(long nativeEngine, long nativeIndexBuffer);
private static native boolean nIsValidVertexBuffer(long nativeEngine, long nativeVertexBuffer);
private static native boolean nIsValidSkinningBuffer(long nativeEngine, long nativeSkinningBuffer);
private static native boolean nIsValidMorphTargetBuffer(long nativeEngine, long nativeMorphTargetBuffer);
private static native boolean nIsValidIndirectLight(long nativeEngine, long nativeIndirectLight);
private static native boolean nIsValidMaterial(long nativeEngine, long nativeMaterial);
private static native boolean nIsValidMaterialInstance(long nativeEngine, long nativeMaterial, long nativeMaterialInstance);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,45 @@ public Builder count(@IntRange(from = 1) int count) {
return this;
}

/**
* Use this method to enable or disable the built-in position morphing buffer.
* Default is true.
*
* @param enabled true to enable, false to disable
* @return this <code>Builder</code> object for chaining calls
*/
@NonNull
public Builder withPositions(boolean enabled) {
nBuilderWithPositions(mNativeBuilder, enabled);
return this;
}

/**
* Use this method to enable or disable the built-in tangent morphing buffer.
* Default is true.
*
* @param enabled true to enable, false to disable
* @return this <code>Builder</code> object for chaining calls
*/
@NonNull
public Builder withTangents(boolean enabled) {
nBuilderWithTangents(mNativeBuilder, enabled);
return this;
}

/**
* Use this method to enable or disable custom morphing.
* Default is false.
*
* @param enabled true to enable, false to disable
* @return this <code>Builder</code> object for chaining calls
*/
@NonNull
public Builder enableCustomMorphing(boolean enabled) {
nBuilderEnableCustomMorphing(mNativeBuilder, enabled);
return this;
}

/**
* Creates and returns the <code>MorphTargetBuffer</code> object.
*
Expand Down Expand Up @@ -156,6 +195,27 @@ public int getCount() {
return nGetCount(mNativeObject);
}

/**
* @return true if this MorphTargetBuffer has a position buffer.
*/
public boolean hasPositions() {
return nHasPositions(mNativeObject);
}

/**
* @return true if this MorphTargetBuffer has a tangent buffer.
*/
public boolean hasTangents() {
return nHasTangents(mNativeObject);
}

/**
* @return true if custom morphing is enabled.
*/
public boolean isCustomMorphingEnabled() {
return nIsCustomMorphingEnabled(mNativeObject);
}

public long getNativeObject() {
if (mNativeObject == 0) {
throw new IllegalStateException("Calling method on destroyed MorphTargetBuffer");
Expand All @@ -171,10 +231,16 @@ void clearNativeObject() {
private static native void nDestroyBuilder(long nativeBuilder);
private static native void nBuilderVertexCount(long nativeBuilder, int vertexCount);
private static native void nBuilderCount(long nativeBuilder, int count);
private static native void nBuilderWithPositions(long nativeBuilder, boolean enabled);
private static native void nBuilderWithTangents(long nativeBuilder, boolean enabled);
private static native void nBuilderEnableCustomMorphing(long nativeBuilder, boolean enabled);
private static native long nBuilderBuild(long nativeBuilder, long nativeEngine);

private static native int nSetPositionsAt(long nativeObject, long nativeEngine, int targetIndex, float[] positions, int count);
private static native int nSetTangentsAt(long nativeObject, long nativeEngine, int targetIndex, short[] tangents, int count);
private static native int nGetVertexCount(long nativeObject);
private static native int nGetCount(long nativeObject);
private static native boolean nHasPositions(long nativeObject);
private static native boolean nHasTangents(long nativeObject);
private static native boolean nIsCustomMorphingEnabled(long nativeObject);
}
92 changes: 83 additions & 9 deletions filament/include/filament/MorphTargetBuffer.h
Original file line number Diff line number Diff line change
Expand Up @@ -31,10 +31,29 @@
namespace filament {

/**
* MorphTargetBuffer is used to hold morphing data (positions and tangents).
* A container for vertex morphing data that supports both automatic and manual morphing.
*
* Both positions and tangents are required.
* MorphTargetBuffer operates in a hybrid model depending on the attribute being morphed:
*
* 1. Automatic for Built-ins (positions/tangents):
* Enable via `withPositions(true)` or `withTangents(true)`. The MorphTargetBuffer will
* allocate internal storage and hold the data for these attributes, which you upload
* via `setPositionsAt()` or `setTangentsAt()`. The framework automatically applies
* the morphing logic in the vertex shader.
*
* 2. Manual for Custom Data (e.g., UVs, colors):
* The MorphTargetBuffer does NOT hold data for custom targets. The user is responsible
* for the full data pipeline:
* - Create and manage a separate `Texture` to hold the morph target data (offsets).
* - In the material, declare a `sampler2d_array` parameter.
* - Bind the `Texture` to the material instance.
* - In the vertex shader, manually call `morphData2`, `morphData3`, or `morphData4`
* with the custom sampler to apply the morphing.
*
* A MorphTargetBuffer object must be associated with a Renderable via
* `RenderableManager::Builder::morphing()` to enable the morphing pipeline for all cases.
*
* @see RenderableManager
*/
class UTILS_PUBLIC MorphTargetBuffer : public FilamentAPI {
struct BuilderDetails;
Expand Down Expand Up @@ -91,6 +110,46 @@ class UTILS_PUBLIC MorphTargetBuffer : public FilamentAPI {
*/
Builder& name(utils::StaticString const& name) noexcept;

/**
* Enables and allocates the built-in buffer for position morphing.
*
* If enabled, `setPositionsAt` can be called to set the position data for each target.
* The vertex position will be morphed automatically without any further actions.
*
* @param enable true to enable, false to disable. Default is true.
* @return A reference to this Builder for chaining calls.
*/
Builder& withPositions(bool enable = true) noexcept;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry I missed this before... we need the java bindings for the new methods.


/**
* Enables and allocates the built-in buffer for tangent/normal morphing.
*
* If enabled, `setTangentsAt` can be called to set the tangent data for each target.
* The vertex position will be morphed automatically without any further actions.
*
* @param enable true to enable, false to disable. Default is true.
* @return A reference to this Builder for chaining calls.
*/
Builder& withTangents(bool enable = true) noexcept;

/**
* Enables the custom morphing pipeline.
*
* When enabled, the `morphData2`, `morphData3`, and `morphData4` helper functions are
* available in the vertex shader. You must provide a 2D array texture containing the morph
* deltas, bind it to a `sampler2DArray` uniform, and call the appropriate `morphData`
* function to apply the morphing to your custom attributes.
*
* Note: Unlike `withPositions` or `withTangents`, this does NOT allocate any internal
* storage. You are responsible for managing the morph data texture.
*
* Custom morphing can be used together with automatic position and/or tangent morphing.
*
* @param enable true to enable, false to disable. Default is false.
* @return A reference to this Builder for chaining calls.
*/
Builder& enableCustomMorphing(bool enable) noexcept;

/**
* Creates the MorphTargetBuffer object and returns a pointer to it.
*
Expand All @@ -110,38 +169,36 @@ class UTILS_PUBLIC MorphTargetBuffer : public FilamentAPI {
/**
* Updates positions for the given morph target.
*
* This method can only be called if the MorphTargetBuffer was built with `withPositions(true)`.
* This is equivalent to the float4 method, but uses 1.0 for the 4th component.
*
* Both positions and tangents must be provided.
*
* @param engine Reference to the filament::Engine associated with this MorphTargetBuffer.
* @param targetIndex the index of morph target to be updated.
* @param positions pointer to at least "count" positions
* @param count number of float3 vectors in positions
* @param offset offset into the target buffer, expressed as a number of float4 vectors
* @see setTangentsAt
* @param offset offset into the target buffer, expressed as a number of float3 vectors
*/
void setPositionsAt(Engine& engine, size_t targetIndex,
math::float3 const* UTILS_NONNULL positions, size_t count, size_t offset = 0);

/**
* Updates positions for the given morph target.
*
* Both positions and tangents must be provided.
* This method can only be called if the MorphTargetBuffer was built with `withPositions(true)`.
*
* @param engine Reference to the filament::Engine associated with this MorphTargetBuffer.
* @param targetIndex the index of morph target to be updated.
* @param positions pointer to at least "count" positions
* @param count number of float4 vectors in positions
* @param offset offset into the target buffer, expressed as a number of float4 vectors
* @see setTangentsAt
*/
void setPositionsAt(Engine& engine, size_t targetIndex,
math::float4 const* UTILS_NONNULL positions, size_t count, size_t offset = 0);

/**
* Updates tangents for the given morph target.
*
* This method can only be called if the MorphTargetBuffer was built with `withTangents(true)`.
* These quaternions must be represented as signed shorts, where real numbers in the [-1,+1]
* range multiplied by 32767.
*
Expand All @@ -150,7 +207,6 @@ class UTILS_PUBLIC MorphTargetBuffer : public FilamentAPI {
* @param tangents pointer to at least "count" tangents
* @param count number of short4 quaternions in tangents
* @param offset offset into the target buffer, expressed as a number of short4 vectors
* @see setPositionsAt
*/
void setTangentsAt(Engine& engine, size_t targetIndex,
math::short4 const* UTILS_NONNULL tangents, size_t count, size_t offset = 0);
Expand All @@ -167,6 +223,24 @@ class UTILS_PUBLIC MorphTargetBuffer : public FilamentAPI {
*/
size_t getCount() const noexcept;

/**
* Returns true if this MorphTargetBuffer has a position buffer.
* @see Builder::withPositions
*/
bool hasPositions() const noexcept;

/**
* Returns true if this MorphTargetBuffer has a tangent buffer.
* @see Builder::withTangents
*/
bool hasTangents() const noexcept;

/**
* Returns true if custom morphing is enabled
* @see Builder::enableCustomMorphing
*/
bool isCustomMorphingEnabled() const noexcept;

protected:
// prevent heap allocation
~MorphTargetBuffer() = default;
Expand Down
Loading
Loading