All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.jme3.bullet.objects.PhysicsSoftBody Maven / Gradle / Ivy

There is a newer version: 8.2.0+mt
Show newest version
/*
 * Copyright (c) 2009-2016 jMonkeyEngine
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are
 * met:
 *
 * * Redistributions of source code must retain the above copyright
 *   notice, this list of conditions and the following disclaimer.
 *
 * * Redistributions in binary form must reproduce the above copyright
 *   notice, this list of conditions and the following disclaimer in the
 *   documentation and/or other materials provided with the distribution.
 *
 * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
 *   may be used to endorse or promote products derived from this software
 *   without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
 * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
 * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
 * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */
package com.jme3.bullet.objects;

import com.jme3.bounding.BoundingBox;
import com.jme3.bullet.SoftBodyWorldInfo;
import com.jme3.bullet.collision.PcoType;
import com.jme3.bullet.collision.shapes.CollisionShape;
import com.jme3.bullet.objects.infos.Cluster;
import com.jme3.bullet.objects.infos.SoftBodyConfig;
import com.jme3.bullet.objects.infos.SoftBodyMaterial;
import com.jme3.export.InputCapsule;
import com.jme3.export.JmeExporter;
import com.jme3.export.JmeImporter;
import com.jme3.export.OutputCapsule;
import com.jme3.math.Matrix3f;
import com.jme3.math.Quaternion;
import com.jme3.math.Transform;
import com.jme3.math.Vector3f;
import com.jme3.scene.mesh.IndexBuffer;
import com.jme3.util.BufferUtils;
import com.jme3.util.clone.Cloner;
import com.simsilica.mathd.Matrix3d;
import com.simsilica.mathd.Quatd;
import com.simsilica.mathd.Vec3d;
import java.io.IOException;
import java.nio.Buffer;
import java.nio.ByteBuffer;
import java.nio.FloatBuffer;
import java.nio.IntBuffer;
import java.nio.ShortBuffer;
import java.util.logging.Level;
import java.util.logging.Logger;
import jme3utilities.MeshNormals;
import jme3utilities.Validate;
import jme3utilities.math.MyBuffer;

/**
 * A collision object to simulate a soft body, based on Bullet's btSoftBody. It
 * is modeled as a mesh of nodes. Unlike other collision objects, the
 * CollisionShape is ignored and is typically null.
 *
 * @author dokthar
 */
public class PhysicsSoftBody extends PhysicsBody {
    // *************************************************************************
    // constants and loggers

    /**
     * number of axes in the coordinate system
     */
    final private static int numAxes = 3;
    /**
     * number of vertices per edge
     */
    final private static int vpe = 2;
    /**
     * number of vertices per triangle
     */
    final private static int vpt = 3;
    /**
     * message logger for this class
     */
    final public static Logger logger2
            = Logger.getLogger(PhysicsSoftBody.class.getName());
    /**
     * field names for serialization
     */
    final private static String tagConfig = "config";
    final private static String tagFaceIndices = "faceIndices";
    final private static String tagIndices = "indices";
    final private static String tagIsWorldInfoProtected
            = "isWorldInfoProtected";
    final private static String tagLinkIndices = "linkIndices";
    final private static String tagMaterial = "material";
    final private static String tagNodeLocations = "nodeLocations";
    final private static String tagNodeMasses = "nodeMasses";
    final private static String tagNodeNormals = "nodeNormals";
    final private static String tagNodeVelocities = "nodeVelocities";
    final private static String tagNumClusters = "numClusters";
    final private static String tagPhysicsLocation = "physicsLocation";
    final private static String tagRestLengthScale = "restLengthScale";
    final private static String tagTetraIndices = "tetraIndices";
    final private static String tagWorldInfo = "worldInfo";
    // *************************************************************************
    // fields

    /**
     * false→ world info should be replaced when this body gets added to a
     * space, true→world info should be preserved
     */
    private boolean isWorldInfoProtected = false;
    /**
     * configuration properties of this soft body
     */
    private SoftBodyConfig config;
    /**
     * material properties of this soft body, allocated lazily
     */
    private SoftBodyMaterial material = null;
    /**
     * properties (including gravity) that may be replaced when this body gets
     * added to a space
     */
    private SoftBodyWorldInfo worldInfo;
    // *************************************************************************
    // constructors

    /**
     * Instantiate an empty soft body. The new body is not added to any physics
     * space.
     */
    public PhysicsSoftBody() {
        this.worldInfo = new SoftBodyWorldInfo();
        long infoId = worldInfo.nativeId();
        long bodyId = createEmpty(infoId);
        super.setNativeId(bodyId);
        assert getInternalType(bodyId) == PcoType.soft :
                getInternalType(bodyId);
        logger2.log(Level.FINE, "Created {0}.", this);

        this.config = new SoftBodyConfig(this);
        super.initUserPointer();

        float defaultMargin = CollisionShape.getDefaultMargin();
        setMargin(defaultMargin);

        assert !isInWorld();
        assert isEmpty();
    }
    // *************************************************************************
    // new methods exposed

    /**
     * Add velocity to this entire body.
     *
     * @param velocity the velocity to add (in physics-space coordinates, not
     * null, unaffected)
     */
    public void addVelocity(Vector3f velocity) {
        Validate.finite(velocity, "velocity");

        long objectId = nativeId();
        addVelocity(objectId, velocity);
    }

    /**
     * Add velocity to the indexed node of this body.
     *
     * @param velocity the velocity to add (in physics-space coordinates, not
     * null, unaffected)
     * @param nodeIndex which node to add it to (≥0, <numNodes)
     */
    public void addVelocity(Vector3f velocity, int nodeIndex) {
        Validate.finite(velocity, "velocity");
        int numNodes = countNodes();
        Validate.inRange(nodeIndex, "node index", 0, numNodes - 1);

        long objectId = nativeId();
        addVelocity(objectId, velocity, nodeIndex);
    }

    /**
     * Append faces to this body. A Face is a triangle connecting 3 nodes.
     *
     * @param nodeIndices a face is created for every 3 indices in this buffer
     * (not null, direct, size a multiple of 3)
     */
    public void appendFaces(IndexBuffer nodeIndices) {
        if (!nodeIndices.getBuffer().isDirect()) {
            throw new IllegalArgumentException("The buffer must be direct.");
        }
        if (nodeIndices.size() % vpt != 0) {
            throw new IllegalArgumentException(
                    "The number of indices must be a multiple of 3.");
        }

        long objectId = nativeId();
        int numFaces = nodeIndices.size() / vpt;
        Buffer buffer = nodeIndices.getBuffer();
        if (buffer instanceof ByteBuffer) {
            appendFaces(objectId, numFaces, (ByteBuffer) buffer);
        } else if (buffer instanceof ShortBuffer) {
            appendFaces(objectId, numFaces, (ShortBuffer) buffer);
        } else if (buffer instanceof IntBuffer) {
            appendFaces(objectId, numFaces, (IntBuffer) buffer);
        } else {
            throw new IllegalArgumentException(
                    buffer.getClass().getSimpleName());
        }
    }

    /**
     * Append links to this body. A link is an interaction (or force) between 2
     * nodes.
     *
     * @param nodeIndices a link is created for each pair of indices in this
     * buffer (not null, direct, size a multiple of 2)
     */
    public void appendLinks(IndexBuffer nodeIndices) {
        if (!nodeIndices.getBuffer().isDirect()) {
            throw new IllegalArgumentException("The buffer must be direct.");
        }
        if (nodeIndices.size() % vpe != 0) {
            throw new IllegalArgumentException(
                    "The number of indices must be a multiple of 2.");
        }

        long objectId = nativeId();
        int numLinks = nodeIndices.size() / vpe;
        Buffer buffer = nodeIndices.getBuffer();
        if (buffer instanceof ByteBuffer) {
            appendLinks(objectId, numLinks, (ByteBuffer) buffer);
        } else if (buffer instanceof ShortBuffer) {
            appendLinks(objectId, numLinks, (ShortBuffer) buffer);
        } else if (buffer instanceof IntBuffer) {
            appendLinks(objectId, numLinks, (IntBuffer) buffer);
        } else {
            throw new IllegalArgumentException(
                    buffer.getClass().getSimpleName());
        }
    }

    /**
     * Append nodes to this body, each with mass=1. Nodes provide the shape of a
     * soft body. Each node has its own location, velocity, mass, etcetera.
     *
     * @param nodeLocations a node is created for every 3 floats in this buffer
     * (not null, direct, limit a multiple of 3)
     */
    public void appendNodes(FloatBuffer nodeLocations) {
        Validate.nonNull(nodeLocations, "node locations");
        Validate.require(nodeLocations.isDirect(), "direct buffer");
        Validate.require(nodeLocations.limit() % numAxes == 0,
                "limit a multiple of 3");

        long objectId = nativeId();
        int numNodes = nodeLocations.limit() / numAxes;
        appendNodes(objectId, numNodes, nodeLocations);
    }

    /**
     * Append tetrahedra to this body. A tetrahedron defines a volume between 4
     * nodes.
     *
     * @param tetrahedra a tetrahedron is created for every 4 indices in this
     * buffer (not null, direct, size a multiple of 4)
     */
    public void appendTetras(IndexBuffer tetrahedra) {
        if (!tetrahedra.getBuffer().isDirect()) {
            throw new IllegalArgumentException("The buffer must be direct.");
        }
        if (tetrahedra.size() % 4 != 0) {
            throw new IllegalArgumentException(
                    "The number of indices must be a multiple of 4.");
        }

        long objectId = nativeId();
        int numTetras = tetrahedra.size() / 4;
        Buffer buffer = tetrahedra.getBuffer();
        if (buffer instanceof ByteBuffer) {
            appendTetras(objectId, numTetras, (ByteBuffer) buffer);
        } else if (buffer instanceof ShortBuffer) {
            appendTetras(objectId, numTetras, (ShortBuffer) buffer);
        } else if (buffer instanceof IntBuffer) {
            appendTetras(objectId, numTetras, (IntBuffer) buffer);
        } else {
            throw new IllegalArgumentException(
                    buffer.getClass().getSimpleName());
        }
    }

    /**
     * Apply a force that acts uniformly across the entire body.
     *
     * @param force the force to add (in physics-space coordinates, not null,
     * unaffected)
     */
    public void applyForce(Vector3f force) {
        Validate.finite(force, "force");

        long objectId = nativeId();
        addForce(objectId, force);
    }

    /**
     * Apply a force to the indexed node of this body.
     *
     * @param force the force to add (in physics-space coordinates, not null,
     * unaffected)
     * @param nodeIndex which node to add it to (≥0, <numNodes)
     */
    public void applyForce(Vector3f force, int nodeIndex) {
        Validate.finite(force, "force");
        int numNodes = countNodes();
        Validate.inRange(nodeIndex, "node index", 0, numNodes - 1);

        long objectId = nativeId();
        addForce(objectId, force, nodeIndex);
    }

    /**
     * Rotate this body.
     *
     * @param rotation the rotation to apply (not null, unaffected)
     */
    public void applyRotation(Quaternion rotation) {
        Validate.nonNull(rotation, "rotation");

        long objectId = nativeId();
        applyPhysicsRotation(objectId, rotation);
    }

    /**
     * Scale this body.
     *
     * @param factors the scale factor to apply to each axis (not null,
     * unaffected)
     */
    public void applyScale(Vector3f factors) {
        Validate.finite(factors, "factors");

        long objectId = nativeId();
        applyPhysicsScale(objectId, factors);
    }

    /**
     * Transform this body.
     *
     * @param transform the transform to apply (not null, unaffected)
     */
    public void applyTransform(Transform transform) {
        Validate.nonNull(transform, "transform");

        long objectId = nativeId();
        applyPhysicsTransform(objectId, transform);
    }

    /**
     * Translate this body.
     *
     * @param offset the translation to apply to each axis (not null,
     * unaffected)
     */
    public void applyTranslation(Vector3f offset) {
        Validate.finite(offset, "offset");

        long objectId = nativeId();
        applyPhysicsTranslate(objectId, offset);
    }

    /**
     * Copy the center location of the indexed cluster.
     *
     * @param clusterIndex which cluster (≥0, <numClusters)
     * @param storeResult storage for the result (modified if not null)
     * @return a location vector (in physics-space coordinates, either
     * storeResult or a new vector)
     */
    public Vector3f clusterCenter(int clusterIndex, Vector3f storeResult) {
        int numClusters = countClusters();
        Validate.inRange(clusterIndex, "cluster index", 0, numClusters - 1);
        Vector3f result = (storeResult == null) ? new Vector3f() : storeResult;

        long objectId = nativeId();
        getClusterCenter(objectId, clusterIndex, result);

        return result;
    }

    /**
     * Copy the center-of-mass locations of all clusters in this body.
     *
     * @param storeResult storage for the result (direct, modified) or null
     * @return a direct buffer containing 3 floats per cluster (in physics-space
     * coordinates, either storeResult or a new buffer)
     */
    public FloatBuffer copyClusterCenters(FloatBuffer storeResult) {
        if (storeResult != null && !storeResult.isDirect()) {
            throw new IllegalArgumentException("The buffer must be direct.");
        }
        int numFloats = numAxes * countClusters();
        FloatBuffer result = MyBuffer.ensureCapacity(numFloats, storeResult);

        if (numFloats != 0) {
            long objectId = nativeId();
            getClustersPositions(objectId, result);
        }

        return result;
    }

    /**
     * Copy the masses of all clusters in this body.
     *
     * @param storeResult storage for the result (direct, modified) or null
     * @return a direct buffer containing a float value per cluster (either
     * storeResult or a new buffer)
     */
    public FloatBuffer copyClusterMasses(FloatBuffer storeResult) {
        if (storeResult != null && !storeResult.isDirect()) {
            throw new IllegalArgumentException("The buffer must be direct.");
        }
        int numFloats = countClusters();
        FloatBuffer result = MyBuffer.ensureCapacity(numFloats, storeResult);

        if (numFloats != 0) {
            long objectId = nativeId();
            getClustersMasses(objectId, result);
        }

        return result;
    }

    /**
     * Copy the (linear) velocities of all clusters in this body.
     *
     * @param storeResult storage for the result (direct, modified) or null
     * @return a direct buffer containing 3 floats per cluster (in physics-space
     * coordinates, either storeResult or a new buffer)
     */
    public FloatBuffer copyClusterVelocities(FloatBuffer storeResult) {
        if (storeResult != null && !storeResult.isDirect()) {
            throw new IllegalArgumentException("The buffer must be direct.");
        }
        int numFloats = numAxes * countClusters();
        FloatBuffer result = MyBuffer.ensureCapacity(numFloats, storeResult);

        if (numFloats != 0) {
            long objectId = nativeId();
            getClustersLinearVelocities(objectId, result);
        }

        return result;
    }

    /**
     * Copy the node indices of all faces in this body.
     *
     * @param storeResult storage for the result (direct, modified) or null
     * @return a direct buffer containing 3 indices per face (either storeResult
     * or a new buffer)
     */
    public IntBuffer copyFaces(IntBuffer storeResult) {
        int numInts = vpt * countFaces();
        IntBuffer result;
        if (storeResult == null) {
            result = BufferUtils.createIntBuffer(numInts);
        } else {
            assert storeResult.isDirect();
            assert storeResult.capacity() == numInts;
            result = storeResult;
        }

        if (numInts != 0) {
            long objectId = nativeId();
            getFacesIndexes(objectId, result);
        }

        return result;
    }

    /**
     * Copy the node indices of all links in this body.
     *
     * @param storeResult storage for the result (direct, modified) or null
     * @return a direct buffer containing 2 indices per link (either storeResult
     * or a new buffer)
     */
    public IntBuffer copyLinks(IntBuffer storeResult) {
        int numInts = vpe * countLinks();
        IntBuffer result;
        if (storeResult == null) {
            result = BufferUtils.createIntBuffer(numInts);
        } else {
            assert storeResult.isDirect();
            assert storeResult.capacity() == numInts;
            result = storeResult;
        }

        if (numInts != 0) {
            long objectId = nativeId();
            getLinksIndexes(objectId, result);
        }

        return result;
    }

    /**
     * Copy the locations of all nodes in this body.
     *
     * @param storeResult storage for the result (direct, modified) or null
     * @return a direct buffer containing 3 floats per node (in physics-space
     * coordinates, either storeResult or a new buffer)
     */
    public FloatBuffer copyLocations(FloatBuffer storeResult) {
        if (storeResult != null && !storeResult.isDirect()) {
            throw new IllegalArgumentException("The buffer must be direct.");
        }
        int numFloats = numAxes * countNodes();
        FloatBuffer result = MyBuffer.ensureCapacity(numFloats, storeResult);

        if (numFloats != 0) {
            long objectId = nativeId();
            getNodesPositions(objectId, result);
        }

        return result;
    }

    /**
     * Copy the masses of all nodes in this body.
     *
     * @param storeResult storage for the result (direct, modified) or null
     * @return a direct buffer containing a float value per node (either
     * storeResult or a new buffer)
     */
    public FloatBuffer copyMasses(FloatBuffer storeResult) {
        if (storeResult != null && !storeResult.isDirect()) {
            throw new IllegalArgumentException("The buffer must be direct.");
        }
        int numFloats = countNodes();
        FloatBuffer result = MyBuffer.ensureCapacity(numFloats, storeResult);

        if (numFloats != 0) {
            long objectId = nativeId();
            getMasses(objectId, result);
        }

        return result;
    }

    /**
     * Copy the normal vectors of all nodes in this body.
     *
     * @param storeResult storage for the result (direct, modified) or null
     * @return a direct buffer containing 3 floats per node (in physics-space
     * coordinates, either storeResult or a new buffer)
     */
    public FloatBuffer copyNormals(FloatBuffer storeResult) {
        if (storeResult != null && !storeResult.isDirect()) {
            throw new IllegalArgumentException("The buffer must be direct.");
        }
        int numFloats = numAxes * countNodes();
        FloatBuffer result = MyBuffer.ensureCapacity(numFloats, storeResult);

        if (numFloats != 0) {
            long objectId = nativeId();
            getNodesNormals(objectId, result);
        }

        return result;
    }

    /**
     * Copy the node indices of all tetrahedra in this body.
     *
     * @param storeResult storage for the result (direct, modified) or null
     * @return a direct buffer containing 4 indices per tetrahedron (either
     * storeResult or a new buffer)
     */
    public IntBuffer copyTetras(IntBuffer storeResult) {
        int numInts = 4 * countTetras();
        IntBuffer result;
        if (storeResult == null) {
            result = BufferUtils.createIntBuffer(numInts);
        } else {
            assert storeResult.isDirect();
            assert storeResult.capacity() == numInts;
            result = storeResult;
        }

        if (numInts != 0) {
            long objectId = nativeId();
            getTetrasIndexes(objectId, result);
        }

        return result;
    }

    /**
     * Copy the (linear) velocities of all nodes in this body.
     *
     * @param storeResult storage for the result (direct, modified) or null
     * @return a direct buffer containing 3 floats per node (in physics-space
     * coordinates, either storeResult or a new buffer)
     */
    public FloatBuffer copyVelocities(FloatBuffer storeResult) {
        if (storeResult != null && !storeResult.isDirect()) {
            throw new IllegalArgumentException("The buffer must be direct.");
        }
        int numFloats = numAxes * countNodes();
        FloatBuffer result = MyBuffer.ensureCapacity(numFloats, storeResult);

        if (numFloats != 0) {
            long objectId = nativeId();
            getNodesVelocities(objectId, result);
        }

        return result;
    }

    /**
     * Count the clusters in this body.
     *
     * @return the number of clusters (≥0)
     */
    final public int countClusters() {
        long objectId = nativeId();
        int result = getClusterCount(objectId);

        return result;
    }

    /**
     * Count the faces in this body.
     *
     * @return the number of faces (≥0)
     */
    final public int countFaces() {
        long objectId = nativeId();
        int result = getNbFaces(objectId);

        return result;
    }

    /**
     * Count the links in this body.
     *
     * @return the number of links (≥0)
     */
    final public int countLinks() {
        long objectId = nativeId();
        int result = getNbLinks(objectId);

        return result;
    }

    /**
     * Count the nodes in this body.
     *
     * @return the number of nodes (≥0)
     */
    final public int countNodes() {
        long objectId = nativeId();
        int result = getNbNodes(objectId);

        return result;
    }

    /**
     * Count the nodes in the indexed cluster.
     *
     * @param clusterIndex which cluster (≥0, <numClusters)
     * @return the number of nodes (≥0)
     */
    public int countNodesInCluster(int clusterIndex) {
        int numClusters = countClusters();
        Validate.inRange(clusterIndex, "cluster index", 0, numClusters - 1);

        long objectId = nativeId();
        int result = countNodesInCluster(objectId, clusterIndex);

        return result;
    }

    /**
     * Count the pinned nodes in this body.
     *
     * @return the number of pinned nodes (≥0)
     */
    final public int countPinnedNodes() {
        long objectId = nativeId();
        int result = getNbPinnedNodes(objectId);

        return result;
    }

    /**
     * Count the tetrahedra in this body.
     *
     * @return the number of tetrahedra (≥0)
     */
    final public int countTetras() {
        long objectId = nativeId();
        int result = getNbTetras(objectId);

        return result;
    }

    /**
     * Cut a pre-existing link in this body. TODO clarify the semantics
     *
     * @param nodeIndex0 the index of a node in the link (≥0,<numNodes)
     * @param nodeIndex1 the index of the other node in the link
     * (≥0,<numNodes)
     * @param cutLocation where to cut the link
     * @return true if successful, otherwise false
     */
    public boolean cutLink(int nodeIndex0, int nodeIndex1, float cutLocation) {
        int numNodes = countNodes();
        Validate.inRange(nodeIndex0, "node index 0", 0, numNodes - 1);
        Validate.inRange(nodeIndex1, "node index 1", 0, numNodes - 1);

        long objectId = nativeId();
        boolean result = cutLink(objectId, nodeIndex0, nodeIndex1, cutLocation);

        return result;
    }

    /**
     * Generate bending constraints based on hops in the adjacency graph. This
     * may increase the number of links.
     *
     * @param numHops (in links, ≥2)
     * @param material the material for appending links (not null)
     */
    public void generateBendingConstraints(int numHops,
            SoftBodyMaterial material) {
        Validate.inRange(numHops, "number of hops", 2, Integer.MAX_VALUE);

        long objectId = nativeId();
        long materialId = material.nativeId();
        generateBendingConstraints(objectId, numHops, materialId);
    }

    /**
     * Generate one cluster per tetrahedron (or one per face if there are no
     * tetrahedra). Any pre-existing clusters are released.
     */
    public void generateClusters() {
        long objectId = nativeId();
        generateClusters(objectId, 0, 8_192);
    }

    /**
     * Generate clusters (K-mean). Any pre-existing clusters are released.
     *
     * @param k (≥1, <numNodes)
     * @param maxIterations the maximum number of iterations (>0,
     * default=8192)
     */
    public void generateClusters(int k, int maxIterations) {
        int numNodes = countNodes();
        Validate.inRange(k, "k", 1, numNodes);
        Validate.positive(maxIterations, "maximum number of iterations");

        long objectId = nativeId();
        generateClusters(objectId, k, maxIterations);
    }

    /**
     * Read the specified parameter of the indexed cluster.
     *
     * @param parameter which parameter to read (not null)
     * @param clusterIndex which cluster (≥0, <numClusters)
     * @return the coefficient value
     */
    public float get(Cluster parameter, int clusterIndex) {
        int numClusters = countClusters();
        Validate.inRange(clusterIndex, "cluster index", 0, numClusters - 1);

        float result;
        long objectId = nativeId();
        switch (parameter) {
            case AngularDamping:
                result = getClusterAngularDamping(objectId, clusterIndex);
                break;
            case LinearDamping:
                result = getClusterLinearDamping(objectId, clusterIndex);
                break;
            case Matching:
                result = getClusterMatching(objectId, clusterIndex);
                break;
            case MaxSelfImpulse:
                result = getClusterMaxSelfImpulse(objectId, clusterIndex);
                break;
            case NodeDamping:
                result = getClusterNodeDamping(objectId, clusterIndex);
                break;
            case SelfImpulse:
                result = getClusterSelfImpulse(objectId, clusterIndex);
                break;
            default:
                throw new IllegalArgumentException(parameter.toString());
        }

        return result;
    }

    /**
     * Access the SoftBodyConfig of this body.
     *
     * @return the pre-existing instance (not null)
     */
    public SoftBodyConfig getSoftConfig() {
        return config;
    }

    /**
     * Access the Material of this body.
     *
     * @return the instance associated with this body (not null)
     */
    public SoftBodyMaterial getSoftMaterial() {
        if (material == null) {
            this.material = new SoftBodyMaterial(this);
        }

        return material;
    }

    /**
     * Access the world info.
     *
     * @return the pre-existing instance (not null)
     */
    public SoftBodyWorldInfo getWorldInfo() {
        assert worldInfo != null;
        assert worldInfo.nativeId() == getSoftBodyWorldInfo(nativeId());
        return worldInfo;
    }

    /**
     * Test whether collisions are allowed between this body and the identified
     * collision object. Disallowed collisions may result from anchors.
     *
     * @param pcoId the native ID of the other collision object (not zero)
     * @return true if allowed, otherwise false
     */
    public boolean isCollisionAllowed(long pcoId) {
        Validate.nonZero(pcoId, "collision object ID");

        long objectId = nativeId();
        boolean result = isCollisionAllowed(objectId, pcoId);

        return result;
    }

    /**
     * Test whether this body is empty.
     *
     * @return true if empty, otherwise false
     */
    final public boolean isEmpty() {
        boolean result = countNodes() == 0
                && countFaces() == 0
                && countLinks() == 0
                && countTetras() == 0
                && countJoints() == 0
                && countClusters() == 0;
        return result;
    }

    /**
     * Test whether this body's world info should be replaced when added to a
     * space.
     *
     * @return false if the info should be replaced, true if it should be
     * preserved
     */
    public boolean isWorldInfoProtected() {
        boolean result = isWorldInfoProtected;
        return result;
    }

    /**
     * List all nodes in the indexed cluster.
     *
     * @param clusterIndex which cluster (≥0, <numClusters)
     * @param storeResult storage for the result (direct, modified) or null
     * @return a direct buffer containing node indices (either storeResult or a
     * new buffer)
     */
    public IntBuffer listNodesInCluster(int clusterIndex,
            IntBuffer storeResult) {
        int numClusters = countClusters();
        Validate.inRange(clusterIndex, "cluster index", 0, numClusters - 1);
        int numNodes = countNodesInCluster(clusterIndex);
        IntBuffer resultBuffer;
        if (storeResult == null) {
            resultBuffer = BufferUtils.createIntBuffer(numNodes);
        } else {
            assert storeResult.isDirect();
            assert storeResult.capacity() == numNodes;
            resultBuffer = storeResult;
        }

        long objectId = nativeId();
        listNodesInCluster(objectId, clusterIndex, resultBuffer);

        return resultBuffer;
    }

    /**
     * Read the collision margin of this body.
     *
     * @return the margin distance (in physics-space units, >0)
     */
    public float margin() {
        long objectId = nativeId();
        float result = getMargin(objectId);

        return result;
    }

    /**
     * Copy the location of the indexed node.
     *
     * @param nodeIndex which node (≥0, <numNodes)
     * @param storeResult storage for the result (modified if not null)
     * @return a location vector (in physics-space coordinates, either
     * storeResult or a new vector)
     */
    public Vector3f nodeLocation(int nodeIndex, Vector3f storeResult) {
        int numNodes = countNodes();
        Validate.inRange(nodeIndex, "node index", 0, numNodes - 1);
        Vector3f result = (storeResult == null) ? new Vector3f() : storeResult;

        long objectId = nativeId();
        getNodeLocation(objectId, nodeIndex, result);

        return result;
    }

    /**
     * Read the mass of the indexed node.
     *
     * @param nodeIndex which node to read (≥0, <numNodes)
     * @return the mass of the node (≥0)
     */
    public float nodeMass(int nodeIndex) {
        int numNodes = countNodes();
        Validate.inRange(nodeIndex, "node index", 0, numNodes - 1);

        long objectId = nativeId();
        float result = getMass(objectId, nodeIndex);

        return result;
    }

    /**
     * Copy the normal vector of the indexed node.
     *
     * @param nodeIndex which node (≥0, <numNodes)
     * @param storeResult storage for the result (modified if not null)
     * @return a normal vector (in physics-space coordinates, either storeResult
     * or a new vector)
     */
    public Vector3f nodeNormal(int nodeIndex, Vector3f storeResult) {
        int numNodes = countNodes();
        Validate.inRange(nodeIndex, "node index", 0, numNodes - 1);
        Vector3f result = (storeResult == null) ? new Vector3f() : storeResult;

        long objectId = nativeId();
        getNodeNormal(objectId, nodeIndex, result);

        return result;
    }

    /**
     * Copy the velocity of the indexed node.
     *
     * @param nodeIndex which node (≥0, <numNodes)
     * @param storeResult storage for the result (modified if not null)
     * @return a velocity vector (in physics-space coordinates, either
     * storeResult or a new vector)
     */
    public Vector3f nodeVelocity(int nodeIndex, Vector3f storeResult) {
        int numNodes = countNodes();
        Validate.inRange(nodeIndex, "node index", 0, numNodes - 1);
        Vector3f result = (storeResult == null) ? new Vector3f() : storeResult;

        long objectId = nativeId();
        getNodeVelocity(objectId, nodeIndex, result);

        return result;
    }

    /**
     * Randomize constraints to reduce solver bias.
     */
    public void randomizeConstraints() {
        long objectId = nativeId();
        randomizeConstraints(objectId);
    }

    /**
     * Release all clusters.
     */
    public void releaseAllClusters() {
        long objectId = nativeId();
        releaseClusters(objectId);
    }

    /**
     * Release the indexed cluster.
     *
     * @param clusterIndex which cluster to release (≥0, <numClusters)
     */
    public void releaseCluster(int clusterIndex) {
        int numClusters = countClusters();
        Validate.inRange(clusterIndex, "cluster index", 0, numClusters - 1);

        long objectId = nativeId();
        releaseCluster(objectId, clusterIndex);
    }

    /**
     * Set the resting lengths of all links to their current lengths.
     */
    public void resetRestingLengths() {
        long objectId = nativeId();
        resetLinkRestLengths(objectId);
    }

    /**
     * Read the scale factor for resting lengths.
     *
     * @return the scale factor
     */
    public float restingLengthsScale() {
        long objectId = nativeId();
        float result = getRestLengthScale(objectId);

        return result;
    }

    /**
     * Alter the specified parameter of the indexed cluster.
     *
     * @param parameter which parameter to alter (not null)
     * @param clusterIndex which cluster (≥0, <numClusters)
     * @param value the desired value
     */
    public void set(Cluster parameter, int clusterIndex, float value) {
        int numClusters = countClusters();
        Validate.inRange(clusterIndex, "cluster index", 0, numClusters - 1);

        long objectId = nativeId();
        switch (parameter) {
            case AngularDamping:
                setClusterAngularDamping(objectId, clusterIndex, value);
                break;
            case LinearDamping:
                setClusterLinearDamping(objectId, clusterIndex, value);
                break;
            case Matching:
                setClusterMatching(objectId, clusterIndex, value);
                break;
            case MaxSelfImpulse:
                setClusterMaxSelfImpulse(objectId, clusterIndex, value);
                break;
            case NodeDamping:
                setClusterNodeDamping(objectId, clusterIndex, value);
                break;
            case SelfImpulse:
                setClusterSelfImpulse(objectId, clusterIndex, value);
                break;
            default:
                throw new IllegalArgumentException(parameter.toString());
        }
    }

    /**
     * Alter the collision margin of this body.
     *
     * @param margin the desired margin distance (in physics-space units, >0)
     */
    final public void setMargin(float margin) {
        Validate.positive(margin, "margin");

        long objectId = nativeId();
        setMargin(objectId, margin);
    }

    /**
     * Alter the total mass for this body, distributing it based on the area of
     * each face.
     *
     * @param totalMass the desired total mass (>0)
     */
    public void setMassByArea(float totalMass) {
        Validate.positive(totalMass, "total mass");

        long objectId = nativeId();
        setTotalMass(objectId, totalMass, true);
    }

    /**
     * Alter the total mass for this body, distributing it to nodes in
     * proportion to their current masses.
     *
     * @param totalMass the desired total mass (>0, default=numNodes)
     */
    public void setMassByCurrent(float totalMass) {
        Validate.positive(totalMass, "total mass");

        long objectId = nativeId();
        setTotalMass(objectId, totalMass, false);
    }

    /**
     * Alter the masses of all nodes.
     *
     * @param masses a direct buffer containing the desired masses (not null,
     * all elements ≥0)
     */
    public void setMasses(FloatBuffer masses) {
        Validate.nonNull(masses, "masses");
        if (!masses.isDirect()) {
            throw new IllegalArgumentException("The buffer must be direct.");
        }

        long objectId = nativeId();
        setMasses(objectId, masses);
    }

    /**
     * Alter the total mass of this body, weighted by volume.
     *
     * @param density the desired density (>0)
     */
    public void setMassFromDensity(float density) {
        Validate.positive(density, "density");

        long objectId = nativeId();
        setTotalDensity(objectId, density);
    }

    /**
     * Alter the mass of the indexed node.
     *
     * @param nodeIndex which node to modify (≥0, <numNodes)
     * @param mass the desired mass (≥0, default=1)
     */
    public void setNodeMass(int nodeIndex, float mass) {
        int numNodes = countNodes();
        Validate.inRange(nodeIndex, "node index", 0, numNodes - 1);
        Validate.nonNegative(mass, "mass");

        long objectId = nativeId();
        setMass(objectId, nodeIndex, mass);
    }

    /**
     * Alter the velocity of the indexed node.
     *
     * @param nodeIndex which node to modify (≥0, <numNodes)
     * @param velocity the desired velocity vector (not null, unaffected)
     */
    public void setNodeVelocity(int nodeIndex, Vector3f velocity) {
        int numNodes = countNodes();
        Validate.inRange(nodeIndex, "node index", 0, numNodes - 1);
        Validate.finite(velocity, "velocity");

        long objectId = nativeId();
        setNodeVelocity(objectId, nodeIndex, velocity);
    }

    /**
     * Alter the normal vectors of all nodes.
     *
     * @param normals a direct buffer containing the desired normals (in
     * physics-space coordinates, not null, unaffected)
     */
    public void setNormals(FloatBuffer normals) {
        Validate.nonNull(normals, "normals");
        if (!normals.isDirect()) {
            throw new IllegalArgumentException("The buffer must be direct.");
        }

        long objectId = nativeId();
        setNormals(objectId, normals);
    }

    /**
     * Directly relocate the center of this body's bounding box.
     *
     * @param location the desired location (in physics-space coordinates, not
     * null, unaffected)
     */
    public void setPhysicsLocationDp(Vec3d location) {
        Validate.nonNull(location, "location");

        long objectId = nativeId();
        setPhysicsLocationDp(objectId, location);
    }

    /**
     * Set the "default pose" (lowest energy state) of this body based on its
     * current pose.
     *
     * @param setVolumePose true→alter the volume pose, false→ don't
     * alter it
     * @param setFramePose true→alter the frame pose, false→ don't
     * alter it
     */
    public void setPose(boolean setVolumePose, boolean setFramePose) {
        long objectId = nativeId();
        setPose(objectId, setVolumePose, setFramePose);
    }

    /**
     * Alter whether this body's world info should be replaced when the body
     * gets added to a space.
     *
     * @param newState true to preserve the world info, false to allow it to be
     * replaced (default=false)
     */
    public void setProtectWorldInfo(boolean newState) {
        this.isWorldInfoProtected = newState;
    }

    /**
     * Alter the scale factor for resting lengths.
     *
     * @param scale the desired scale factor
     */
    public void setRestingLengthScale(float scale) {
        long objectId = nativeId();
        setRestLengthScale(objectId, scale);
    }

    /**
     * Alter the velocities of all nodes.
     *
     * @param velocities a direct buffer containing the desired velocities (in
     * physics-space coordinates, not null, unaffected)
     */
    public void setVelocities(FloatBuffer velocities) {
        Validate.nonNull(velocities, "velocities");
        if (!velocities.isDirect()) {
            throw new IllegalArgumentException("The buffer must be direct.");
        }

        long objectId = nativeId();
        setVelocities(objectId, velocities);
    }

    /**
     * Alter the velocities of all nodes to make them identical.
     *
     * @param velocity the desired velocity vector (in physics-space
     * coordinates, not null, unaffected)
     */
    public void setVelocity(Vector3f velocity) {
        Validate.finite(velocity, "velocity");

        long objectId = nativeId();
        setVelocity(objectId, velocity);
    }

    /**
     * Set volume density (using tetrahedra) TODO clarify semantics
     *
     * @param density the desired density
     */
    public void setVolumeDensity(float density) {
        long objectId = nativeId();
        setVolumeDensity(objectId, density);
    }

    /**
     * Set volume mass (using tetrahedra) TODO clarify semantics
     *
     * @param mass the desired mass
     */
    public void setVolumeMass(float mass) {
        long objectId = nativeId();
        setVolumeMass(objectId, mass);
    }

    /**
     * Alter the wind velocity.
     *
     * @param velocity the desired velocity vector (in physics-space
     * coordinates, not null, unaffected)
     */
    public void setWindVelocity(Vector3f velocity) {
        Validate.finite(velocity, "velocity");

        long objectId = nativeId();
        setWindVelocity(objectId, velocity);
    }

    /**
     * Replace the world info of this body.
     * 

* Invoke this method after adding the body to a space. Adding a * body to a space may replace its world info. * * @param worldInfo the desired SoftBodyWorldInfo (not null, alias created) */ public void setWorldInfo(SoftBodyWorldInfo worldInfo) { if (!isInWorld()) { logger2.warning("The body is not in any space."); } long objectId = nativeId(); long worldInfoId = worldInfo.nativeId(); setSoftBodyWorldInfo(objectId, worldInfoId); this.worldInfo = worldInfo; } /** * Calculate the volume of this body. * * @return the total volume (in cubic physics-space units, ≥0) */ public float volume() { long objectId = nativeId(); float result = getVolume(objectId); return result; } /** * Copy the wind velocity. * * @param storeResult storage for the result (modified if not null) * @return a velocity vector (in physics-space coordinates, either * storeResult or a new vector) */ public Vector3f windVelocity(Vector3f storeResult) { Vector3f result = (storeResult == null) ? new Vector3f() : storeResult; long objectId = nativeId(); getWindVelocity(objectId, result); return result; } // ************************************************************************* // new protected methods /** * Destroy the pre-existing btSoftBody (if any) except for its worldInfo. */ protected void destroySoftBody() { if (hasAssignedNativeObject()) { logger2.log(Level.FINE, "Destroying {0}.", this); unassignNativeObject(); } this.material = null; this.config = null; } /** * Reinitialize the btSoftBody to the default values. */ protected void initDefault() { long objectId = nativeId(); initDefault(objectId); } /** * Create an empty {@code btSoftBody} for this PhysicsSoftBody, using the * pre-existing worldInfo. The pre-existing btSoftBody (if any) will be * destroyed. */ protected void newEmptySoftBody() { destroySoftBody(); long infoId = worldInfo.nativeId(); long objectId = createEmpty(infoId); setNativeId(objectId); assert getInternalType(objectId) == PcoType.soft : getInternalType(objectId); logger2.log(Level.FINE, "Created {0}.", this); this.config = new SoftBodyConfig(this); initUserPointer(); assert !isInWorld(); assert countNodes() == 0; assert countFaces() == 0; assert countLinks() == 0; assert countTetras() == 0; assert countClusters() == 0; } // ************************************************************************* // PhysicsBody methods /** * Calculate the axis-aligned bounding box for this body. * * @param storeResult storage for the result (modified if not null) * @return a bounding box (in physics-space coordinates, either storeResult * or a new instance) */ @Override public BoundingBox boundingBox(BoundingBox storeResult) { BoundingBox result = (storeResult == null) ? new BoundingBox() : storeResult; Vector3f minima = new Vector3f(); // TODO garbage Vector3f maxima = new Vector3f(); long objectId = nativeId(); getBounds(objectId, minima, maxima); result.setMinMax(minima, maxima); return result; } /** * Callback from {@link com.jme3.util.clone.Cloner} to convert this * shallow-cloned body into a deep-cloned one, using the specified Cloner * and original to resolve copied fields. * * @param cloner the Cloner that's cloning this body (not null) * @param original the instance from which this body was shallow-cloned (not * null, unaffected) */ @Override public void cloneFields(Cloner cloner, Object original) { assert !hasAssignedNativeObject(); PhysicsSoftBody old = (PhysicsSoftBody) original; assert old != this; assert old.hasAssignedNativeObject(); super.cloneFields(cloner, original); if (hasAssignedNativeObject()) { return; } this.worldInfo = cloner.clone(old.worldInfo); long infoId = worldInfo.nativeId(); long objectId = createEmpty(infoId); setNativeId(objectId); assert getInternalType(objectId) == PcoType.soft : getInternalType(objectId); logger2.log(Level.FINE, "Created {0}.", this); this.config = new SoftBodyConfig(this); initUserPointer(); cloneIgnoreList(cloner, old); copyPcoProperties(old); config.copyAll(old.config); this.material = cloner.clone(old.material); FloatBuffer floats = old.copyLocations(null); appendNodes(floats); old.copyNormals(floats); setNormals(floats); old.copyVelocities(floats); setVelocities(floats); FloatBuffer masses = old.copyMasses(null); setMasses(masses); IntBuffer faces = old.copyFaces(null); IndexBuffer faceIndices = IndexBuffer.wrapIndexBuffer(faces); appendFaces(faceIndices); IntBuffer links = old.copyLinks(null); IndexBuffer linkIndices = IndexBuffer.wrapIndexBuffer(links); appendLinks(linkIndices); IntBuffer tetras = old.copyTetras(null); IndexBuffer tetraIndices = IndexBuffer.wrapIndexBuffer(tetras); appendLinks(tetraIndices); assert countClusters() == 0 : countClusters(); int numClusters = old.countClusters(); for (int clusterIndex = 0; clusterIndex < numClusters; ++clusterIndex) { IntBuffer nodeIndices = old.listNodesInCluster(clusterIndex, null); int numNodesInCluster = nodeIndices.capacity(); appendCluster(objectId, numNodesInCluster, nodeIndices); for (Cluster clusterParameter : Cluster.values()) { float value = old.get(clusterParameter, clusterIndex); set(clusterParameter, clusterIndex, value); } } finishClusters(objectId); assert countClusters() == numClusters : countClusters(); // Soft joints require clusters; clone them after appending clusters. cloneJoints(cloner, old); } /** * Copy this body's gravitational acceleration. * * @param storeResult storage for the result (modified if not null) * @return an acceleration vector (in physics-space coordinates, either * storeResult or a new vector, not null) */ @Override public Vector3f getGravity(Vector3f storeResult) { SoftBodyWorldInfo info = getWorldInfo(); Vector3f result = info.copyGravity(storeResult); return result; } /** * Determine the total mass of this body. * * @return the total mass (≥0) */ @Override public float getMass() { long objectId = nativeId(); float totalMass = getTotalMass(objectId); assert totalMass >= 0f : totalMass; return totalMass; } /** * Locate the center of this body's axis-aligned bounding box. * * @param storeResult storage for the result (modified if not null) * @return a location vector (in physics-space coordinates, either * storeResult or a new instance, not null) */ @Override public Vector3f getPhysicsLocation(Vector3f storeResult) { Vector3f result = (storeResult == null) ? new Vector3f() : storeResult; long objectId = nativeId(); getPhysicsLocation(objectId, result); assert Vector3f.isValidVector(result) : result; return result; } /** * Locate the center of this body's axis-aligned bounding box. * * @param storeResult storage for the result (modified if not null) * @return a location vector (in physics-space coordinates, either * storeResult or a new instance, not null, finite) */ @Override public Vec3d getPhysicsLocationDp(Vec3d storeResult) { Vec3d result = (storeResult == null) ? new Vec3d() : storeResult; long objectId = nativeId(); getPhysicsLocationDp(objectId, result); assert result.isFinite() : result; return result; } /** * Copy the orientation (rotation) of this body to a Quaternion. * * @param storeResult storage for the result (modified if not null) * @return an identity quaternion (either storeResult or a new instance, not * null) */ @Override public Quaternion getPhysicsRotation(Quaternion storeResult) { Quaternion result = (storeResult == null) ? new Quaternion() : storeResult; result.loadIdentity(); return result; } /** * Copy the orientation (rotation) of this body to a Quatd. * * @param storeResult storage for the result (modified if not null) * @return an identity quaternion (either storeResult or a new instance, not * null) */ @Override public Quatd getPhysicsRotationDp(Quatd storeResult) { Quatd result; if (storeResult == null) { result = new Quatd(); } else { result = storeResult.set(0., 0., 0., 1.); } return result; } /** * Copy the orientation of this body (the basis of its local coordinate * system) to a Matrix3f. * * @param storeResult storage for the result (modified if not null) * @return an identity matrix (either storeResult or a new instance, not * null) */ @Override public Matrix3f getPhysicsRotationMatrix(Matrix3f storeResult) { Matrix3f result = (storeResult == null) ? new Matrix3f() : storeResult; result.loadIdentity(); return result; } /** * Copy the orientation of this body (the basis of its local coordinate * system) to a Matrix3d. * * @param storeResult storage for the result (modified if not null) * @return an identity matrix (either storeResult or a new instance, not * null) */ @Override public Matrix3d getPhysicsRotationMatrixDp(Matrix3d storeResult) { Matrix3d result; if (storeResult == null) { result = new Matrix3d(); } else { result = storeResult.makeIdentity(); } return result; } /** * Copy the scale factors of this object. * * @param storeResult storage for the result (modified if not null) * @return the value (1,1,1) (either storeResult or a new vector) */ @Override public Vector3f getScale(Vector3f storeResult) { Vector3f result = (storeResult == null) ? new Vector3f() : storeResult; result.set(1f, 1f, 1f); return result; } /** * De-serialize this body from the specified importer, for example when * loading from a J3O file. * * @param importer (not null) * @throws IOException from the importer */ @Override public void read(JmeImporter importer) throws IOException { InputCapsule capsule = importer.getCapsule(this); this.worldInfo = (SoftBodyWorldInfo) capsule.readSavable(tagWorldInfo, null); newEmptySoftBody(); // needs worldInfo! super.read(importer); readPcoProperties(capsule); this.isWorldInfoProtected = capsule.readBoolean(tagIsWorldInfoProtected, false); this.config = (SoftBodyConfig) capsule.readSavable(tagConfig, null); assert config != null; this.material = (SoftBodyMaterial) capsule.readSavable(tagMaterial, null); float[] fArray = capsule.readFloatArray(tagNodeLocations, new float[0]); appendNodes(BufferUtils.createFloatBuffer(fArray)); fArray = capsule.readFloatArray(tagNodeMasses, new float[0]); setMasses(BufferUtils.createFloatBuffer(fArray)); fArray = capsule.readFloatArray(tagNodeNormals, new float[0]); setNormals(BufferUtils.createFloatBuffer(fArray)); fArray = capsule.readFloatArray(tagNodeVelocities, new float[0]); setVelocities(BufferUtils.createFloatBuffer(fArray)); int[] nodeIndices = capsule.readIntArray(tagFaceIndices, new int[0]); IndexBuffer indexBuffer = IndexBuffer.wrapIndexBuffer( BufferUtils.createIntBuffer(nodeIndices)); appendFaces(indexBuffer); nodeIndices = capsule.readIntArray(tagLinkIndices, new int[0]); indexBuffer = IndexBuffer.wrapIndexBuffer( BufferUtils.createIntBuffer(nodeIndices)); appendLinks(indexBuffer); nodeIndices = capsule.readIntArray(tagTetraIndices, new int[0]); indexBuffer = IndexBuffer.wrapIndexBuffer( BufferUtils.createIntBuffer(nodeIndices)); appendTetras(indexBuffer); assert countClusters() == 0 : countClusters(); long objectId = nativeId(); int numClusters = capsule.readInt(tagNumClusters, 0); for (int clusterIndex = 0; clusterIndex < numClusters; ++clusterIndex) { nodeIndices = capsule.readIntArray(tagIndices + clusterIndex, new int[0]); int numNodesInCluster = nodeIndices.length; IntBuffer intBuffer = BufferUtils.createIntBuffer(nodeIndices); appendCluster(objectId, numNodesInCluster, intBuffer); for (Cluster clusterParameter : Cluster.values()) { String tag = clusterParameter.toString() + clusterIndex; float defValue = clusterParameter.defValue(); float value = capsule.readFloat(tag, defValue); set(clusterParameter, clusterIndex, value); } } finishClusters(objectId); assert countClusters() == numClusters : countClusters(); setRestingLengthScale(capsule.readFloat(tagRestLengthScale, 0f)); setPhysicsLocation((Vector3f) capsule.readSavable(tagPhysicsLocation, new Vector3f())); readJoints(capsule); } /** * Alter which normals to include in new debug meshes. * * @param newSetting an enum value (either None or Smooth) */ @Override public void setDebugMeshNormals(MeshNormals newSetting) { Validate.nonNull(newSetting, "new setting"); switch (newSetting) { case None: case Smooth: super.setDebugMeshNormals(newSetting); break; default: String message = "normals = " + newSetting; throw new IllegalArgumentException(message); } } /** * Alter this body's gravitational acceleration. *

* Invoke this method after adding the body to a space. Adding a * body to a space may override its gravity. * * @param acceleration the desired acceleration vector (in physics-space * coordinates, not null, unaffected) */ @Override public void setGravity(Vector3f acceleration) { Validate.finite(acceleration, "acceleration"); SoftBodyWorldInfo newInfo = new SoftBodyWorldInfo(); newInfo.copyAll(worldInfo); newInfo.setGravity(acceleration); setWorldInfo(newInfo); } /** * Alter the total mass for this body, distributing it in proportion to the * current mass of each node. * * @param totalMass the desired total mass (>0, default=numNodes) */ @Override public void setMass(float totalMass) { Validate.positive(totalMass, "total mass"); setMassByCurrent(totalMass); } /** * Directly relocate the center of this body's bounding box. * * @param location the desired location (in physics-space coordinates, not * null, finite, unaffected) */ @Override public void setPhysicsLocation(Vector3f location) { Validate.finite(location, "location"); long objectId = nativeId(); setPhysicsLocation(objectId, location); } /** * Serialize this body to the specified exporter, for example when saving to * a J3O file. * * @param exporter (not null) * @throws IOException from the exporter */ @Override public void write(JmeExporter exporter) throws IOException { super.write(exporter); OutputCapsule capsule = exporter.getCapsule(this); capsule.write(isWorldInfoProtected, tagIsWorldInfoProtected, false); capsule.write(restingLengthsScale(), tagRestLengthScale, 0f); capsule.write(getPhysicsLocation(null), tagPhysicsLocation, null); FloatBuffer floatBuffer = copyLocations(null); int capacity = floatBuffer.capacity(); float[] floatArray = MyBuffer.toFloatArray(floatBuffer, 0, capacity); capsule.write(floatArray, tagNodeLocations, null); floatBuffer = copyMasses(null); capacity = floatBuffer.capacity(); floatArray = MyBuffer.toFloatArray(floatBuffer, 0, capacity); capsule.write(floatArray, tagNodeMasses, null); floatBuffer = copyNormals(null); capacity = floatBuffer.capacity(); floatArray = MyBuffer.toFloatArray(floatBuffer, 0, capacity); capsule.write(floatArray, tagNodeNormals, null); floatBuffer = copyVelocities(null); capacity = floatBuffer.capacity(); floatArray = MyBuffer.toFloatArray(floatBuffer, 0, capacity); capsule.write(floatArray, tagNodeVelocities, null); IntBuffer intBuffer = copyFaces(null); capacity = intBuffer.capacity(); int[] intArray = MyBuffer.toIntArray(intBuffer, 0, capacity); capsule.write(intArray, tagFaceIndices, null); intBuffer = copyLinks(null); capacity = intBuffer.capacity(); intArray = MyBuffer.toIntArray(intBuffer, 0, capacity); capsule.write(intArray, tagLinkIndices, null); intBuffer = copyTetras(null); capacity = intBuffer.capacity(); intArray = MyBuffer.toIntArray(intBuffer, 0, capacity); capsule.write(intArray, tagTetraIndices, null); int numClusters = countClusters(); capsule.write(numClusters, tagNumClusters, 0); for (int clusterIndex = 0; clusterIndex < numClusters; ++clusterIndex) { intBuffer = listNodesInCluster(clusterIndex, null); capacity = intBuffer.capacity(); intArray = MyBuffer.toIntArray(intBuffer, 0, capacity); capsule.write(intArray, tagIndices + clusterIndex, null); for (Cluster clusterParameter : Cluster.values()) { float value = get(clusterParameter, clusterIndex); String tag = clusterParameter.toString() + clusterIndex; float defValue = clusterParameter.defValue(); capsule.write(value, tag, defValue); } } assert worldInfo != null; capsule.write(worldInfo, tagWorldInfo, null); assert config != null; capsule.write(config, tagConfig, null); capsule.write(material, tagMaterial, null); writeJoints(capsule); } // ************************************************************************* // native private methods native private static void addForce(long bodyId, Vector3f forceVector); native private static void addForce(long bodyId, Vector3f forceVector, int nodeIndex); native private static void addVelocity(long bodyId, Vector3f velocityVector); native private static void addVelocity(long bodyId, Vector3f velocityVector, int nodeIndex); native private static void appendCluster( long softBodyId, int numNodesInCluster, IntBuffer intBuffer); native private static void appendFaces(long bodyId, int numFaces, ByteBuffer byteBuffer); native private static void appendFaces(long bodyId, int numFaces, IntBuffer intBuffer); native private static void appendFaces(long bodyId, int numFaces, ShortBuffer shortBuffer); native private static void appendLinks(long bodyId, int numLinks, ByteBuffer byteBuffer); native private static void appendLinks(long bodyId, int numLinks, IntBuffer intBuffer); native private static void appendLinks(long bodyId, int numLinks, ShortBuffer shortBuffer); native private static void appendNodes(long bodyId, int numNodes, FloatBuffer locationBuffer); native private static void appendTetras(long bodyId, int numNodes, ByteBuffer byteBuffer); native private static void appendTetras(long bodyId, int numNodes, IntBuffer intBuffer); native private static void appendTetras(long bodyId, int numNodes, ShortBuffer shortBuffer); native private static void applyPhysicsRotation(long bodyId, Quaternion quaternion); native private static void applyPhysicsScale(long bodyId, Vector3f vector); native private static void applyPhysicsTransform(long bodyId, Transform transform); native private static void applyPhysicsTranslate(long bodyId, Vector3f offsetVector); native private static int countNodesInCluster(long objectId, int clusterIndex); native private static long createEmpty(long infoId); native private static boolean cutLink( long bodyId, int nodeIndex0, int nodeIndex1, float cutLocation); native private static void finishClusters(long softBodyId); native private static void generateBendingConstraints( long bodyId, int distance, long materialId); native private static void generateClusters(long bodyId, int k, int maxIterations); native private static void getBounds( long objectId, Vector3f storeMinima, Vector3f storeMaxima); native private static float getClusterAngularDamping(long bodyId, int clusterIndex); native private static void getClusterCenter( long bodyId, int clusterIndex, Vector3f storeVector); native private static int getClusterCount(long bodyId); native private static float getClusterLinearDamping(long bodyId, int clusterIndex); native private static float getClusterMatching(long bodyId, int clusterIndex); native private static float getClusterMaxSelfImpulse(long bodyId, int clusterIndex); native private static float getClusterNodeDamping(long bodyId, int clusterIndex); native private static float getClusterSelfImpulse(long bodyId, int clusterIndex); native private static void getClustersLinearVelocities(long bodyId, FloatBuffer storeBuffer); native private static void getClustersMasses(long bodyId, FloatBuffer storeBuffer); native private static void getClustersPositions(long bodyId, FloatBuffer storeBuffer); native private static void getFacesIndexes(long bodyId, IntBuffer storeBuffer); native private static void getLinksIndexes(long bodyId, IntBuffer storeBuffer); native private static float getMargin(long bodyId); native private static float getMass(long bodyId, int nodeIndex); native private static void getMasses(long bodyId, FloatBuffer storeBuffer); native private static int getNbFaces(long bodyId); native private static int getNbLinks(long bodyId); native private static int getNbNodes(long bodyId); native private static int getNbPinnedNodes(long bodyId); native private static int getNbTetras(long bodyId); native private static void getNodeLocation(long bodyId, int nodeIndex, Vector3f storeVector); native private static void getNodeNormal(long bodyId, int nodeIndex, Vector3f storeVector); native private static void getNodesNormals(long bodyId, FloatBuffer storeBuffer); native private static void getNodesPositions(long bodyId, FloatBuffer storeBuffer); native private static void getNodesVelocities(long bodyId, FloatBuffer storeBuffer); native private static void getNodeVelocity(long bodyId, int nodeIndex, Vector3f storeVector); native private static void getPhysicsLocation(long bodyId, Vector3f storeVector); native private static void getPhysicsLocationDp(long bodyId, Vec3d storeVector); native private static float getRestLengthScale(long bodyId); native private static long getSoftBodyWorldInfo(long bodyId); native private static void getTetrasIndexes(long bodyId, IntBuffer indexBuffer); native private static float getTotalMass(long bodyId); native private static float getVolume(long bodyId); native private static void getWindVelocity(long bodyId, Vector3f storeVector); native private static void initDefault(long bodyId); native private static boolean isCollisionAllowed(long softBodyId, long pcoId); native private static void listNodesInCluster( long bodyId, int clusterIndex, IntBuffer indexBuffer); native private static void randomizeConstraints(long bodyId); native private static void releaseCluster(long bodyId, int index); native private static void releaseClusters(long bodyId); native private static void resetLinkRestLengths(long bodyId); native private static void setClusterAngularDamping( long bodyId, int clusterIndex, float damping); native private static void setClusterLinearDamping( long bodyId, int clusterIndex, float damping); native private static void setClusterMatching( long bodyId, int clusterIndex, float coefficient); native private static void setClusterMaxSelfImpulse( long bodyId, int clusterIndex, float impulse); native private static void setClusterNodeDamping( long bodyId, int clusterIndex, float damping); native private static void setClusterSelfImpulse(long bodyId, int clusterIndex, float factor); native private static void setMargin(long bodyId, float margin); native private static void setMass(long bodyId, int nodeIndex, float mass); native private static void setMasses(long bodyId, FloatBuffer massBuffer); native private static void setNodeVelocity( long bodyId, int nodeIndex, Vector3f velocityVector); native private static void setNormals(long bodyId, FloatBuffer normalBuffer); native private static void setPhysicsLocation(long bodyId, Vector3f locationVector); native private static void setPhysicsLocationDp(long bodyId, Vec3d locationVector); native private static void setPose(long bodyId, boolean setVolumePose, boolean setFramePose); native private static void setRestLengthScale(long bodyId, float scale); native private static void setSoftBodyWorldInfo(long bodyId, long worldInfoId); native private static void setTotalDensity(long bodyId, float density); native private static void setTotalMass(long bodyId, float mass, boolean fromFaces); native private static void setVelocities(long bodyId, FloatBuffer velocityBuffer); native private static void setVelocity(long bodyId, Vector3f velocityVector); native private static void setVolumeDensity(long bodyId, float density); native private static void setVolumeMass(long bodyId, float mass); native private static void setWindVelocity(long bodyId, Vector3f velocityVector); }





© 2015 - 2025 Weber Informatics LLC | Privacy Policy