diff --git a/.gitignore b/.gitignore index f6b286c..4fe2129 100644 --- a/.gitignore +++ b/.gitignore @@ -34,7 +34,7 @@ captures/ # Intellij *.iml -.idea/workspace.xml +.idea # Keystore files *.jks diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 0000000..cbe2286 --- /dev/null +++ b/.travis.yml @@ -0,0 +1,18 @@ +script: + # By default Travis CI executes './gradlew build connectedCheck' if no 'script:' section found. + - ./gradlew build + +language: android + +jdk: + - oraclejdk8 + +android: + components: + - tools + - build-tools-23.0.1 + - android-23 + - extra-android-m2repository + +notifications: + email: false diff --git a/app/build.gradle b/app/build.gradle new file mode 100644 index 0000000..183ea5b --- /dev/null +++ b/app/build.gradle @@ -0,0 +1,32 @@ +apply plugin: 'com.android.application' + +android { + compileSdkVersion 23 + buildToolsVersion "23.0.1" + + defaultConfig { + applicationId "org.literacyapp.visemes" + minSdkVersion 21 + targetSdkVersion 23 + versionCode 10000000 + versionName "1.0.0" + } + + buildTypes { + release { + minifyEnabled false + } + } + + lintOptions { + abortOnError false + } +} + +dependencies { + compile fileTree(dir: 'libs', include: ['*.jar']) + testCompile 'junit:junit:4.12' + compile 'com.android.support:appcompat-v7:23.4.0' + compile 'com.android.support:support-v4:23.4.0' + compile 'org.rajawali3d:rajawali:1.0.325@aar' +} diff --git a/app/src/androidTest/java/org/literacyapp/visemes/ApplicationTest.java b/app/src/androidTest/java/org/literacyapp/visemes/ApplicationTest.java new file mode 100644 index 0000000..10b1851 --- /dev/null +++ b/app/src/androidTest/java/org/literacyapp/visemes/ApplicationTest.java @@ -0,0 +1,13 @@ +package org.literacyapp.visemes; + +import android.app.Application; +import android.test.ApplicationTestCase; + +/** + * Testing Fundamentals + */ +public class ApplicationTest extends ApplicationTestCase { + public ApplicationTest() { + super(Application.class); + } +} \ No newline at end of file diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml new file mode 100644 index 0000000..abf39f9 --- /dev/null +++ b/app/src/main/AndroidManifest.xml @@ -0,0 +1,28 @@ + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/java/org/literacyapp/visemes/FaceActivity.java b/app/src/main/java/org/literacyapp/visemes/FaceActivity.java new file mode 100644 index 0000000..6f391b7 --- /dev/null +++ b/app/src/main/java/org/literacyapp/visemes/FaceActivity.java @@ -0,0 +1,63 @@ +package org.literacyapp.visemes; + +import android.os.Bundle; +import android.support.v7.app.AppCompatActivity; +import android.view.View; +import android.widget.Button; + +import org.rajawali3d.surface.IRajawaliSurface; +import org.rajawali3d.surface.RajawaliSurfaceView; + +public class FaceActivity extends AppCompatActivity { + + private PoseRenderer poseRenderer; + private RajawaliSurfaceView surface; + + private Button letterSound1Button; + private Button letterSound2Button; + private Button letterSound3Button; + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + setContentView(R.layout.activity_face); + + letterSound1Button = (Button) findViewById(R.id.letterSound1); + letterSound2Button = (Button) findViewById(R.id.letterSound2); + letterSound3Button = (Button) findViewById(R.id.letterSound3); + + surface = (RajawaliSurfaceView) findViewById(R.id.rajwali_surface); + surface.setFrameRate(15); + surface.setRenderMode(IRajawaliSurface.RENDERMODE_WHEN_DIRTY); + + poseRenderer = new PoseRenderer(this); + surface.setSurfaceRenderer(poseRenderer); + } + + @Override + protected void onStart() { + super.onStart(); + + letterSound1Button.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View view) { + poseRenderer.renderViseme("e"); + } + }); + + letterSound2Button.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View view) { + poseRenderer.renderViseme("t"); + } + }); + + letterSound3Button.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View view) { + poseRenderer.renderViseme("a"); + } + }); + } +} diff --git a/app/src/main/java/org/literacyapp/visemes/PoseRenderer.java b/app/src/main/java/org/literacyapp/visemes/PoseRenderer.java new file mode 100644 index 0000000..dbd901d --- /dev/null +++ b/app/src/main/java/org/literacyapp/visemes/PoseRenderer.java @@ -0,0 +1,250 @@ +package org.literacyapp.visemes; + +import android.app.Activity; +import android.content.Context; +import android.content.res.Resources; +import android.graphics.Color; +import android.util.Log; +import android.view.MotionEvent; + +import org.literacyapp.visemes.util.MediaPlayerHelper; +import org.rajawali3d.Geometry3D; +import org.rajawali3d.Object3D; +import org.rajawali3d.cameras.ArcballCamera; +import org.rajawali3d.loader.LoaderOBJ; +import org.rajawali3d.renderer.RajawaliRenderer; + +public class PoseRenderer extends RajawaliRenderer { + + private static final double ONE_BILLION = 1000 * 1000 * 1000; + static final double PEAK_OFFSET = 0.5; + + private Context context; + + Object3D object3dFace; + Object3D object3dFaceAh; + Object3D object3dFaceEh; +// Object3D object3dFaceSurprise; + Object3D object3dFaceClone; + Object3D object3dFaceMorph; + + Object3D object3dHair; +// Object3D object3dLeftCornea; + Object3D object3dLeftEye; + Object3D object3dRightEye; + Object3D object3dUpperTeeth; + Object3D object3dLowerTeeth; + Object3D object3dTongue; + + public PoseRenderer(Context context) { + super(context); + Log.i(getClass().getName(), "PoseRenderer"); + this.context = context; + } + + protected void initScene() { + Log.i(getClass().getName(), "initScene"); + + try { + getCurrentScene().setBackgroundColor(Color.WHITE); + + LoaderOBJ loaderObjFace = new LoaderOBJ(getContext().getResources(), mTextureManager, R.raw.face_tri_20_obj); + loaderObjFace.parse(); + object3dFace = loaderObjFace.getParsedObject(); + Log.d(getClass().getName(), "object3dFace.getGeometry().getVertices(): " + object3dFace.getGeometry().getVertices()); + object3dFace.setTransparent(false); + + LoaderOBJ loaderObjFaceAh = new LoaderOBJ(getContext().getResources(), mTextureManager, R.raw.face_ah_tri_20_obj); + loaderObjFaceAh.parse(); + object3dFaceAh = loaderObjFaceAh.getParsedObject(); + Log.d(getClass().getName(), "object3dFaceAh.getGeometry().getVertices(): " + object3dFaceAh.getGeometry().getVertices()); + object3dFaceAh.setTransparent(false); + + LoaderOBJ loaderObjFaceEh = new LoaderOBJ(getContext().getResources(), mTextureManager, R.raw.face_eh_tri_20_obj); + loaderObjFaceEh.parse(); + object3dFaceEh = loaderObjFaceEh.getParsedObject(); + Log.d(getClass().getName(), "object3dFaceEh.getGeometry().getVertices(): " + object3dFaceEh.getGeometry().getVertices()); + object3dFaceEh.setTransparent(false); + +// LoaderOBJ loaderObjFaceSurprise = new LoaderOBJ(getContext().getResources(), mTextureManager, R.raw.face_surprise_obj); +// loaderObjFaceSurprise.parse(); +// object3dFaceSurprise = loaderObjFaceSurprise.getParsedObject(); +// Log.d(getClass().getName(), "object3dFaceSurprise.getGeometry().getVertices(): " + object3dFaceSurprise.getGeometry().getVertices()); +// object3dFaceSurprise.setTransparent(false); + + LoaderOBJ loaderObjHair = new LoaderOBJ(getContext().getResources(), mTextureManager, R.raw.hair_tri_20_obj); + loaderObjHair.parse(); + object3dHair = loaderObjHair.getParsedObject(); + object3dHair.setTransparent(false); + +// LoaderOBJ loaderObjLeftCornea = new LoaderOBJ(getContext().getResources(), mTextureManager, R.raw.left_cornea_obj); +// loaderObjLeftCornea.parse(); +// object3dLeftCornea = loaderObjLeftCornea.getParsedObject(); +// object3dLeftCornea.setTransparent(false); + + + LoaderOBJ loaderObjLeftEye = new LoaderOBJ(getContext().getResources(), mTextureManager, R.raw.left_eye_tri_20_obj); + loaderObjLeftEye.parse(); + object3dLeftEye = loaderObjLeftEye.getParsedObject(); + object3dLeftEye.setTransparent(false); + + LoaderOBJ loaderObjRightEye = new LoaderOBJ(getContext().getResources(), mTextureManager, R.raw.right_eye_tri_20_obj); + loaderObjRightEye.parse(); + object3dRightEye = loaderObjRightEye.getParsedObject(); + object3dRightEye.setTransparent(false); + + LoaderOBJ loaderObjUpperTeeth = new LoaderOBJ(getContext().getResources(), mTextureManager, R.raw.upper_teeth_tri_20_obj); + loaderObjUpperTeeth.parse(); + object3dUpperTeeth = loaderObjUpperTeeth.getParsedObject(); + object3dUpperTeeth.setTransparent(false); + + LoaderOBJ loaderObjLowerTeeth = new LoaderOBJ(getContext().getResources(), mTextureManager, R.raw.lower_teeth_tri_20_obj); + loaderObjLowerTeeth.parse(); + object3dLowerTeeth = loaderObjLowerTeeth.getParsedObject(); + object3dLowerTeeth.setTransparent(false); + + LoaderOBJ loaderObjTongue = new LoaderOBJ(getContext().getResources(), mTextureManager, R.raw.tongue_tri_20_obj); + loaderObjTongue.parse(); + object3dTongue = loaderObjTongue.getParsedObject(); + object3dTongue.setTransparent(false); + +// Material material = new Material(); +// material.enableLighting(true); +// material.setDiffuseMethod(new DiffuseMethod.Lambert()); +//// material.setColor(Color.rgb(185, 140, 111)); +// Texture texture = new Texture("Face", R.drawable.face); +// try{ +// material.addTexture(texture); +// } catch (ATexture.TextureException error){ +//// Log.d(getClass(), "TEXTURE ERROR"); +// } +// object3dFace.setMaterial(material); +// object3dFaceAh.setMaterial(material); + + object3dFaceClone = object3dFace.clone(true); +// Log.d(getClass(), "object3dFaceClone.getGeometry().getVertices(): " + object3dFaceClone.getGeometry().getVertices()); + getCurrentScene().addChild(object3dFaceClone); + getCurrentScene().addChild(object3dHair); +// getCurrentScene().addChild(object3dLeftCornea); + getCurrentScene().addChild(object3dLeftEye); + getCurrentScene().addChild(object3dRightEye); + getCurrentScene().addChild(object3dUpperTeeth); + getCurrentScene().addChild(object3dLowerTeeth); + getCurrentScene().addChild(object3dTongue); + + ArcballCamera arcball = new ArcballCamera(mContext, ((Activity) mContext).findViewById(R.id.rajwali_surface)); +// arcball.setLookAt(object3dFaceClone.getPosition()); + arcball.setPosition(0, 0, 7); + getCurrentScene().replaceAndSwitchCamera(getCurrentCamera(), arcball); + } catch (Exception e) { + e.printStackTrace(); + } + } + + @Override + protected void onRender(long ellapsedRealtime, double deltaTime) { +//// Log.d(getClass().getName(), "onRender"); +// super.onRender(ellapsedRealtime, deltaTime); +//// Log.d(getClass().getName(), "ellapsedRealtime: " + ellapsedRealtime); +//// Log.d(getClass().getName(), "deltaTime: " + deltaTime); +// double s = ellapsedRealtime/ONE_BILLION; +// Log.d(getClass().getName(), "s: " + s); +// double t = s / 4; +// Log.d(getClass().getName(), "t: " + t); +// object3dFaceClone.getGeometry().setVertices(vertices(object3dFace, object3dFaceAh, t), true); +// object3dFaceClone.getGeometry().createBuffers(); + + if (object3dFaceMorph == null) { + object3dFaceMorph = object3dFaceAh; + } + + super.onRender(ellapsedRealtime, deltaTime); + double s = ellapsedRealtime/ONE_BILLION - PEAK_OFFSET; + object3dFaceClone.getGeometry().setVertices(vertices(object3dFaceMorph, object3dFace, s), true); + object3dFaceClone.getGeometry().createBuffers(); + } + + float[] vertices(Object3D pose1, Object3D pose2, double t) { + //float weight = (float)(Math.sin(2 * Math.PI * t)/2 + 0.5); + float weight = (float)(1/(1+t*t)); + float[] v1 = Geometry3D.getFloatArrayFromBuffer(pose1.getGeometry().getVertices()); + float[] v2 = Geometry3D.getFloatArrayFromBuffer(pose2.getGeometry().getVertices()); + float[] result = new float[v1.length]; + if(v1.length == v2.length) { + for(int i=0; i + * + * See https://developer.android.com/reference/android/media/MediaPlayer.html#create%28android.content.Context,%20int%29 + */ +public class MediaPlayerHelper { + + public static void play(Context context, int resId) { + Log.i(MediaPlayerHelper.class.getName(), "play"); + + final MediaPlayer mediaPlayer = MediaPlayer.create(context, resId); + mediaPlayer.setOnCompletionListener(new MediaPlayer.OnCompletionListener() { + @Override + public void onCompletion(MediaPlayer mp) { + mediaPlayer.release(); + } + }); + mediaPlayer.start(); + } +} diff --git a/app/src/main/res/drawable/face.jpg b/app/src/main/res/drawable/face.jpg new file mode 100644 index 0000000..70f2fcb Binary files /dev/null and b/app/src/main/res/drawable/face.jpg differ diff --git a/app/src/main/res/drawable/face_specularity.jpg b/app/src/main/res/drawable/face_specularity.jpg new file mode 100644 index 0000000..6076bb8 Binary files /dev/null and b/app/src/main/res/drawable/face_specularity.jpg differ diff --git a/app/src/main/res/drawable/hair.jpg b/app/src/main/res/drawable/hair.jpg new file mode 100644 index 0000000..6bba4cb Binary files /dev/null and b/app/src/main/res/drawable/hair.jpg differ diff --git a/app/src/main/res/drawable/hair_specularity.jpg b/app/src/main/res/drawable/hair_specularity.jpg new file mode 100644 index 0000000..35a017c Binary files /dev/null and b/app/src/main/res/drawable/hair_specularity.jpg differ diff --git a/app/src/main/res/layout/activity_face.xml b/app/src/main/res/layout/activity_face.xml new file mode 100644 index 0000000..44f50f4 --- /dev/null +++ b/app/src/main/res/layout/activity_face.xml @@ -0,0 +1,41 @@ + + + + + + +