WormRule.java

import ru.biosoft.physicell.biofvm.Microenvironment;
import ru.biosoft.physicell.core.Cell;
import ru.biosoft.physicell.core.CellFunctions.CustomCellRule;
import ru.biosoft.physicell.core.Model;
import ru.biosoft.physicell.core.Phenotype;
import ru.biosoft.physicell.core.standard.Chemotaxis;
import ru.biosoft.physicell.core.CellFunctions.UpdateMigrationBias;
import ru.biosoft.physicell.biofvm.VectorUtil;

public class WormRule extends CustomCellRule
{
    private Model model;
    private boolean isInit = false;

    public void init(Model model)
    {
        this.model = model;
        isInit = true;
    }

    @Override
    public void execute(Cell pCell, Phenotype phenotype, double dt)
    {
        if (!isInit)
            init(pCell.getModel());

        // bookkeeping
        Microenvironment m = pCell.getMicroenvironment();
        int nSignal = m.findDensityIndex( "signal" );

        // look for cells to form attachments, if 0 attachments
        int number_of_attachments = pCell.state.numberAttachedCells();
        if( number_of_attachments == 0 )
        {
            for( Cell neighbor : pCell.nearby_interacting_cells() )
            {
                if( neighbor.state.numberAttachedCells() < neighbor.customData.get( "max_attachments" ) )
                {
                    Cell.attachcCells( neighbor, pCell );
                    number_of_attachments++;
                }
                if( number_of_attachments > pCell.customData.get( "max_attachments" ) )
                    break;
            }
        }

        if( number_of_attachments == 0 )
        {
            pCell.functions.updateMigration = new Chemotaxis();
        }
        else if( number_of_attachments == 1 ) // if 1 attachment, do some logic
        {
            // constant expression in end cells
            pCell.customData.set( "head", pCell.customData.get( "head_initial" ) );

            // am I the head?
            boolean head = false;
            if( pCell.customData.get( "head" ) > pCell.state.attachedCells.iterator().next().customData.get( "head" ) )
                head = true;

            //            if( head )
            pCell.functions.updateMigration = head ? new HeadMigration( model ) : new TailMigration( model );
            //            else
            //                pCell.functions.updateMigration = new TailMigration( model );

            phenotype.secretion.secretionRates[nSignal] = 100;
        }
        else if( number_of_attachments > 1 ) // if 2 or more attachments, use middle
        {
            pCell.functions.updateMigration = new MiddleMigration( model );
            phenotype.secretion.secretionRates[nSignal] = 1;
        }
    }

public class HeadMigration extends Chemotaxis
{
    private int direction;
    private double speed;
    private double bias;
    private double persistenceTime;

    public HeadMigration(Model model)
    {
        direction = model.getParameterInt( "head_migration_direction" );
        speed = model.getParameterDouble( "head_migration_speed" );
        bias = model.getParameterDouble( "head_migration_bias" );
        persistenceTime = model.getParameterDouble( "head_migration_persistence" );
    }

    @Override
    public void execute(Cell pCell, Phenotype phenotype, double dt)
    {
        phenotype.motility.chemotaxisDirection = direction;
        phenotype.motility.migrationSpeed = speed;
        phenotype.motility.migrationBias = bias;
        phenotype.motility.persistenceTime = persistenceTime;
        // use this for fun rotational paths
        /*
        double r = norm( pCell.position ) + 1e-16;
        phenotype.motility.migration_bias_direction[0] = - pCell.position[1] / r;
        phenotype.motility.migration_bias_direction[1] = pCell.position[0] / r;

        normalize( &(phenotype.motility.migration_bias_direction) );
        return;
        */
        super.execute( pCell, phenotype, dt );
    }
}

public class MiddleMigration extends UpdateMigrationBias
{
    double speed;

    public MiddleMigration(Model model)
    {
        speed = model.getParameterDouble( "middle_migration_speed" );
    }

    public void execute(Cell pCell, Phenotype phenotype, double dt)
    {
        // get velocity from "Upstream"
        Cell headCell = null;
        for( Cell cell : pCell.state.attachedCells )
        {
            if( headCell == null || cell.customData.get( "head" ) > headCell.customData.get( "head" ) )
                headCell = cell;
        }
        phenotype.motility.migrationSpeed = speed;
        phenotype.motility.migrationBiasDirection = headCell.phenotype.motility.migrationBiasDirection;
        VectorUtil.normalize( phenotype.motility.migrationBiasDirection );
    }
}

public class TailMigration extends Chemotaxis
{
    private int direction;
    private double speed;
    private double bias;
    private double persistenceTime;

    public TailMigration(Model model)
    {
        direction = model.getParameterInt( "tail_migration_direction" );
        speed = model.getParameterDouble( "tail_migration_speed" );
        bias = model.getParameterDouble( "tail_migration_bias" );
        persistenceTime = model.getParameterDouble( "tail_migration_persistence" );
    }

    @Override
    public void execute(Cell pCell, Phenotype phenotype, double dt)
    {
        phenotype.motility.chemotaxisDirection = direction;
        phenotype.motility.migrationSpeed = speed;
        phenotype.motility.migrationBias = bias;
        phenotype.motility.persistenceTime = persistenceTime;
        super.execute( pCell, phenotype, dt );
    }
}

}