ArtifactForms/MeshLibCore

View on GitHub
src/main/java/mesh/modifier/CrocodileModifier.java

Summary

Maintainability
A
0 mins
Test Coverage
package mesh.modifier;

import math.Vector3f;
import mesh.Face3D;
import mesh.Mesh3D;
import mesh.conway.ConwayAmboModifier;
import mesh.selection.FaceSelection;

/**
 * The `CrocodileModifier` transforms a 3D mesh by applying the Conway Ambo
 * operation and adding spike-like protrusions to the resulting faces.
 * 
 * <pre>
 * - Ambo Operation: This operation slices off the corners of the original
 * polyhedron by creating slicing planes that pass through the midpoints of
 * edges. This produces new vertices and new polygonal faces, effectively
 * generating a new polyhedral structure.
 * 
 * - Spike Creation: After applying the Ambo operation, spike-like protrusions
 * are added to the centers of these newly created faces. These spikes extend
 * outward, giving the resulting mesh a spiky appearance. This spike effect can
 * be viewed as a variation of the Kis operation.
 * 
 * - Kis-Like Operation: While the traditional Kis operation modifies all faces
 * of a polyhedron, this implementation is limited to targeting only the newly
 * created faces resulting from the Ambo operation. This allows for a more
 * focused and controlled deformation.
 * </pre>
 */
public class CrocodileModifier implements IMeshModifier {

    /**
     * The distance defines how much the spike protrudes outward from the face's
     * center.
     */
    private float distance;

    /** The 3D mesh being modified. */
    private Mesh3D mesh;

    /** Selection utility to track faces created by the Ambo operation. */
    private FaceSelection selection;

    /**
     * Modifies the given 3D mesh by applying the Ambo operation, creating spikes,
     * and removing certain faces to produce the final transformation.
     * 
     * @param mesh The 3D mesh to be transformed.
     * @return The modified 3D mesh.
     */
    @Override
    public Mesh3D modify(Mesh3D mesh) {
        validateMesh(mesh);
        if (mesh.vertices.isEmpty()) {
            return mesh;
        }
        setMesh(mesh);
        applConwayAmboOperation();
        selectAmboFaces();
        createSpikes();
        removeSelectedAmboFaces();
        return mesh;
    }

    /**
     * Creates spike-like protrusions by generating new triangular faces at the
     * centers of selected faces.
     */
    private void createSpikes() {
        int nextIndex = mesh.vertices.size();
        for (Face3D face : selection.getFaces()) {
            mesh.add(calculateSpikeTip(face));
            createSpikeFaces(face, nextIndex);
            nextIndex++;
        }
    }

    /**
     * Calculates the tip of the spike based on the center of the given face and
     * projects it outward along the normal vector scaled by the distance.
     * 
     * @param face The face used to calculate the spike tip.
     * @return The 3D position of the spike tip.
     */
    private Vector3f calculateSpikeTip(Face3D face) {
        Vector3f center = mesh.calculateFaceCenter(face);
        Vector3f normal = mesh.calculateFaceNormal(face);
        if (distance == 0) {
            return center;
        }
        return center.add(normal.mult(distance));
    }

    /**
     * Generates the triangular spike faces connecting edges of a given face to
     * the spike tip.
     * 
     * @param face      The target face to create spikes for.
     * @param nextIndex The index of the newly created spike tip vertex.
     */
    private void createSpikeFaces(Face3D face, int nextIndex) {
        for (int i = 0; i < face.indices.length; i++) {
            int fromIndex = face.indices[i];
            int toIndex = face.indices[(i + 1) % face.indices.length];
            int centerIndex = nextIndex;
            Face3D triangle = new Face3D(fromIndex, toIndex, centerIndex);
            triangle.tag = "spikes";
            mesh.add(triangle);
        }
    }

    /**
     * Validates that the given mesh is not null.
     * 
     * @param mesh The mesh to validate.
     */
    private void validateMesh(Mesh3D mesh) {
        if (mesh == null) {
            throw new IllegalArgumentException("Mesh cannot be null.");
        }
    }

    /**
     * Applies the Conway Ambo operation to the 3D mesh to generate new faces and
     * vertices.
     */
    private void applConwayAmboOperation() {
        new ConwayAmboModifier().modify(mesh);
    }

    /**
     * Selects the faces created by the Ambo operation using their assigned tags.
     */
    private void selectAmboFaces() {
        selection = new FaceSelection(mesh);
        selection.selectByTag("ambo");
    }

    /**
     * Removes all the newly created faces (tagged by the Ambo operation) from the
     * mesh as part of the transformation process.
     */
    private void removeSelectedAmboFaces() {
        mesh.faces.removeAll(selection.getFaces());
    }

    /**
     * Sets the current mesh for transformation operations.
     * 
     * @param mesh The 3D mesh to operate on.
     */
    private void setMesh(Mesh3D mesh) {
        this.mesh = mesh;
    }

    /**
     * Retrieves the distance value, which defines how far spikes protrude.
     * 
     * @return The current distance value.
     */
    public float getDistance() {
        return distance;
    }

    /**
     * Sets the distance value, which controls how far the spike-like protrusions
     * are displaced from their originating face centers.
     * 
     * @param distance The distance value to set.
     */
    public void setDistance(float distance) {
        this.distance = distance;
    }

}