├── .idea ├── .name ├── copyright │ └── profiles_settings.xml ├── scopes │ └── scope_settings.xml ├── encodings.xml ├── vcs.xml ├── modules.xml ├── gradle.xml ├── compiler.xml └── misc.xml ├── app ├── .gitignore ├── src │ ├── main │ │ ├── res │ │ │ ├── drawable-hdpi │ │ │ │ └── ic_launcher.png │ │ │ ├── drawable-mdpi │ │ │ │ └── ic_launcher.png │ │ │ ├── drawable-xhdpi │ │ │ │ └── ic_launcher.png │ │ │ ├── drawable-xxhdpi │ │ │ │ └── ic_launcher.png │ │ │ ├── values │ │ │ │ ├── styles.xml │ │ │ │ ├── dimens.xml │ │ │ │ └── strings.xml │ │ │ ├── menu │ │ │ │ └── menu_main.xml │ │ │ ├── values-w820dp │ │ │ │ └── dimens.xml │ │ │ └── layout │ │ │ │ └── activity_main.xml │ │ ├── java │ │ │ └── com │ │ │ │ └── heaven │ │ │ │ └── application │ │ │ │ └── metaballdemo │ │ │ │ ├── PointFWithA.java │ │ │ │ ├── MainActivity.java │ │ │ │ ├── MathUtil.java │ │ │ │ ├── metaballsystem │ │ │ │ ├── Metaball.java │ │ │ │ ├── Vector2D.java │ │ │ │ └── MetaballManager.java │ │ │ │ └── MetaballView.java │ │ └── AndroidManifest.xml │ └── androidTest │ │ └── java │ │ └── com │ │ └── heaven │ │ └── application │ │ └── metaballdemo │ │ └── ApplicationTest.java ├── build.gradle ├── proguard-rules.pro └── app.iml ├── settings.gradle ├── .gitignore ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── README.md ├── metaball.iml ├── gradle.properties ├── metaballDemo.iml ├── gradlew.bat └── gradlew /.idea/.name: -------------------------------------------------------------------------------- 1 | metaball -------------------------------------------------------------------------------- /app/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /settings.gradle: -------------------------------------------------------------------------------- 1 | include ':app' 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .gradle 2 | /local.properties 3 | /.idea/workspace.xml 4 | /.idea/libraries 5 | .DS_Store 6 | /build 7 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/7heaven/metaball/HEAD/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /.idea/copyright/profiles_settings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # metaball 2 | 3 | it's a simple source code copy from http://labs.byhook.com/2011/09/26/vector-metaballs/ 4 | 5 | -------------------------------------------------------------------------------- /app/src/main/res/drawable-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/7heaven/metaball/HEAD/app/src/main/res/drawable-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/7heaven/metaball/HEAD/app/src/main/res/drawable-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/7heaven/metaball/HEAD/app/src/main/res/drawable-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/7heaven/metaball/HEAD/app/src/main/res/drawable-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /.idea/scopes/scope_settings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | -------------------------------------------------------------------------------- /.idea/encodings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /.idea/vcs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /app/src/main/res/values/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /app/src/main/res/values/dimens.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 16dp 4 | 16dp 5 | 6 | -------------------------------------------------------------------------------- /app/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | metaballDemo 5 | Hello world! 6 | Settings 7 | 8 | 9 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Wed Apr 10 15:27:10 PDT 2013 2 | distributionBase=GRADLE_USER_HOME 3 | distributionPath=wrapper/dists 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | distributionUrl=https\://services.gradle.org/distributions/gradle-2.2.1-all.zip 7 | -------------------------------------------------------------------------------- /app/src/main/java/com/heaven/application/metaballdemo/PointFWithA.java: -------------------------------------------------------------------------------- 1 | package com.heaven.application.metaballdemo; 2 | 3 | import android.graphics.PointF; 4 | 5 | /** 6 | * Created by caifangmao on 15/4/30. 7 | */ 8 | public class PointFWithA extends PointF { 9 | 10 | public float a; 11 | 12 | public PointFWithA(float x, float y){ 13 | super(x, y); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /.idea/modules.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /app/src/main/res/menu/menu_main.xml: -------------------------------------------------------------------------------- 1 | 4 | 6 | 7 | -------------------------------------------------------------------------------- /app/src/main/res/values-w820dp/dimens.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 64dp 6 | 7 | -------------------------------------------------------------------------------- /app/src/androidTest/java/com/heaven/application/metaballdemo/ApplicationTest.java: -------------------------------------------------------------------------------- 1 | package com.heaven.application.metaballdemo; 2 | 3 | import android.app.Application; 4 | import android.test.ApplicationTestCase; 5 | 6 | /** 7 | * Testing Fundamentals 8 | */ 9 | public class ApplicationTest extends ApplicationTestCase { 10 | public ApplicationTest() { 11 | super(Application.class); 12 | } 13 | } -------------------------------------------------------------------------------- /app/src/main/java/com/heaven/application/metaballdemo/MainActivity.java: -------------------------------------------------------------------------------- 1 | package com.heaven.application.metaballdemo; 2 | 3 | import android.app.Activity; 4 | import android.os.Bundle; 5 | 6 | import com.heaven.application.metaballdemo.R; 7 | 8 | public class MainActivity extends Activity { 9 | 10 | private MetaballView metaballView; 11 | 12 | @Override 13 | protected void onCreate(Bundle savedInstanceState) { 14 | super.onCreate(savedInstanceState); 15 | setContentView(R.layout.activity_main); 16 | 17 | metaballView = (MetaballView) findViewById(R.id.metaball_view); 18 | } 19 | 20 | } 21 | -------------------------------------------------------------------------------- /.idea/gradle.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /app/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.application' 2 | 3 | android { 4 | compileSdkVersion 21 5 | buildToolsVersion "21.1.2" 6 | 7 | defaultConfig { 8 | applicationId "com.heaven.application.metaballdemo" 9 | minSdkVersion 8 10 | targetSdkVersion 21 11 | versionCode 1 12 | versionName "1.0" 13 | } 14 | buildTypes { 15 | release { 16 | minifyEnabled false 17 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' 18 | } 19 | } 20 | } 21 | 22 | dependencies { 23 | compile fileTree(dir: 'libs', include: ['*.jar']) 24 | compile 'com.android.support:appcompat-v7:21.0.3' 25 | } 26 | -------------------------------------------------------------------------------- /app/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # By default, the flags in this file are appended to flags specified 3 | # in /Users/caifangmao/Documents/android-sdk/tools/proguard/proguard-android.txt 4 | # You can edit the include path and order by changing the proguardFiles 5 | # directive in build.gradle. 6 | # 7 | # For more details, see 8 | # http://developer.android.com/guide/developing/tools/proguard.html 9 | 10 | # Add any project specific keep options here: 11 | 12 | # If your project uses WebView with JS, uncomment the following 13 | # and specify the fully qualified class name to the JavaScript interface 14 | # class: 15 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 16 | # public *; 17 | #} 18 | -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_main.xml: -------------------------------------------------------------------------------- 1 | 7 | 8 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 10 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /.idea/compiler.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /metaball.iml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /gradle.properties: -------------------------------------------------------------------------------- 1 | # Project-wide Gradle settings. 2 | 3 | # IDE (e.g. Android Studio) users: 4 | # Gradle settings configured through the IDE *will override* 5 | # any settings specified in this file. 6 | 7 | # For more details on how to configure your build environment visit 8 | # http://www.gradle.org/docs/current/userguide/build_environment.html 9 | 10 | # Specifies the JVM arguments used for the daemon process. 11 | # The setting is particularly useful for tweaking memory settings. 12 | # Default value: -Xmx10248m -XX:MaxPermSize=256m 13 | # org.gradle.jvmargs=-Xmx2048m -XX:MaxPermSize=512m -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8 14 | 15 | # When configured, Gradle will run in incubating parallel mode. 16 | # This option should only be used with decoupled projects. More details, visit 17 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects 18 | # org.gradle.parallel=true -------------------------------------------------------------------------------- /.idea/misc.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 1.7 14 | 15 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /metaballDemo.iml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /app/src/main/java/com/heaven/application/metaballdemo/MathUtil.java: -------------------------------------------------------------------------------- 1 | package com.heaven.application.metaballdemo; 2 | 3 | /** 4 | * Created by caifangmao on 15/2/11. 5 | */ 6 | public class MathUtil { 7 | public static final float PI = 3.14159265F; 8 | public static final float HALF_PI = 1.57079633F; 9 | public static final float PI_OVER_180 = 0.0174532925F; 10 | public static final float PI_UNDER_180 = 57.2957795F; 11 | 12 | public static float map(float value, float fromLow, float fromHigh, float toLow, float toHigh){ 13 | return (value - fromLow) * (toHigh - toLow) / (fromHigh - fromLow) + toLow; 14 | } 15 | 16 | public static float strictMap(float value, float fromLow, float fromHigh, float toLow, float toHigh){ 17 | float result = (value - fromLow) * (toHigh - toLow) / (fromHigh - fromLow) + toLow; 18 | return result < toLow ? toLow : ( result > toHigh ? toHigh : result); 19 | } 20 | 21 | public static float clamp(float value, float min, float max){ 22 | return value < min ? min : (value > max ? max: value); 23 | } 24 | 25 | public static float angleBetween(float x, float y, float u, float v, boolean radians){ 26 | return radians ? (float) Math.atan2(v - y, u - x) : degrees((float) Math.atan2(v - y, u - x)); 27 | } 28 | 29 | public static float angleBetween(float x, float y, float u, float v){ 30 | return degrees((float) Math.atan2(v - y, u - x)); 31 | } 32 | 33 | public static float degrees(float value){ 34 | return value * PI_UNDER_180; 35 | } 36 | 37 | public static float radians(float value){ 38 | return value * PI_OVER_180; 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /app/src/main/java/com/heaven/application/metaballdemo/metaballsystem/Metaball.java: -------------------------------------------------------------------------------- 1 | package com.heaven.application.metaballdemo.metaballsystem; 2 | 3 | import android.graphics.PointF; 4 | 5 | import com.heaven.application.metaballdemo.MathUtil; 6 | 7 | /** 8 | * Created by caifangmao on 15/2/11. 9 | */ 10 | public class Metaball { 11 | 12 | public static final float MIN_STRENGTH = 1F; 13 | public static final float MAX_STRENGTH = 100F; 14 | 15 | private Vector2D position; 16 | private float strength; 17 | 18 | public boolean attached = false; 19 | public boolean tracked; 20 | public Vector2D edge; 21 | public Vector2D direction; 22 | 23 | public Metaball(Vector2D position, float strength){ 24 | this.position = position.clone(); 25 | this.strength = strength; 26 | 27 | tracked = false; 28 | edge = position.clone(); 29 | direction = new Vector2D((float) Math.random() * 2 - 1, (float) Math.random() * 2 - 1); 30 | } 31 | 32 | public float strengthAt(Vector2D vector, float c){ 33 | float div = (float) Math.pow(Vector2D.subtract(position, vector).lengthSq(), c * 0.5F); 34 | 35 | return (div != 0) ? (strength / div) : 10000; 36 | } 37 | 38 | public Vector2D getPosition(){ 39 | return position; 40 | } 41 | 42 | public void setPosition(Vector2D position){ 43 | this.position.copy(position); 44 | } 45 | 46 | public void setStrength(float value){ 47 | strength = MathUtil.clamp(value, MIN_STRENGTH, MAX_STRENGTH); 48 | } 49 | 50 | public float getStrength(){ 51 | return strength; 52 | } 53 | 54 | @Override 55 | public String toString(){ 56 | return "[object Metaball][position=" + position + "][size=" + strength + "]"; 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /gradlew.bat: -------------------------------------------------------------------------------- 1 | @if "%DEBUG%" == "" @echo off 2 | @rem ########################################################################## 3 | @rem 4 | @rem Gradle startup script for Windows 5 | @rem 6 | @rem ########################################################################## 7 | 8 | @rem Set local scope for the variables with windows NT shell 9 | if "%OS%"=="Windows_NT" setlocal 10 | 11 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 12 | set DEFAULT_JVM_OPTS= 13 | 14 | set DIRNAME=%~dp0 15 | if "%DIRNAME%" == "" set DIRNAME=. 16 | set APP_BASE_NAME=%~n0 17 | set APP_HOME=%DIRNAME% 18 | 19 | @rem Find java.exe 20 | if defined JAVA_HOME goto findJavaFromJavaHome 21 | 22 | set JAVA_EXE=java.exe 23 | %JAVA_EXE% -version >NUL 2>&1 24 | if "%ERRORLEVEL%" == "0" goto init 25 | 26 | echo. 27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 28 | echo. 29 | echo Please set the JAVA_HOME variable in your environment to match the 30 | echo location of your Java installation. 31 | 32 | goto fail 33 | 34 | :findJavaFromJavaHome 35 | set JAVA_HOME=%JAVA_HOME:"=% 36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 37 | 38 | if exist "%JAVA_EXE%" goto init 39 | 40 | echo. 41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 42 | echo. 43 | echo Please set the JAVA_HOME variable in your environment to match the 44 | echo location of your Java installation. 45 | 46 | goto fail 47 | 48 | :init 49 | @rem Get command-line arguments, handling Windowz variants 50 | 51 | if not "%OS%" == "Windows_NT" goto win9xME_args 52 | if "%@eval[2+2]" == "4" goto 4NT_args 53 | 54 | :win9xME_args 55 | @rem Slurp the command line arguments. 56 | set CMD_LINE_ARGS= 57 | set _SKIP=2 58 | 59 | :win9xME_args_slurp 60 | if "x%~1" == "x" goto execute 61 | 62 | set CMD_LINE_ARGS=%* 63 | goto execute 64 | 65 | :4NT_args 66 | @rem Get arguments from the 4NT Shell from JP Software 67 | set CMD_LINE_ARGS=%$ 68 | 69 | :execute 70 | @rem Setup the command line 71 | 72 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 73 | 74 | @rem Execute Gradle 75 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% 76 | 77 | :end 78 | @rem End local scope for the variables with windows NT shell 79 | if "%ERRORLEVEL%"=="0" goto mainEnd 80 | 81 | :fail 82 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 83 | rem the _cmd.exe /c_ return code! 84 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 85 | exit /b 1 86 | 87 | :mainEnd 88 | if "%OS%"=="Windows_NT" endlocal 89 | 90 | :omega 91 | -------------------------------------------------------------------------------- /app/src/main/java/com/heaven/application/metaballdemo/metaballsystem/Vector2D.java: -------------------------------------------------------------------------------- 1 | package com.heaven.application.metaballdemo.metaballsystem; 2 | 3 | /** 4 | * Created by caifangmao on 15/2/11. 5 | */ 6 | public class Vector2D { 7 | 8 | public float x; 9 | public float y; 10 | 11 | public Vector2D(float x, float y){ 12 | this.x = x; 13 | this.y = y; 14 | } 15 | 16 | public float getLength(){ 17 | return (float) Math.sqrt(lengthSq()); 18 | } 19 | 20 | public void setLength(float length){ 21 | x *= (length /= getLength()); 22 | y *= length; 23 | } 24 | 25 | public float lengthSq(){ 26 | return (x * x + y * y); 27 | } 28 | 29 | public float getAngle(){ 30 | return (float) Math.atan2(x, y); 31 | } 32 | 33 | public void setAngle(float angle){ 34 | float len = getLength(); 35 | x = (float) Math.cos(angle) * len; 36 | y = (float) Math.sin(angle) * len; 37 | } 38 | 39 | public float dot(Vector2D v2){ 40 | return (x * v2.x) + (y * v2.y); 41 | } 42 | 43 | public float dist(Vector2D v2){ 44 | return (float) Math.sqrt(distSq(v2)); 45 | } 46 | 47 | public float distSq(Vector2D v2){ 48 | float dx = v2.x - x; 49 | float dy = v2.y - y; 50 | 51 | return (dx * dx + dy * dy); 52 | } 53 | 54 | public Vector2D zero(){ 55 | x = 0; 56 | y = 0; 57 | 58 | return this; 59 | } 60 | 61 | public Vector2D copy(Vector2D v2){ 62 | x = v2.x; 63 | y = v2.y; 64 | 65 | return this; 66 | } 67 | 68 | public Vector2D norm(){ 69 | if( x== 0 && y == 0){ 70 | x = 1; 71 | return this; 72 | } 73 | 74 | float len = getLength(); 75 | x /= len; 76 | y /= len; 77 | 78 | return this; 79 | } 80 | 81 | public Vector2D limit(float max){ 82 | float len = getLength(); 83 | if(len > max){ 84 | x *= (max /= len); 85 | y *= max; 86 | } 87 | 88 | return this; 89 | } 90 | 91 | public Vector2D add(Vector2D v2){ 92 | x += v2.x; 93 | y += v2.y; 94 | 95 | return this; 96 | } 97 | 98 | public Vector2D subtract(Vector2D v2){ 99 | x -= v2.x; 100 | y -= v2.y; 101 | 102 | return this; 103 | } 104 | 105 | public Vector2D multiply(float value){ 106 | x *= value; 107 | y *= value; 108 | 109 | return this; 110 | } 111 | 112 | public Vector2D divide(float value){ 113 | x /= value; 114 | y /= value; 115 | 116 | return this; 117 | } 118 | 119 | public Vector2D clone(){ 120 | return new Vector2D(x, y); 121 | } 122 | 123 | public Vector2D getPerpLeft(){ 124 | return new Vector2D(-y, x); 125 | } 126 | 127 | public Vector2D getPerpRight(){ 128 | return new Vector2D(y, -x); 129 | } 130 | 131 | public static float angleBetween(Vector2D v1, Vector2D v2){ 132 | return (float) (Math.atan2(v2.y, v2.x) - Math.atan2(v1.y, v1.x)); 133 | } 134 | 135 | public static Vector2D add(Vector2D v1, Vector2D v2){ 136 | return new Vector2D(v1.x + v2.x, v1.y + v2.y); 137 | } 138 | 139 | public static Vector2D subtract(Vector2D v1, Vector2D v2){ 140 | return new Vector2D(v1.x - v2.x, v1.y - v2.y); 141 | } 142 | 143 | @Override 144 | public String toString(){ 145 | return "[object Vector2D][x=" + x + "][y=" + y + "]"; 146 | } 147 | 148 | } 149 | -------------------------------------------------------------------------------- /app/src/main/java/com/heaven/application/metaballdemo/metaballsystem/MetaballManager.java: -------------------------------------------------------------------------------- 1 | package com.heaven.application.metaballdemo.metaballsystem; 2 | 3 | import android.graphics.Path; 4 | 5 | import java.util.ArrayList; 6 | import java.util.List; 7 | 8 | /** 9 | * Created by caifangmao on 15/2/12. 10 | */ 11 | public class MetaballManager { 12 | 13 | private static MetaballManager instance; 14 | 15 | private static float gooieness = 2.0F; 16 | private static float threshold = 0.0006F; 17 | private static float resolution = 5.0F; 18 | private static int maxSteps = 600; 19 | 20 | private List metaballs; 21 | private Path outline; 22 | private float minStrength; 23 | 24 | private MetaballManager(){ 25 | metaballs = new ArrayList(); 26 | outline = new Path(); 27 | 28 | minStrength = Metaball.MIN_STRENGTH; 29 | } 30 | 31 | public static MetaballManager getInstance(){ 32 | if(instance == null){ 33 | instance = new MetaballManager(); 34 | } 35 | 36 | return instance; 37 | } 38 | 39 | public Path getOutline(){ 40 | return outline; 41 | } 42 | 43 | public void addMetaball(Metaball metaball){ 44 | minStrength = Math.min(metaball.getStrength(), minStrength); 45 | 46 | metaballs.add(metaball); 47 | } 48 | 49 | public void removeMetaball(Metaball metaball){ 50 | metaballs.remove(metaball); 51 | } 52 | 53 | public void removeAllMetaball(){ metaballs.clear();} 54 | 55 | public int getSize(){ 56 | return metaballs.size(); 57 | } 58 | 59 | public void freeze(){ 60 | outline.reset(); 61 | 62 | Vector2D seeker = new Vector2D(0, 0); 63 | // Metaball metaball; 64 | int i; 65 | 66 | for(Metaball metaball : metaballs){ 67 | metaball.tracked = false; 68 | 69 | seeker.copy(metaball.getPosition()); 70 | i = 0; 71 | while((stepToEdge(seeker) > threshold) && (++i < 50)){} 72 | metaball.edge.copy(seeker); 73 | } 74 | 75 | int edgeSteps = 0; 76 | Metaball current = untrackedMetaball(); 77 | 78 | seeker.copy(current.edge); 79 | outline.moveTo(seeker.x, seeker.y); 80 | 81 | while(current != null && edgeSteps < maxSteps){ 82 | rk2(seeker, resolution); 83 | 84 | outline.lineTo(seeker.x, seeker.y); 85 | 86 | for(Metaball metaball : metaballs){ 87 | if(seeker.dist(metaball.edge) < (resolution * 0.9F)){ 88 | seeker.copy(metaball.edge); 89 | outline.lineTo(seeker.x, seeker.y); 90 | 91 | current.tracked = true; 92 | 93 | if(metaball.tracked){ 94 | current = untrackedMetaball(); 95 | 96 | if(current != null){ 97 | seeker.copy(current.edge); 98 | outline.moveTo(seeker.x, seeker.y); 99 | } 100 | }else{ 101 | current = metaball; 102 | } 103 | 104 | break; 105 | } 106 | } 107 | 108 | ++edgeSteps; 109 | } 110 | 111 | outline.close(); 112 | } 113 | 114 | private Metaball untrackedMetaball(){ 115 | for(Metaball metaball : metaballs){ 116 | if(!metaball.tracked){ 117 | return metaball; 118 | } 119 | } 120 | 121 | return null; 122 | } 123 | 124 | private float stepToEdge(Vector2D seeker){ 125 | float force = fieldStrength(seeker); 126 | float stepsize; 127 | 128 | stepsize = (float) (Math.pow(minStrength / threshold, 1 / gooieness) - Math.pow(minStrength / force, 1 / gooieness) + 0.01F); 129 | 130 | seeker.add(fieldNormal(seeker).multiply(stepsize)); 131 | 132 | return force; 133 | } 134 | 135 | private float fieldStrength(Vector2D v){ 136 | float force = 0.0F; 137 | 138 | for(Metaball metaball : metaballs){ 139 | force += metaball.strengthAt(v, gooieness); 140 | } 141 | 142 | return force; 143 | } 144 | 145 | private Vector2D fieldNormal(Vector2D v){ 146 | Vector2D force = new Vector2D(0, 0); 147 | Vector2D radius; 148 | 149 | for(Metaball metaball : metaballs){ 150 | radius = Vector2D.subtract(metaball.getPosition(), v); 151 | 152 | if(radius.lengthSq() == 0){ 153 | continue; 154 | } 155 | 156 | radius.multiply((float) (-gooieness * metaball.getStrength() * (1.0F / Math.pow(radius.lengthSq(), (2.0F + gooieness) * 0.5F)))); 157 | 158 | force.add(radius); 159 | } 160 | 161 | return force.norm(); 162 | } 163 | 164 | private void rk2(Vector2D v, float h){ 165 | Vector2D t1 = fieldNormal(v).getPerpLeft(); 166 | t1.multiply(h * 0.5F); 167 | 168 | Vector2D t2 = fieldNormal(Vector2D.add(v, t1)).getPerpLeft(); 169 | t2.multiply(h); 170 | 171 | v.add(t2); 172 | } 173 | } 174 | -------------------------------------------------------------------------------- /gradlew: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | ############################################################################## 4 | ## 5 | ## Gradle start up script for UN*X 6 | ## 7 | ############################################################################## 8 | 9 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 10 | DEFAULT_JVM_OPTS="" 11 | 12 | APP_NAME="Gradle" 13 | APP_BASE_NAME=`basename "$0"` 14 | 15 | # Use the maximum available, or set MAX_FD != -1 to use that value. 16 | MAX_FD="maximum" 17 | 18 | warn ( ) { 19 | echo "$*" 20 | } 21 | 22 | die ( ) { 23 | echo 24 | echo "$*" 25 | echo 26 | exit 1 27 | } 28 | 29 | # OS specific support (must be 'true' or 'false'). 30 | cygwin=false 31 | msys=false 32 | darwin=false 33 | case "`uname`" in 34 | CYGWIN* ) 35 | cygwin=true 36 | ;; 37 | Darwin* ) 38 | darwin=true 39 | ;; 40 | MINGW* ) 41 | msys=true 42 | ;; 43 | esac 44 | 45 | # For Cygwin, ensure paths are in UNIX format before anything is touched. 46 | if $cygwin ; then 47 | [ -n "$JAVA_HOME" ] && JAVA_HOME=`cygpath --unix "$JAVA_HOME"` 48 | fi 49 | 50 | # Attempt to set APP_HOME 51 | # Resolve links: $0 may be a link 52 | PRG="$0" 53 | # Need this for relative symlinks. 54 | while [ -h "$PRG" ] ; do 55 | ls=`ls -ld "$PRG"` 56 | link=`expr "$ls" : '.*-> \(.*\)$'` 57 | if expr "$link" : '/.*' > /dev/null; then 58 | PRG="$link" 59 | else 60 | PRG=`dirname "$PRG"`"/$link" 61 | fi 62 | done 63 | SAVED="`pwd`" 64 | cd "`dirname \"$PRG\"`/" >&- 65 | APP_HOME="`pwd -P`" 66 | cd "$SAVED" >&- 67 | 68 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 69 | 70 | # Determine the Java command to use to start the JVM. 71 | if [ -n "$JAVA_HOME" ] ; then 72 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 73 | # IBM's JDK on AIX uses strange locations for the executables 74 | JAVACMD="$JAVA_HOME/jre/sh/java" 75 | else 76 | JAVACMD="$JAVA_HOME/bin/java" 77 | fi 78 | if [ ! -x "$JAVACMD" ] ; then 79 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 80 | 81 | Please set the JAVA_HOME variable in your environment to match the 82 | location of your Java installation." 83 | fi 84 | else 85 | JAVACMD="java" 86 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 87 | 88 | Please set the JAVA_HOME variable in your environment to match the 89 | location of your Java installation." 90 | fi 91 | 92 | # Increase the maximum file descriptors if we can. 93 | if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then 94 | MAX_FD_LIMIT=`ulimit -H -n` 95 | if [ $? -eq 0 ] ; then 96 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then 97 | MAX_FD="$MAX_FD_LIMIT" 98 | fi 99 | ulimit -n $MAX_FD 100 | if [ $? -ne 0 ] ; then 101 | warn "Could not set maximum file descriptor limit: $MAX_FD" 102 | fi 103 | else 104 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" 105 | fi 106 | fi 107 | 108 | # For Darwin, add options to specify how the application appears in the dock 109 | if $darwin; then 110 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" 111 | fi 112 | 113 | # For Cygwin, switch paths to Windows format before running java 114 | if $cygwin ; then 115 | APP_HOME=`cygpath --path --mixed "$APP_HOME"` 116 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` 117 | 118 | # We build the pattern for arguments to be converted via cygpath 119 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` 120 | SEP="" 121 | for dir in $ROOTDIRSRAW ; do 122 | ROOTDIRS="$ROOTDIRS$SEP$dir" 123 | SEP="|" 124 | done 125 | OURCYGPATTERN="(^($ROOTDIRS))" 126 | # Add a user-defined pattern to the cygpath arguments 127 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then 128 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" 129 | fi 130 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 131 | i=0 132 | for arg in "$@" ; do 133 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` 134 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option 135 | 136 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition 137 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` 138 | else 139 | eval `echo args$i`="\"$arg\"" 140 | fi 141 | i=$((i+1)) 142 | done 143 | case $i in 144 | (0) set -- ;; 145 | (1) set -- "$args0" ;; 146 | (2) set -- "$args0" "$args1" ;; 147 | (3) set -- "$args0" "$args1" "$args2" ;; 148 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;; 149 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; 150 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; 151 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; 152 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; 153 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; 154 | esac 155 | fi 156 | 157 | # Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules 158 | function splitJvmOpts() { 159 | JVM_OPTS=("$@") 160 | } 161 | eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS 162 | JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME" 163 | 164 | exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@" 165 | -------------------------------------------------------------------------------- /app/app.iml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 8 | 9 | 10 | 11 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | -------------------------------------------------------------------------------- /app/src/main/java/com/heaven/application/metaballdemo/MetaballView.java: -------------------------------------------------------------------------------- 1 | package com.heaven.application.metaballdemo; 2 | 3 | import android.content.Context; 4 | import android.graphics.Canvas; 5 | import android.graphics.Paint; 6 | import android.graphics.Path; 7 | import android.graphics.Point; 8 | import android.graphics.PointF; 9 | import android.os.Handler; 10 | import android.util.AttributeSet; 11 | import android.view.MotionEvent; 12 | import android.view.View; 13 | 14 | import com.heaven.application.metaballdemo.metaballsystem.Metaball; 15 | import com.heaven.application.metaballdemo.metaballsystem.MetaballManager; 16 | import com.heaven.application.metaballdemo.metaballsystem.Vector2D; 17 | 18 | import java.util.ArrayList; 19 | import java.util.List; 20 | 21 | /** 22 | * Created by caifangmao on 15/2/11. 23 | */ 24 | public class MetaballView extends View { 25 | 26 | private int width; 27 | private int height; 28 | 29 | private int centerX; 30 | private int centerY; 31 | 32 | private MetaballManager metaballManager; 33 | 34 | private Metaball[] targetBall = new Metaball[1]; 35 | private List subMetaball; 36 | private List originPositions; 37 | private List as; 38 | 39 | private Paint paint; 40 | 41 | private Handler handler = new Handler(); 42 | private Runnable runnable = new Runnable(){ 43 | @Override 44 | public void run(){ 45 | for(int i = 0; i < as.size(); i++){ 46 | Metaball ball = subMetaball.get(i); 47 | if(!ball.attached){ 48 | Point origin = originPositions.get(i); 49 | PointF a = as.get(i); 50 | Vector2D v = ball.getPosition(); 51 | 52 | a.x += (origin.x - v.x) * 0.1F; 53 | a.y += (origin.y - v.y) * 0.1F; 54 | 55 | v.x += a.x; 56 | v.y += a.y; 57 | 58 | ball.setPosition(v); 59 | 60 | a.x *= 0.81F; 61 | a.y *= 0.81F; 62 | } 63 | 64 | } 65 | 66 | if(metaballManager != null && metaballManager.getSize() > 0) metaballManager.freeze(); 67 | 68 | invalidate(); 69 | 70 | handler.postDelayed(this, 20); 71 | } 72 | }; 73 | 74 | public MetaballView(Context context){ 75 | this(context, null); 76 | } 77 | 78 | public MetaballView(Context context, AttributeSet attrs){ 79 | this(context, attrs, 0); 80 | } 81 | 82 | public MetaballView(Context context, AttributeSet attrs, int defStyle){ 83 | super(context, attrs, defStyle); 84 | 85 | metaballManager = MetaballManager.getInstance(); 86 | 87 | subMetaball = new ArrayList(); 88 | as = new ArrayList(); 89 | originPositions = new ArrayList(); 90 | 91 | for(int i = 0; i < targetBall.length; i++){ 92 | targetBall[i] = new Metaball(new Vector2D(0, 0), 1.0F); 93 | } 94 | 95 | paint = new Paint(Paint.ANTI_ALIAS_FLAG); 96 | paint.setStyle(Paint.Style.STROKE); 97 | paint.setStrokeWidth(5); 98 | paint.setColor(0xFF00CCFF); 99 | 100 | handler.post(runnable); 101 | 102 | } 103 | 104 | @Override 105 | protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { 106 | super.onMeasure(widthMeasureSpec, heightMeasureSpec); 107 | 108 | width = MeasureSpec.getSize(widthMeasureSpec); 109 | height = MeasureSpec.getSize(heightMeasureSpec); 110 | 111 | centerX = width / 2; 112 | centerY = height / 2; 113 | 114 | metaballManager.removeAllMetaball(); 115 | 116 | subMetaball.clear(); 117 | as.clear(); 118 | originPositions.clear(); 119 | for(float i = 0; i < Math.PI * 2; i+=Math.PI / 3){ 120 | 121 | Point p = centerRadiusPoint(new Point(centerX, centerY), i, 5); 122 | originPositions.add(p); 123 | 124 | Metaball metaball = new Metaball(new Vector2D(p.x, p.y), 2.0F); 125 | 126 | metaballManager.addMetaball(metaball); 127 | 128 | subMetaball.add(metaball); 129 | as.add(new PointF(0, 0)); 130 | } 131 | 132 | for(Metaball b : targetBall){ 133 | metaballManager.addMetaball(b); 134 | } 135 | } 136 | 137 | protected Point centerRadiusPoint(Point center, double angle, double radius){ 138 | Point p = new Point(); 139 | p.x = (int) (radius * Math.cos(angle)) + center.x; 140 | p.y = (int) (radius * Math.sin(angle)) + center.y; 141 | 142 | return p; 143 | } 144 | 145 | @Override 146 | public boolean onTouchEvent(MotionEvent event){ 147 | switch(event.getActionMasked()){ 148 | case MotionEvent.ACTION_DOWN: 149 | break; 150 | case MotionEvent.ACTION_MOVE: 151 | targetBall[0].setPosition(new Vector2D(event.getX(), event.getY())); 152 | 153 | float dx; 154 | float dy; 155 | 156 | Metaball tBall = null; 157 | float dis = Integer.MAX_VALUE; 158 | float ddis = 0; 159 | float d = 0; 160 | float dd = 0; 161 | for(int i = 0; i < subMetaball.size(); i++){ 162 | Metaball m = subMetaball.get(i); 163 | Vector2D v = m.getPosition(); 164 | Point p = originPositions.get(i); 165 | 166 | dx = event.getX() - p.x; 167 | dy = event.getY() - p.y; 168 | 169 | d = dx * dx + dy * dy; 170 | 171 | 172 | 173 | dx = v.x - p.x; 174 | dy = v.y - p.y; 175 | 176 | dd = dx * dx + dy * dy; 177 | 178 | if(d < dis){ 179 | dis = d; 180 | tBall = m; 181 | ddis = dd; 182 | } 183 | 184 | if(dis > 100 && ddis > 2500){ 185 | m.attached = false; 186 | } 187 | } 188 | 189 | if(tBall != null){ 190 | if(dis > 100 && ddis > 2500){ 191 | tBall.attached = false; 192 | }else{ 193 | tBall.attached = true; 194 | tBall.getPosition().x = event.getX(); 195 | tBall.getPosition().y = event.getY(); 196 | } 197 | } 198 | 199 | metaballManager.freeze(); 200 | 201 | invalidate(); 202 | 203 | break; 204 | case MotionEvent.ACTION_CANCEL: 205 | case MotionEvent.ACTION_UP: 206 | for(Metaball m : subMetaball){ 207 | m.attached = false; 208 | } 209 | break; 210 | } 211 | 212 | return true; 213 | } 214 | 215 | @Override 216 | public void onDraw(Canvas canvas){ 217 | super.onDraw(canvas); 218 | // 219 | Path outline = metaballManager.getOutline(); 220 | 221 | if(outline != null){ 222 | canvas.drawPath(outline, paint); 223 | } 224 | } 225 | 226 | } 227 | --------------------------------------------------------------------------------